19

I have a structure that can be very easily represented using a three-deep nested dictionary, like so

private static Dictionary<string, Dictionary<string, Dictionary<string,string>>> PrerenderedTemplates;

Where the structure might be used something like this

PrerenderedTemplates[instanceID][templategroup][templatepart]

Now, I realise that this code is hard to read, because from looking at the definition statement, you can't tell what it's being used for. The only advantage I can really see in changing it to Dictionary<string, PrerenderedTemplate> is readability. Converting each nesting into its own class (e.g class PrerenderedTemplate{} class TemplateGroup{} class TemplatePart{}) would add many more lines of code for little (if any) computational advantage. As far as I can see.

  • So, is my approach "ok" or should I go the extra mile and create seperate classes?
  • Is it okay to cover how the nested Dictionary works in the documentation/comments
  • Is there a best practice for handling this sort of nesting?
  • Bear in mind, this is a private member, it doesn't need to be straightforward for people using the class.

Update

So, inspired by Reza, but unable to use Tuples, I decided to create my own key generator and implement his pattern like this:

private Dictionary<string, string> PrerenderedTemplates;
private string GetPrerenderedTemplateKey(string InstanceId, string FeatureId, string OptionId)
{
    return new StringBuilder(instanceId)
    .Append(FormatTools.LIST_ENTRY_DELIMITER)
    .Append(templategroup)
    .Append(FormatTools.LIST_ENTRY_DELIMITER)
    .Append(templatepart).ToString();
}

Where FormatTools.LIST_ENTRY_DELIMITER is the Unicode Private Use Character 0xe04d.

Iain Fraser
  • 6,578
  • 8
  • 43
  • 68
  • Since the first two nestings are essentially just identifiers, perhaps I could get away with a simple Dictionary. So `PrerenderedTemplates["instance1"]["fruit"]["banana"]` could just be represented as `PrerenderedTemplates["instance1_fruit_banana"]`, like a namespace. – Iain Fraser Feb 16 '12 at 00:46
  • Do you need the ability to use `PrerenderedTemplates` to list your template groups or template parts? Sort of in the way of `PrerenderedTemplates[instanceID].Keys` or `PrerenderedTemplates[instanceID][templateGroup]`? If so then this is probably the easiest way to handle it. – M.Babcock Feb 16 '12 at 00:48
  • @M.Babcock, well, I'm looping over a collection of objects that contain metadata pointing to how to render a template. Before rendering that template, I want to check my Dictionary to make sure it hasn't been rendered before. If it hasn't, I render it and add the result to my Dictionary. (Template change-mangement is handled elsewhere) – Iain Fraser Feb 16 '12 at 00:55
  • 1
    @Iain Fraser I like your first option with a special character you don't allow in the language of keys to be the separator. I've done that in the past and it's worked for me. – Eric H Feb 16 '12 at 01:24
  • Similar http://stackoverflow.com/questions/11908991/is-there-a-benefit-to-tuple-based-or-nested-dictionaries, http://stackoverflow.com/questions/955982/tuples-or-arrays-as-dictionary-keys-in-c-sharp – nawfal May 19 '14 at 13:30

3 Answers3

18

I offer another choice:

Dictionary<Tuple<string, string, string>, string> pt;

Access to dictionary:

pt[Tuple.Create("id","group","part")]

UPDATE:

Value Tuples introduced in C# 7 is most eye-catching:

Dictionary<(string id, string group, string part), string> pt;

Access to dictionary:

pt[("id", "group", "part")]
Reza ArabQaeni
  • 4,848
  • 27
  • 46
  • Nice, I will definately give this a go and let you know how it works out! – Iain Fraser Feb 16 '12 at 00:58
  • I don't understand your mean!! – Reza ArabQaeni Feb 16 '12 at 01:03
  • 2
    Sorry Reza, I was telling you that I like your approach and I am going to try it. I should try not to use so much slang. – Iain Fraser Feb 16 '12 at 01:09
  • I should have specified that I'm using C# v3.5 and Tuple was only introduced in C# v4.0. I really wanted to use your pattern too :( – Iain Fraser Feb 16 '12 at 01:17
  • 2
    There is nothing stopping you from making similar class to be used as keys - implement Equals and GetHashCode and you are good to go (adding other things like == would be nice, but optional, i.e. sample in http://stackoverflow.com/questions/569903/multi-value-dictionary). Will be cleaner solution that concatenating keys as strings, also a bit more complex. – Alexei Levenkov Feb 16 '12 at 01:59
  • @AlexeiLevenkov, The Tuple class implements equality with this feature you want, see http://msdn.microsoft.com/en-us/library/dd387310.aspx and http://referencesource.microsoft.com/#mscorlib/system/tuple.cs#9124c4bea9ab0199 – Reza ArabQaeni May 17 '14 at 13:00
  • @RezaArabQaeni - (a bit an old topic :) ) - Indeed, but note that my comment was to Iain Fraser who could not use `Tuple` (3.5 vs 4.0)... – Alexei Levenkov May 18 '14 at 01:07
