How would you traverse the Dictionary?

One: background

1. Tell a story

Yesterday I saw a very interesting question on StackOverflow. It said: You can traverse the dictionary in several ways, and then the posts are all kinds of wonderful answers. It is very interesting. National Day is coming soon. Let’s entertain. Let’s talk about this. Quite boring question????????????.

Two: use foreach to traverse

In order to facilitate the demonstration, first a piece of test code:


            var dict = new Dictionary<int, string>()
            {
                [10] = "A10",
                [20] = "A20",
                [30] = "A30",
                [40] = "A40",
                [50] = "A50"
            };

1. Direct foreach dict

If you want to speak with a percentage, it is estimated that 50%+ of the small partners use this method. Why is it simple and rude, there is nothing else to say, just go to the code:


            foreach (var item in dict)
            {
                Console.WriteLine($"key={item.Key},value={item.Value}");
            }

The item here is packaged with KeyValuePair at the bottom in the MoveNext process. If you don't believe it, look at the source code:


 public bool MoveNext()
 {
  while ((uint)_index < (uint)_dictionary._count)
  {
   ref Entry reference = ref _dictionary._entries[_index++];
   if (reference.next >= -1)
   {
    _current = new KeyValuePair<TKey, TValue>(reference.key, reference.value);
    return true;
   }
  }
 }

2. Use KeyPairValue to deconstruct in foreach

Just now you have also seen that item is of type KeyValuePair, but ???????? Netcore has enhanced KeyValuePair and added a Deconstruct function to deconstruct KeyValuePair. The code is as follows:


    public readonly struct KeyValuePair<TKey, TValue>
    {
        private readonly TKey key;

        private readonly TValue value;

        public TKey Key => key;

        public TValue Value => value;

        public KeyValuePair(TKey key, TValue value)
        {
            this.key = key;
            this.value = value;
        }

        public void Deconstruct(out TKey key, out TValue value)
        {
            key = Key;
            value = Value;
        }
    }

With this destructuring function, you can get the key and value directly during the traversal process instead of the packaged KeyValuePair. This is not possible in netframework. The implementation code is as follows:


            foreach ((int key, string value) in dict)
            {
                Console.WriteLine($"key={key},value={value}");
            }

3. foreach keys

The previous examples are to directly foreach the dict. In fact, you can also perform foreach traversal on dict.keys, and then use the traversed keys to read the dict by a class indexer. The code is as follows:


            foreach (var key in dict.Keys)
            {
                Console.WriteLine($"key={key},value={dict[key]}");
            }

Having said that, I don’t know if you have a subconscious mind, that is, dict can only be traversed by foreach. Is this the truth? To find the answer, or look back at how foreach is traversed.


public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, IEnumerator, IDictionaryEnumerator
{
    public bool MoveNext()
 {
  while ((uint)_index < (uint)_dictionary._count)
  {
   ref Entry reference = ref _dictionary._entries[_index++];
   if (reference.next >= -1)
   {
    _current = new KeyValuePair<TKey, TValue>(reference.key, reference.value);
    return true;
   }
  }
  _index = _dictionary._count + 1;
  _current = default(KeyValuePair<TKey, TValue>);
  return false;
 }
}

If you look carefully at this while loop, you should understand that in essence it also traverses the entries array. The bottom layer uses while. Can I replace it with for and then loop the dict? Haha, it's just imitating anyway.

Three: use for to traverse

In order to simulate the code in MoveNext, the key point is this statement:, ref Entry reference = ref _dictionary._entries[_index++];In fact, it is very simple. Linq's ElementAt method can be used to extract the contents of the _entries array. Is it ~~~? The transformed code is as follows:


            for (int i = 0; i < dict.Count; i++)
            {
                (int key, string value) = dict.ElementAt(i);

                Console.WriteLine($"key={key},value={dict[key]}");
            }

Next, are you curious how this ElementAt extension method is implemented? Let's take a look at the source code.


    public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index)
    {
        IList<TSource> list = source as IList<TSource>;
        if (list != null)
        {
            return list[index];
        }
        if (index >= 0)
        {
            using (IEnumerator<TSource> enumerator = source.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    if (index == 0)
                    {
                        return enumerator.Current;
                    }
                    index--;
                }
            }
        }
    }

As you can see from the above code, if the current source does not implement the IList interface, it is a huge pit. Every time the ElementAt method is executed, the worst time complexity is O(N), just take the for loop just now. , Its worst time complexity is O(n!), is it more terrifying than you think, the lesson is to practice more, look at the source code more~

Four: Summary

This article lists 4 ways to traverse the dict. Which ones will you use? It should be noted that at the end ElementAt must understand the big hole in the Source judgment, don't take it for granted that it is O(N), well, more traversal methods are welcome to add!

Guess you like

Origin blog.csdn.net/sD7O95O/article/details/108877846