4

Essentially what I need is the mapping of different int variables to a dictionary. Or at least that's the way that I was thinking of doing it. The easiest way that I can think to do it slash explain what I want is using switch statements.

string s = "";
int a = 1;
int b = 2;
int c = 0;

switch (a){
    case 0:
        s = "a0";
        break;
    case 1:
        switch (b){
            case 0:
                s = "b0";
                break
            case 1:
                switch (c){
                    case 0:
                        s = "c0";
                        break;
                    case 1:
                        s = "c1";
                        break;
                }
                break
            case 2:
                s = "b2";
                break;
        }
        break;
    case 2:
        s = "a2";
        break;
}

This being a simplified version for brevity's sake, where you would otherwise potentially have many nests and in more than one of the cases and whatnot. I was thinking that a good solution to this would be a dictionary to quickly select the correct value, but that wouldn't really nest well because most of the inner nests of a nested dictionary would not need to have values.

The reason that I thought of dictionaries first is because declaration syntax similar to the following would be nice (which is similar to what it would be for a dictionary of dictionaries).

thing = {
    {0, "a0"},
    {1, {
            {0, "b0"},
            {1, {
                    {0, "c0"}, 
                    {1, "c1"}
                }
            },
            {2, "b2"}
        }
    },
    {2, "a2"}
}
// Next line is sort of hopeful but potentially unrealistic syntax
s = thing[a][b][c]; // or  = thing(a,b,c);

EDIT: That is not a required declaration syntax, but its short and easy to understand, which is what I'm looking for.

EDIT: or LINQ, i've seen a lot of LINQ suggestions for similar questions, but I'm not particularly familiar with it.

DanielCardin
  • 545
  • 2
  • 8
  • 17

5 Answers5

3

Given that you're looking for a partial match on the key, you're not going to be able to accomplish this with a single dictionary. Here's why:

Suppose you've got some sort of "rule" class. We'll call it "Key". You might instantiate it like this:

Key.Create(0) // this "rule" would match any query key starting with 0 (e.g., {0}, {0, 1}, or {0, 1, 9, 2, 23, 243})

Now suppose you want to query it using some sort of "fact" or "query key" class. Since you query a dictionary using the type of value that was used as the key during the Add operation, you would have to reuse the same type:

Key.Create(0, 2, 13) // this fact should be matched by rules {0}, {0,2} or {0, 2, 13}

Now you're going to attempt to get the value:

var value = map[Key.Create(0, 2, 13)]

The Key class could override Equals to allow partial-key matching. However, the dictionary is going to use the hashcode first, and the hashcode for Key.Create(0, 2, 13) is never going to match the hascode for Key.Create(0). You won't be able to get around this by using any sort of base/derived type either.

The best option would probably be to roll your own class. Something like this should do:

class ResultMap
{
    public void Add(int[] key, string value)
    {
        Debug.Assert(key != null);
        Debug.Assert(key.Length > 0);

        var currentMap = _root;
        foreach (var i in key.Take(key.Length - 1))
        {
            object levelValue;
            if (currentMap.TryGetValue(i, out levelValue))
            {
                currentMap = levelValue as Dictionary<int, object>;
                if (currentMap == null)
                    throw new Exception("A rule is already defined for this key.");
            }
            else
            {
                var newMap = new Dictionary<int, object>();
                currentMap.Add(i, newMap);
                currentMap = newMap;
            }
        }
        var leaf = key[key.Length - 1];
        if (currentMap.ContainsKey(leaf))
            throw new Exception("A rule is already defined for this key.");
        currentMap.Add(leaf, value);
    }

    public string TryGetValue(params int[] key)
    {
        Debug.Assert(key != null);
        Debug.Assert(key.Length > 0);

        var currentMap = _root;
        foreach (var i in key)
        {
            object levelValue;
            if (!currentMap.TryGetValue(i, out levelValue))
                return null;
            currentMap = levelValue as Dictionary<int, object>;
            if (currentMap == null)
                return (string) levelValue;
        }

        return null;
    }

    private readonly Dictionary<int, object> _root = new Dictionary<int, object>();
}