2

I would create a custom dictionary. Something like this

public class TrippleKeyDict
{
    private const string Separator = "<|>";
    private Dictionary<string, string> _dict = new Dictionary<string, string>();

    public string this[string key1, string key2, string key3]
    {
        get { return _dict[GetKey(key1, key2, key3)]; }
        set { _dict[GetKey(key1, key2, key3)] = value; }
    }

    public void Add(string key1, string key2, string key3, string value)
    {
        _dict.Add(GetKey(key1, key2, key3), value);
    }

    public bool TryGetValue(string key1, string key2, string key3, out string result)
    {
        return _dict.TryGetValue(GetKey(key1, key2, key3), out result);
    }

    private static string GetKey(string key1, string key2, string key3)
    {
        return String.Concat(key1, Separator, key2, Separator, key3);
    }
}

If you think, concatenating the strings is not safe enough, because the keys could contain the separators, then use your own key type or a Touple<string,string,string> as key. Since this implementation detail is hidden inside your custom dictionary, you can change it at any time.

You can use the dictionary like this

var dict = new TrippleKeyDict();

// Using the Add method
dict.Add(instanceID, templategroup, templatepart, "some value");

// Using the indexer
dict[instanceID, templategroup, templatepart] = "xy";
string result = dict[instanceID, templategroup, templatepart];

// Using the TryGetValue method
if (dict.TryGetValue(instanceID, templategroup, templatepart, out result)) {
    // Do something with result
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 1
    I see what you mean. A clash would occur with `hello_world > hooray > stuff` and `hello > world_hooray > stuff`. Both would use the key "hello_world_hooray_stuff"... – Iain Fraser Feb 16 '12 at 01:07
  • I used `"|"` as separator. You could use another one from which you know that it is never used in your keys, like `"<|>"`. – Olivier Jacot-Descombes Feb 16 '12 at 01:16
  • The framework I'm building this on uses a very obscure unicode character as a delimiter. Dare say I'll use that! :) – Iain Fraser Feb 16 '12 at 01:23
  • 2
    Wow, today I learned: http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters#Private_use_characters – Iain Fraser Feb 16 '12 at 01:29
  • I can't use touples since I work with .NET 3.5, however defining the dictionary as `Dictionary,string>` as Reza Arab has shown and incorporatiing it in the `TrippleKeyDict` should not be too difficult. – Olivier Jacot-Descombes Feb 16 '12 at 01:33
0

I would like to offer an alternative approach, using a SortedDictionary and a custom comparer:

    public class PrerenderedTemplate
    {
        public string instanceID;
        public string templategroup;
        public string templatepart;

        public PrerenderedTemplate(string id, string tempGroup, string tempPart)
        {
            instanceID = id;
            templategroup = tempGroup;
            templatepart = tempPart;
        }

        // custom comparer instance used as argument 
        // to SortedDictionary constructor
        public class Comparer : IComparer<PrerenderedTemplate>
        {
            public int Compare(PrerenderedTemplate x, PrerenderedTemplate y)
            {
                int compare = 0;
                if (compare == 0) compare = x.instanceID.CompareTo(y.instanceID);
                if (compare == 0) compare = x.templategroup.CompareTo(y.templategroup);
                if (compare == 0) compare = x.templatepart.CompareTo(y.templatepart);
                return compare;
            }
        }
    }

Is used like so:

    var dictionary = new SortedDictionary<PrerenderedTemplate, string>(new PrerenderedTemplate.Comparer());

    dictionary.Add(new PrerenderedTemplate("1", "2", "3"), "123");
    dictionary.Add(new PrerenderedTemplate("4", "5", "6"), "456");
    dictionary.Add(new PrerenderedTemplate("7", "8", "9"), "789");

    Assert.AreEqual<string>(dictionary[new PrerenderedTemplate("7", "8", "9")], "789");

RezaArab's answer is fit for purpose but personally I dislike Tuples on the basis of their ambiguous properties and verbose syntax.

A custom class with comparer offers more clarity and also flexibility, should any requirements change.

mungflesh
  • 776
  • 11
  • 22