Вопрос

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.

Это было полезно?

Решение

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));
    }

Другие советы

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.

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";
        }
    }
}

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();
}

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);
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top