如何从字典中获取第n个元素?

39
cipher = new Dictionary<char,int>;
cipher.Add( 'a', 324 );
cipher.Add( 'b', 553 );
cipher.Add( 'c', 915 );
如何获取第二个元素?例如,我想要像这样的内容:
KeyValuePair pair = cipher[1]

根据 coop 的建议,使用 List,事情正在运作中:


其中 pair 包含 ( 'b', 553 )

List<KeyValuePair<char, int>> cipher = new List<KeyValuePair<char, int>>();
cipher.Add( new KeyValuePair<char, int>( 'a', 324 ) );
cipher.Add( new KeyValuePair<char, int>( 'b', 553 ) );
cipher.Add( new KeyValuePair<char, int>( 'c', 915 ) );

KeyValuePair<char, int> pair = cipher[ 1 ];

假设我的正确理解是项目会按照它们添加的顺序保留在列表中,我认为我可以只使用列表List而不是建议使用的SortedList


对于阅读此问题的人,我强烈质疑您是否需要通过索引访问字典中的元素。值得检查您的范例。同样,一个不会要求 DataReader 的第五条记录。可能只需要枚举项目。如果不是 foreach,则使用 Dictionary.GetEnumerator() 的 MoveNext() 和 Current。否则使用不同的可索引集合对象,如 SortedDictionary 或数组。有时,来自其他语言且没有 foreach 功能的编码人员会提出这个问题,因此需要适应阶段。 - FreeText
8个回答

39

问题在于字典(Dictionary)没有排序功能。您需要使用SortedList,它允许您通过索引和键获取值,不过在构造函数中可能需要指定自己的比较器来实现所需排序。然后您可以访问已排序的键值对列表,并根据需要使用IndexOfKey/IndexOfValue方法的各种组合。


10
你可以使用ElementAt(int)扩展方法,但像thecoop说的那样,它并没有排序,所以甚至不能保证在两次连续调用之间得到相同的结果。 - Sam Harwell
1
无论字典是否已排序,重要的是你需要保证第n个键返回的是第n个值,并且它可以一致地这样做。请看我的回答。 - grenade
@Gavimoss:不是的。KeysValues属性返回IList<T> - thecoop
如果使用排序列表,更简单的方法是只需使用Cipher.GetKey(n)获取第n个键和Cipher.GetByIndex(n)获取第n个值。 - Gavimoss

29

就像这样:

int n = 0;
int nthValue = cipher[cipher.Keys.ToList()[n]];

请注意,在页面顶部您还需要引用 Linq 的参考...

using System.Linq;

我认为虽然这个方法现在可能有效,但值得注意的是它并不总是有效。请纠正我如果我错了,MSDN说 "Dictionary<TKey, TValue>.KeyCollection中键的顺序是未指定的"。这意味着这个集合中的顺序不能保证始终一致。同样,遍历字典时MSDN说 "返回项的顺序是未定义的"。 - Ben
最好使用新的有序字典,如果可以的话。 - Ben
1
你说得对,Ben。然而,这是我在2009年写的一种实用方法之一,它仍然可行于2016年。有很多比我更聪明的人会解释为什么这种方法不应该起作用,也不应该使用。请随时研究所有这些回应,并了解其中的全部好处。或者,您可以直接粘贴此代码,看它神奇地完成我所说的一切,并继续过您精彩的生活。 - grenade
@grenade,非常感谢您的代码和精美散文。很抱歉地告诉您,至少在ConcurrentDictionary<T>方面,您的方法已经输给了MSDN。正如Ben所观察到的那样,返回项目的顺序是未定义的。 - user3230660

17

你真的需要通过键来查找吗?如果不需要,可以使用 List<KeyValuePair<char, int>>(或者更好的方式是创建一个类型来封装字符和整数)。

字典本质上并没有排序——在.NET中进行排序的字典实现是按照键而不是插入顺序排序的。

如果你需要按照插入顺序和键来访问这个集合,我建议将List和Dictionary封装到单个集合类型中。

或者,如果列表将会相当短,则可以通过线性搜索以索引方式进行查找...


