Wednesday, June 25, 2008

Goodbye to Awkward Dictionaries in .NET

I take enjoyment in analytic work, and often find myself writing code to empirically test a bunch of XML files to see if I've got the "implied schema" right. For example, I might have 500 files of similar structure, and I'd want to see the possible values (and count) of the "/Reports/Globals/@Client" node's value (using XPath). Usually this would entail using a Dictionary and every access would have to test if the dictionary already contains the key ... until now. After seeing that Ruby's designers allow you to specify a default value for a Hash, I took another step forward, allowing a user of the class to provide a default function, allowing more complex defaults such as empty lists.

using System;
using System.Collections.Generic;

namespace ConsoleApplication6
{
class Program
{
public delegate T DefaultFunction<T>();

public class RubyDictionary<TKey, TValue>
{
private IDictionary<TKey, TValue> inner = new Dictionary<TKey, TValue>();
private DefaultFunction<TValue> defaultFunction;

private static TValue Default()
{
return default(TValue);
}

public RubyDictionary()
{
defaultFunction = Default;
}

public RubyDictionary(TValue defaultValue)
{
defaultFunction = delegate()
{
return defaultValue;
};
}

public RubyDictionary(DefaultFunction<TValue> defaultFunction)
{
this.defaultFunction = defaultFunction;
}

public TValue this[TKey key]
{
get
{
if (!inner.ContainsKey(key))
{
inner.Add(key, defaultFunction());
}
return inner[key];
}
set
{
inner[key] = value;
}
}
}

static void Main(string[] args)
{
try
{
RubyDictionary<string, int> dict = new RubyDictionary<string, int>();
Console.WriteLine(dict["9"]);
dict["8"] += 1;
Console.WriteLine(dict["8"]);
dict["7"] = 5;
Console.WriteLine(dict["7"]);
dict["6"]++;
Console.WriteLine(dict["6"]);

dict["5"]--; dict["5"]--;
Console.WriteLine(dict["5"]);


RubyDictionary<string, IList<string>> lists = new RubyDictionary<string, IList<string>>(delegate() { return new List<string>(); });
lists["shopping"].Add("bread");
lists["shopping"].Add("milk");
lists["todo"].Remove("write this dictionary class");

foreach (string item in lists["shopping"])
{
Console.WriteLine(item);
}

RubyDictionary<int, int> foo = new RubyDictionary<int, int>(777);
Console.WriteLine(foo[8] - 111);
}
catch (Exception exception)
{
Console.Error.WriteLine(exception);
}
finally
{
if (System.Diagnostics.Debugger.IsAttached)
{
Console.WriteLine("Press enter to continue...");
Console.ReadLine();
}
}
}
}
}