Here's a unit test:

    public void Test()
    {
        var resultMap = new ResultMap();
        resultMap.Add(new[] {0}, "a0");
        resultMap.Add(new[] {1, 0}, "b0");
        resultMap.Add(new[] {1, 1, 0}, "c0");
        resultMap.Add(new[] {1, 1, 1}, "c1");
        resultMap.Add(new[] {1, 2}, "b2");
        resultMap.Add(new[] {2}, "a2");

        Debug.Assert("a0" == resultMap.TryGetValue(0));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 0));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 1));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 2));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 0, 0));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 0, 1));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 0, 2));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 1, 0));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 1, 1));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 1, 2));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 2, 0));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 2, 1));
        Debug.Assert("a0" == resultMap.TryGetValue(0, 2, 2));
        Debug.Assert(null == resultMap.TryGetValue(1));
        Debug.Assert("b0" == resultMap.TryGetValue(1, 0));
        Debug.Assert(null == resultMap.TryGetValue(1, 1));
        Debug.Assert("b2" == resultMap.TryGetValue(1, 2));
        Debug.Assert("b0" == resultMap.TryGetValue(1, 0, 0));
        Debug.Assert("b0" == resultMap.TryGetValue(1, 0, 1));
        Debug.Assert("b0" == resultMap.TryGetValue(1, 0, 2));
        Debug.Assert("c0" == resultMap.TryGetValue(1, 1, 0));
        Debug.Assert("c1" == resultMap.TryGetValue(1, 1, 1));
        Debug.Assert(null == resultMap.TryGetValue(1, 1, 2));
        Debug.Assert("b2" == resultMap.TryGetValue(1, 2, 0));
        Debug.Assert("b2" == resultMap.TryGetValue(1, 2, 1));
        Debug.Assert("b2" == resultMap.TryGetValue(1, 2, 2));
        Debug.Assert("a2" == resultMap.TryGetValue(2));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 0));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 1));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 2));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 0, 0));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 0, 1));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 0, 2));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 1, 0));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 1, 1));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 1, 2));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 2, 0));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 2, 1));
        Debug.Assert("a2" == resultMap.TryGetValue(2, 2, 2));
    }
Michael Gunter
  • 12,528
  • 1
  • 24
  • 58
  • I'm trying to see if I can't put some real data in, and make sure it works how I think it does, but I'm thinking it is. Thanks! – DanielCardin Jun 04 '13 at 20:45
  • This is basically what I was thinking, but I think abstracting it as a tree makes more sense. To find a value, you start at the root. Any time you are at a non-leaf node you consume another `int` and pick the corresponding branch. At leaf nodes you just output whatever value it has. Inserting a new "rule" traverses the tree in the same manner, but anytime a branch is missing you attach a non-leaf node (and the terminal state should be a non-leaf node which you are adding a leaf node to). The way the code is structured now is the same, but doesn't highlight that it's just a tree. Still, +1 – rliu Jun 04 '13 at 21:22
2

So the problem isn't quite as easy as it might seem at first glance. The big thing that I see when looking at it is the composite pattern, so we'll start out with an interface that can expose the functionality that we need:

public interface INode<TParam, TResult>
{
    TResult GetValue(TParam[] parameters, int depth);
}

I made it generic rather than hard coding in the int parameters and string return value, to make this more reusable from the point of view of a general purpose MultiKeyLookup.

Then we have the simple case, the Leaf nodes that just returns a specific value regardless of what the parameters are:

class Leaf<TParam, TResult> : INode<TParam, TResult>
{
    private TResult value;
    public Leaf(TResult value)
    {
        this.value = value;
    }
    public TResult GetValue(TParam[] parameters, int depth)
    {
        return value;
    }
}

Then we have the less trivial case. The proper Node class. It takes a number of values, and then maps each of those values to an INode object. That's where the magic happens. The INode that it maps to can be either a leaf node with just a specific value, or it could be another node. Then when asked to get a value it simply maps the input parameter at the appropriate depth and in a recursive manor gets the value of the INode for that value:

class Node<TParam, TResult> : INode<TParam, TResult>
{
    //private Tuple<TParam, INode<TParam, TResult>>[] values;
    private Dictionary<TParam, INode<TParam, TResult>> lookup;
    public Node(params Tuple<TParam, INode<TParam, TResult>>[] values)
    {
        lookup = values.ToDictionary(pair => pair.Item1,
            pair => pair.Item2);
    }

    public TResult GetValue(TParam[] parameters, int depth)
    {
        return lookup[parameters[depth]].GetValue(parameters, depth + 1);
    }
}

So at this point we could be done. Here is a (slightly simplified) example mapping:

var node = new Node<int, string>(
    Tuple.Create(0, (INode<int, string>)new Leaf<int, string>("a0")),
    Tuple.Create(1, (INode<int, string>)new Node<int, string>(
        Tuple.Create(0, (INode<int, string>)new Leaf<int, string>("b0")))));

Console.WriteLine(node.GetValue(new int[] { 0 }, 0)); //prints a0

Now that's a bit of a mess. In particular it has a ton of generic argument specifications that we know will always be the same, as well as the need to cast each type of INode to the interface type so that the Tuple is properly typed.

To make this easier I created a "builder" class, MultiKeyLookup. It will have a few helper methods for creating a leaf and a node such that the generic arguments can be specified once for this class instead. Also, since both Leaf and Node won't be needed thanks to these builders, I've made both of those classes private inner classes of MultiKeyLookup, in addition to containing those two classes it also has:

public class MultiKeyLookup<TParam, TResult>
{
    public INode<TParam, TResult> CreateLeaf(TResult result)
    {
        return new Leaf<TParam, TResult>(result);
    }

    public INode<TParam, TResult> CreateNode(
        params Tuple<TParam, INode<TParam, TResult>>[] values)
    {
        return new Node<TParam, TResult>(values);
    }

    public INode<TParam, TResult> Root { get; set; }

    public TResult GetValue(TParam[] parameters)
    {
        return Root.GetValue(parameters, 0);
    }

    //definition of Leaf goes here

    //definition of Node goes here
}

Using this class we can now write:

var map = new MultiKeyLookup<int, string>();

map.Root = map.CreateNode(
    Tuple.Create(0, map.CreateLeaf("a0")),
    Tuple.Create(1, map.CreateNode(
        Tuple.Create(0, map.CreateLeaf("b0")),
        Tuple.Create(1, map.CreateNode(
            Tuple.Create(0, map.CreateLeaf("c0")),
            Tuple.Create(1, map.CreateLeaf("c1")))),
        Tuple.Create(2, map.CreateLeaf("b2")))),
    Tuple.Create(2, map.CreateLeaf("a2")));



Console.WriteLine(map.GetValue(new int[] { 0 })); //prints a0
Console.WriteLine(map.GetValue(new int[] { 0, 0, 4 })); //prints a0
Console.WriteLine(map.GetValue(new int[] { 1, 0 })); // prints b0
Console.WriteLine(map.GetValue(new int[] { 1, 1, 0 })); //prints c0

Note that this is the full creation of what you defined in the OP, not a simplified example.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Very nice solution and thanks for the explanations! From what I can tell you (and I could be wrong) and Michael Gunter's solutions both do the same thing, but your's declarations a bit more verbose. – DanielCardin Jun 04 '13 at 20:43
0

Maybe use classes something like this:

public class A
{
    public string result;

    public A(int case)
    {
        if(case == 0)
        {
            this.result = "a0"; 
        }
        else if(case == 2)
        {
            this.result = "a2";
        }
        else
        {
            return new B(case).result;
        }
    }
}

public class B
{
    public string result;

    public B(int case)
    {
        if(case == 0)
        {
            this.result = "b0"; 
        }
        else if(case == 2)
        {
            this.result = "b2"
        }
        else
        {
            return new C(case).result;
        }
    }
}

public class C
{
    public string result;

    public C(int case)
    {
        if(case == 0)
        {
            this.result = "C0"; 
        }
        else
        {
            this.result = "c1";
        }
    }
}
0

I know you already picked a answer but I came up with a new idea and I think it's kind of cool. Using nested Dictionaries of int keys and object values as follows:

    Dictionary<int, object> baseDictionary = new Dictionary<int, object>();

    baseDictionary.Add(0, new object[] { "a1" });
    baseDictionary.Add(1, new Dictionary<int, object>());
    baseDictionary.Add(2, new object[] { "a2" });

    Dictionary<int, object> childDictionary = baseDictionary[1] as Dictionary<int, object>;
    childDictionary.Add(0, new object[] { "b1" });
    childDictionary.Add(1, new Dictionary<int, object>());
    childDictionary.Add(2, new object[] { "b2" });

    Dictionary<int, object> childTwoDictionary = childDictionary[1] as Dictionary<int, object>;
    childTwoDictionary.Add(0, new object[] { "c1" });
    childTwoDictionary.Add(1, new object[] { "c2" });

Then access the record you want you can using an recursion method with a key array like this:

private object GetResult(int keyIndex, int[] keys, Dictionary<int, object> inputDictionary)
{
    Dictionary<int, object> nextDictionary = inputDictionary[keys[keyIndex]] as Dictionary<int, object>;
    object result;

