Thursday, May 7, 2009

Fun with LINQ

I had a little problem at work today that smacked of "I bet there's a clever LINQ way to do this without using local variables and side effects". The problem: given a list of whatevers, create a dictionary of whatevers whose key is the original index in the list. Don't ask why- that'd take too long to explain.

After screwing around with a false start (yield return in the key selector lambda of ToDictionary- disallowed by the compiler), I came up with something really gross-looking using Aggregate. Then I posed the problem to a couple of coworkers. Here's what we all came up with. Which one do you think runs the fastest? The answer may surprise you.



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

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<string> input = new List<string> { "1", "2", "3", "4", "5", "6" };

// #1: Matt's original with local var
var sw1 = Stopwatch.StartNew();

for (int i = 0; i < 4000000; i++)
{
int index = 0;

var dic = input.ToDictionary(k => index++);
}

sw1.Stop();

// #2: Matt's "yikes" version with .Aggregate
var sw2 = Stopwatch.StartNew();

for (int i = 0; i < 4000000; i++)
{
var dic = input.Aggregate(
new { Index = 0, Dictionary = new Dictionary() },
(a, t) => { a.Dictionary.Add(a.Index, t); return new { Index = a.Index + 1, Dictionary = a.Dictionary }; }
);
}

sw2.Stop();

// #3: James' "list ordinal hack" version
var sw3 = Stopwatch.StartNew();

for (int i = 0; i < 4000000; i++)
{
var dic2 =
input.Aggregate(new List<int>(), (lst, elt) => { lst.Add(lst.Count); return lst; })
.ToDictionary(k => k, v => input[v]);
}

sw3.Stop();

// #4: James' "nasty list hack + .Aggregate" version
var sw4 = Stopwatch.StartNew();

for (int i = 0; i < 4000000; i++)
{
var dic =
input.Aggregate(new Dictionary<int, string>(), (d, elt) => { d[d.Count] = elt; return d; });

}

sw4.Stop();

Console.WriteLine("Done. 1:{0}ms, 2:{1}ms, 3:{2}ms, 4:{3}ms", sw1.ElapsedMilliseconds, sw2.ElapsedMilliseconds, sw3.ElapsedMilliseconds, sw4.ElapsedMilliseconds);

Console.ReadKey();

}

}

}

No comments: