8

How do I iterate over a Dictionary's keys while maintaining the index of the key. What I've done is merge a foreach-loop with a local variable i which gets incremented by one for every round of the loop.

Here's my code that works:

public IterateOverMyDict()
{
    int i=-1;
    foreach (string key in myDict.Keys)
    {
        i++;
        Console.Write(i.ToString() + " : " + key);
    }
}

However, it seems really low tech to use a local variable i. I was wondering if there's a way where I don't have to use the "extra" variable? Not saying this is a bad way, but is there a better one?

  • Does this answer your question? [How do you get the index of the current iteration of a foreach loop?](https://stackoverflow.com/questions/43021/how-do-you-get-the-index-of-the-current-iteration-of-a-foreach-loop) – Sina Hoseinkhani May 10 '20 at 19:48

5 Answers5

25

There's no such concept as "the index of the key". You should always treat a Dictionary<TKey, TValue> as having an unpredictable order - where the order which you happen to get when iterating over it may change. (So in theory, you could add one new entry, and the entries could be in a completely different order next time you iterated over them. In theory this could even happen without you changing the data, but that's less likely in normal implementations.)

If you really want to get the numeric index which you happened to observe this time, you could use:

foreach (var x in dictionary.Select((Entry, Index) => new { Entry, Index }))
{
    Console.WriteLine("{0}: {1} = {2}", x.Index, x.Entry.Key, x.Entry.Value);
}

... but be aware that that's a fairly misleading display, as it suggests an inherent ordering.

From the documentation:

For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair<TKey, TValue> structure representing a value and its key. The order in which the items are returned is undefined.

EDIT: If you don't like the Select call here, you could create your own extension method:

public struct IndexedValue<T>
{
    private readonly T value;
    private readonly int index;

    public T Value { get { return value; } }
    public int Index { get { return index; } }

    public IndexedValue(T value, int index)
    {
        this.value = value;
        this.index = index;
    }
}

public static class Extensions
{
    public static IEnumerable<IndexedValue<T>> WithIndex<T>
        (this IEnumerable<T> source)
    {
        return source.Select((value, index) => new IndexedValue<T>(value, index));
    }
}

Then your loop would be:

foreach (var x in dictionary.WithIndex())
{
    Console.WriteLine("{0}: {1} = {2}", x.Index, x.Value.Key, x.Value.Value);
}
Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • 1
    The order doesn't matter to me, and I don't need the `TValue` at the moment, I just need to print the keys with some kind of index in front of them so user can then type `select 1` and it will select the key with index of 1 (the key with `1` in front of it in the printed version, that is.) –  May 02 '13 at 15:17
  • @MarkusMeskanen: So why did you ask a question in terms of "while maintaining the index of the key"? Anyway, the LINQ query I've shown you should help. – Jon Skeet May 02 '13 at 15:19
  • Apologies for not speaking English as my native language, I have no idea how should I ask it if that's not clear enough. :/ However, that's why I gave you the code I'm using atm, as an example to show I'm not using `TValue` at all and I'm only printing the key and an int in front of it. And with all respect, that LINQ looks terrible and I'd rather use my own solution, thanks for your answer tho :) –  May 02 '13 at 15:23
  • 1
    @MarkusMeskanen: Just explaining that you knew the order was unstable (and why you *want* such a misleading display) would have been helpful. I don't see what's wrong with the LINQ - given that you need to get an extra value from somewhere, what possible other solution would you have expected? (You could write a method to "index" *any* sequence, but via a custom struct, I suppose...) – Jon Skeet May 02 '13 at 15:26
3

Technically, the key is the index in a Dictionary<TKey, TValue>. You're not guaranteed to get the items in any specific order, so there's really no numeric index to be applied.

Justin Niessner
  • 229,755
  • 35
  • 391
  • 521
  • I don't need them in any specific order, I just need to iterate through the keys and print them with an int from 1 to Keys.Count in front of them –  May 02 '13 at 15:26
1

Not really. Note that keys in a dictionary are not logically "ordered". They don't have an index. There is no first or last key, from the Dictionary's point of view. You can keep track on your own whether this is the first key returned by the enumerator, as you are doing, but the Dictionary has no concept of "give me the 5th key", so you couldn't use a for loop with an indexer as you could with a list or array.

Servy
  • 193,745
  • 23
  • 295
  • 406
0

Dictionaries are not exactly lists, arrays, or vectors. They take those constructs a step further. The key can be the index:

Dictionary myDictionary<int, string> = new Dictionary<int, string>()
{
    {0, "cat"},
    {1, "dog"},
    {2, "pig"},
    {3, "horse"}
};
myDictionary[4] = "hat";

for int i = 0; i <5; i++){
    Console.Writeline(myDictionary[i]);
}

At this point you are probably missing most of the benefits of a dictionary (which is similar to enumeration with the benefit of sorting quickly on key values), and using it like a list.

Jos
  • 1,344
  • 3
  • 16
  • 33
amalgamate
  • 2,000
  • 5
  • 20
  • 37
-1

The Select((Entry, Index) => new { Entry, Index }) approach is probably best in the specific context of this question but, as an alternative, System.Linq.Enumerable now lets you convert a dictionary into a list. Something like this would work:

var x = dictionary.ToList();
for (int y=0; y<x.Count; y++) Console.WriteLine(y + " = " + x[y].Key);

There are pros & cons to both approaches depending on what you're trying to do.

ron
  • 1,002
  • 7
  • 15