-1

I'm trying to iterate through the following nested dictionary instance:

Dictionary<String, Dictionary<Datetime, int>> nestedDictionary =
    new Dictionary<String, Dictionary<Datetime, int>>()

The goal is to iterate through all integers in the dictionary. To iterate over the dictionary instance I use a nested foreach loop, as shown below:

int highestTargetInConstraint = 0;
foreach(String key1 in nestedDictionary.Keys) 
{
    foreach(int targetValue in nestedDictionary[key1].Values) 
    {
        if(targetValue > 5) { continue; }

        if(targetValue > highestTargetInConstraint)
        {
            highestTargetInConstraint = targetValue;
        }
    }
}

This is the most appropriate way to loop through a flat dictionary as mentioned in this post. I can't imagine to get faster than this, but maybe you can refute.

Now I'm wondering if there is a syntactically better way to loop through a nested dictionary by using a custom iterator. Useful hints and pointers to relevant documents are appreciated.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
Richard Wieditz
  • 406
  • 1
  • 4
  • 14
  • 1
    you can always use **LINQ** – styx Mar 24 '20 at 15:13
  • 2
    I hate this "most efficient" thing. "Most appropriate" is a far better question and cannot be answered without knowing what the loop body is actually doing. Please clarify. – spender Mar 24 '20 at 15:15
  • ...for instance, parallel linq might be a quick win here, but if you're modifying collections in the loop, maybe not. Is this loop for querying, or something more complicated? – spender Mar 24 '20 at 15:18

2 Answers2

2

It is more efficient to iterate through the values directly rather than iterating through the keys and then doing an extra lookup (as in your sample code).

So this is better:

foreach (var dictionary in nestedDictionary.Values)
{
    foreach (int targetValue in dictionary.Values)
    {
        // Do something with targetValue
    }
}

Here's some test code to compare the speed with the OP:

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace FooBar
{
    class Program
    {
        static void Main(string[] args)
        {
            var nestedDictionary = new Dictionary<String, Dictionary<DateTime, int>>();

            int numInnerDictionaries = 5000;
            int numInnerInts         = 5000;

            for (int i = 0; i < numInnerDictionaries; ++i)
            {
                var innerDict = new Dictionary<DateTime, int>();

                var start = new DateTime(2020, 01, 01);

                for (int j = 0; j < numInnerInts; ++j)
                    innerDict.Add(start.AddSeconds(j), j);

                nestedDictionary.Add(i.ToString(), innerDict);
            }

            var sw = new Stopwatch();

            for (int trial = 0; trial < 5; ++trial)
            {
                sw.Restart();
                method1(nestedDictionary);
                sw.Stop();
                Console.WriteLine("method1() took " + sw.Elapsed);
                double method1Ticks = sw.ElapsedTicks;

                sw.Restart();
                method2(nestedDictionary);
                sw.Stop();
                Console.WriteLine("method2() took " + sw.Elapsed);
                Console.WriteLine($"method1() was {method1Ticks/sw.ElapsedTicks} times faster.");

                Console.WriteLine();
            }
        }

        static long method1(Dictionary<String, Dictionary<DateTime, int>> nestedDictionary)
        {
            long total = 0;

            foreach (var dictionary in nestedDictionary)
            {
                foreach (var item in dictionary.Value)
                {
                    total += item.Value;
                }
            }

            return total;
        }

        static long method2(Dictionary<String, Dictionary<DateTime, int>> nestedDictionary)
        {
            long result = 0;

            foreach(String key1 in nestedDictionary.Keys)
            {
                foreach(int targetValue in nestedDictionary[key1].Values)
                {
                    result += targetValue;
                }
            }

            return result;
        }
    }
}

When I run a RELEASE (not DEBUG) build, it reports that the improved method is just over 3 times faster than the original method. A 3x speedup isn't too bad...

Of course, how much it is speeded up depends on how many outer dictionaries there are - the more there are, the more the speedup.

For example, if I change numInnerDictionaries to 50 the speedup is only around twice as fast.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    ...and so commences the theatre of context-free micro-optimisation :) Focusing on these improvements without any context is... misguided. – spender Mar 24 '20 at 15:21
  • 1
    @spender This code is both simpler and also avoids an additional lookup, so it's win/win in my opinion. – Matthew Watson Mar 24 '20 at 15:24
  • 1
    But `nestedDictionary.Values.AsParallel().SelectMany(d => d.Values)` might be a better idea. We just don't know enough about the problem domain. – spender Mar 24 '20 at 15:29
  • 1
    @spender But what we can say with certainty is the code I posted is better than the OP's code, wouldn't you agree? Which certainly answers the OPs question of "I can't imagine to get faster than this, but maybe you can refute." – Matthew Watson Mar 24 '20 at 15:31
  • 1
    I struggle with questions that don't have an obviously correct answer. Without further clarification, these sorts of questions are more like a fishing-trip, relying on guesswork and assumptions. OP could help a lot here by adding requested clarifications. I'd be much happier to contribute myself if that were the case. – spender Mar 24 '20 at 15:43
0

Here it is in LINQ. Only one line to actually do it - the rest is setup and display.

// Set up some data
var in1 = new Dictionary<DateTime, int> { { DateTime.Now, 1 }, { DateTime.Now, 2 }, { DateTime.Now, 3 } };
var in2 = new Dictionary<DateTime, int> { { DateTime.Now, 11 }, { DateTime.Now, 12 }, { DateTime.Now, 13 } };
var in3 = new Dictionary<DateTime, int> { { DateTime.Now, 21 }, { DateTime.Now, 22 }, { DateTime.Now, 23 } };

Dictionary<string, Dictionary<DateTime, int>> nestedDictionary = new Dictionary<string, Dictionary<DateTime, int>>
{
    { "A",in1 },
    { "B",in2 },
    { "C",in3 }
};

// Do the query
 IEnumerable<int> ints = nestedDictionary.SelectMany(n => n.Value.Select(s => s.Value));

// Show the results
foreach (int i in ints)
    Console.WriteLine(i);
Jasper Kent
  • 3,546
  • 15
  • 21
  • Refute if I'm wrong: According to https://stackoverflow.com/questions/60740662/c-sharp-linq-selectmany-complexity SelectMany offers O(N^2) complexity which is inefficient – Richard Wieditz Mar 24 '20 at 18:39
  • 2
    2) as @PeterDuniho observes, you link to an inconclusive post, which certainly suggests the performance is O(n\*m) where n\*m = N. Hence O(N). – Jasper Kent Mar 24 '20 at 20:41
  • 2
    3) Having just done some bench-marking I get for a 1000 dictionaries each containing 1000 dictionaries (i.e 1,000,000 items in total, N=10^6) a duration of 26ms. For N=10^7, 212ms and for N=10^8, 1919ms. That looks pretty much like O(N) to me. – Jasper Kent Mar 24 '20 at 20:44