我来到这个问题是因为我正在尝试在一个使用字典作为绘制所需项目集合的控件上实现可访问性。因此,该控件确实需要通过其键查找项目,但我还需要能够通过索引查找项目以覆盖AccessibleObject.GetChild(index As Integer) - Nick
6
也许现在五年过去了,我终于可以承认,我当时点踩只是为了让我的答案看起来比你的分数更高,这是一个明显的声望抢夺行为。为自己辩护,当时我还是个新手,而且相当刻薄。SO网站也不允许人们在投票过期后改正错误,所以我只能忍受自己的羞耻。 - grenade
3
@grenade,我刚刚注意到你真诚地承认了自己的过错!我不知道Jon是否也注意到了,但为了表彰你(而不是他需要另外10个声望点),我现在已经赞成了他的答案,以帮助弥补你的负投票。同时,通过这样做,我进一步降低了自己希望在一个问题的答案中获得比Jon Skeet更多赞的希望! - Cyberherbalist

10
您可以像这样使用ElementAt()
cipher.ElementAt(index);

这种方法比使用 Select 选项更好,因为这样你不需要遍历字典:

文档

/// <summary>Returns the element at a specified index in a sequence.</summary>
/// <returns>The element at the specified position in the source sequence.</returns>
/// <param name="source">An <see cref="T:System.Collections.Generic.IEnumerable`1" /> to return an element from.</param>
/// <param name="index">The zero-based index of the element to retrieve.</param>
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="source" /> is null.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="index" /> is less than 0 or greater than or equal to the number of elements in <paramref name="source" />.</exception>

你能否解释一下为什么这个答案比其他发布的答案更好呢?这可能会帮助其他人。 - wahwahwah
@wahwahwah 调用 ElementAt 利用 Dictionary 类的内置功能来检索元素。其他方法要么依赖于遍历 IEnumerable,要么创建一个包含所有元素的全新列表,只为了获取一个元素。 - Tarik

2

为了遵循您对字典的原始规范,我编写了一些代码并得出了以下结果:

Dictionary<string, string> d = new Dictionary<string, string>();

d.Add("a", "apple");
d.Add("b", "ball");
d.Add("c", "cat");
d.Add("d", "dog");

int t = 0;
foreach (string s in d.Values)
{
    t++;
    if (t == 2) Console.WriteLine(s);
}

而且似乎重复地将第二个项目(“ball”)写入控制台。如果将其包装到调用获取第n个元素的方法中,它可能会起作用。但这很丑陋。正如@thecoop所建议的那样,如果您可以使用SortedList,那么您会更加得心应手。

2
这里有一个类似的问题:如何检索字典中的第N个元素?。它很快就会被关闭,但我注意到这里的答案缺少了新的OrderedDictionary类。
现在有一个(自 .NET 4 开始),OrderedDictionary 类。它允许快速查找并提供排序。Item(Int32) 方法返回第n个元素。

0
您可以在您的 'cipher' 字典 上应用以下 LINQ 查询。
        var cipher = new Dictionary<char, int>();
        cipher.Add('a', 324);
        cipher.Add('b', 553);
        cipher.Add('c', 915);

        var nThValue = cipher.Select((Val, Index) => new { Val, Index })
            .Single(viPair => viPair.Index == 1)   //Selecting dictionary item with it's index using index
            .Val                                   //Extracting KeyValuePair from dictionary item
            .Value;                                //Extracting Value from KeyValuePair

0

这是一个老问题,但对我很有帮助。这里是我使用的一种实现方式。我想让第n个元素基于插入顺序。

public class IndexedDictionary<TKey, TValue> : IEnumerable<TValue> {
  private List<TValue> list = new List<TValue>();
  private Dictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>();

  public TValue this[int index] { get { return list[index]; } }
  public TValue this[TKey key] { get { return dict[key]; } }

  public Dictionary<TKey, TValue>.KeyCollection Keys { get { return dict.Keys; } }

  public int Count { get { return list.Count; } }

  public int IndexOf(TValue item) { return list.IndexOf(item);  }
  public int IndexOfKey(TKey key) { return list.IndexOf(dict[key]); } 

  public void Add(TKey key, TValue value) {
    list.Add(value);
    dict.Add(key, value);
  }

  IEnumerator<TValue> IEnumerable<TValue>.GetEnumerator() {
    return list.GetEnumerator();
  }

  IEnumerator IEnumerable.GetEnumerator() {
    return list.GetEnumerator();
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接