    if (nextDictionary != null && keyIndex < keys.Length)
    {
        keyIndex++;
        return GetResult(keyIndex, keys, nextDictionary);
    }
    else if(!string.IsNullOrEmpty(inputDictionary[keys[keyIndex]].ToString()))
    {
        result = inputDictionary[keys[keyIndex]] as object;
        keyIndex++;
        return result;
    }

    return new object[] { "Failed" };

}

and call it as follow:

private void simpleButton1_Click(object sender, EventArgs e)
{
    int keyIndex = 0;
    int[] keys = { 1, 1, 1 };

    object result = this.GetResult(keyIndex, keys, this.baseDictionary);
    labelControl1.Text = (((object[])(result))[0]).ToString();
}
-2

If you can know your "key structure" in advance, it might just be cheaper to use a Dictionary<string, string> and generate a key that is a concatenation of the 3 parts: "ABC"... Avoids the nest and offers direct lookups.

If you know that a = 1, b = 2, and c = 3 for example, you can concatenate them to a string of "123" and that's your key for the Dictionary lookup. This is essentially how HttpContext Caching and .NET 4.0 MemoryCache work as well.

EDIT:

If you don't always have all 3 values, use string.Format with a key structure that provides a divider/separator between values. This is generally a best practice anyway, otherwise you can have key collisions easily:

private const string _keyFormat = "{0}_{1}_{2}";

private string GenerateKey(object a, object b, object c)
{
    return string.Format(_keyFormat, a, b, c);
}
Haney
  • 32,775
  • 8
  • 59
  • 68
  • That would be fine if he used all three values all of the time. He doesn't. That makes the problem *much* harder. – Servy Jun 04 '13 at 19:12
  • I disagree, if he only had two, he just needs a sufficiently unique key. I'll update with an example. – Haney Jun 04 '13 at 19:13
  • 1
    Consider using a separator in the key so when you get to the double digit reference in the alphabet you'll save some confusion, ex: 2||10||3 a = 2; b = 10; c = 3. – JB13 Jun 04 '13 at 19:16
  • @DavidH The point is that if the first value is 0 he doesn't need to look at the second or third values. The caller will provide all three, but the value in the dictionary will just be `"0"`. They won't match, so it won't find the value. – Servy Jun 04 '13 at 19:18
  • @Servy - I don't know what to say. It's an O(1) cheap lookup. It's pretty hard to go wrong with the above method. I use it successfully every day. – Haney Jun 04 '13 at 19:19
  • 1
    @DavidH The issue is that he's not comparing the values for equality. He's wants to have a key of `0` and pass in `0_0_0` and have it match, or pass in `0_1_3` and have it match. It won't, the way you have it set up. The idea of using lookups is great, I also use them all the time; but as it stands it's not applicable given the constraints of this problem. – Servy Jun 04 '13 at 19:27
  • It will, if he stored "0" at 0_0_0 and 0_1_3. It's all about the implementation, which we can't encapsulate in StackOverflow entirely. Basically if he stores the appropriate value at each key, he's all set. – Haney Jun 04 '13 at 19:29
  • 1
    @DavidH So that means that if there are n possible values for the second position and m possible values in the third position he needs to store n*m keys, all with `0` as the first value, all pointing to the same result. That's a *lot* of excess storage space being used there, and also requires a fair bit of extra work in creating the dictionary. On top of that, if there aren't a known, finite number of values for each of the three positions that solution *can't* work. – Servy Jun 04 '13 at 19:42
  • @Servy - You're right about the memory usage, but it depends in the "cache miss" ratio. If he populates 95% of the data (meaning only 5% is the "0" or miss result, it's SIGNIFICANTLY cheaper than a "jagged array" of nested Dictionaries. Dictionaries carry significant memory footprint and overhead and so having only one with the overhead of some strings (which can be interned for even better memory savings if the majority are "0"!) is MUCH cheaper than having MANY nested within one. Memory profile it if you don't believe me. – Haney Jun 04 '13 at 19:47
  • 1
    maybe use a custom `IEqualityComparer` for the Dictionary to match 0_0_0 and 0_1_3 to the same dictionaray key – Christian Jun 04 '13 at 19:54
  • 1
    @Christian Go ahead and try it. It's impossible to do (efficiently) as the hash code for any values that match need to be the same, so you'll end up with basically everything having the same hash, meaning all of your dictionaries have become linked lists and your efficient searching has all gone to hell. – Servy Jun 04 '13 at 20:02
  • @Christian you could do it fairly reasonably, encapsulating the above logic. If (a == 0) return 0, else... etc. – Haney Jun 04 '13 at 20:08