c#不同深度的嵌套字典

时间:2013-06-04 19:01:22

标签: c# dictionary switch-statement nested control-structure

基本上我需要的是将不同的int变量映射到字典。或者至少那是我想做的方式。我能想到的最简单的方法就是使用switch语句来解释我想要的东西。

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

这是一个简化版本,为了简洁起见,你可能会有许多巢穴和不止一个案例和诸如此类的东西。我认为对此的一个很好的解决方案是快速选择正确值的字典,但这不会很好地嵌套,因为嵌套字典的大多数内巢都不需要有值。

我首先考虑字典的原因是因为类似于下面的声明语法会很好(这与字典字典的类似)。

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

编辑:这不是一个必需的声明语法,但它简短易懂,这就是我正在寻找的。

编辑:或LINQ,我已经看到很多关于类似问题的LINQ建议,但我并不是特别熟悉它。

5 个答案:

答案 0 :(得分:3)

鉴于您正在寻找键上的部分匹配,您将无法使用单个字典完成此操作。原因如下:

假设你有某种“规则”类。我们称之为“关键”。您可以像这样实例化它:

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

现在假设您想使用某种“fact”或“query key”类来查询它。由于您使用在Add操作期间用作键的值类型查询字典,因此您必须重用相同的类型:

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

现在,您将尝试获取值:

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

Key类可以重写Equals以允许部分键匹配。但是,字典将首先使用哈希码,而Key.Create(0,2,13)的哈希码永远不会与Key.Create(0)的hascode匹配。您将无法通过使用任何类型的基类/派生类型来解决这个问题。

最好的选择可能是推出自己的课程。这样的事情应该做:

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

这是一个单元测试:

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

答案 1 :(得分:2)

所以问题并不像初看起来那么容易。在我看到它时,我看到的最重要的是复合模式,所以我们将从一个可以公开我们需要的功能的接口开始:

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

我在int参数和string返回值中将其设为通用而不是硬编码,以便从通用MultiKeyLookup的角度来看,使其更具可重用性。

然后我们有一个简单的例子,Leaf节点只返回一个特定的值,无论参数是什么:

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

然后我们有一个不那么简单的案例。适当的Node类。它需要许多值,然后将每个值映射到INode对象。这就是魔术发生的地方。它映射到的INode可以是仅具有特定值的叶节点,也可以是另一个节点。然后当被要求获取值时,它只是将输入参数映射到适当的深度,并在递归庄园中获取该值的INode值:

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

所以在这一点上我们可以完成。这是一个(略微简化的)示例映射:

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

现在有点乱。特别是它有大量的通用参数规范,我们知道这些规范将始终相同,并且需要将每种类型的INode强制转换为接口类型,以便正确键入Tuple

为了使这更容易,我创建了一个“构建器”类MultiKeyLookup。它将有一些辅助方法来创建叶子和节点,这样可以为此类指定一次泛型参数。此外,由于这些构建器不需要LeafNode,因此除了包含这两个类外,我还将这两个类都设为MultiKeyLookup的私有内部类。它还有:

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
}

使用这个课我们现在可以写:

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

请注意,这是您在OP中定义的内容的完整创建,而非简化示例。

答案 2 :(得分:0)

也许使用类似这样的类:

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

答案 3 :(得分:0)

我知道你已经选择了答案,但我提出了一个新想法,我认为这很酷。使用嵌套的int键和对象值字典,如下所示:

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

然后使用带有密钥数组的递归方法访问您想要的记录,如下所示:

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

}

并将其称为:

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

答案 4 :(得分:-2)

如果你事先能够知道你的“关键结构”,那么使用Dictionary<string, string>并生成一个由3个部分连接起来的密钥可能更便宜:“ABC”...避免嵌套并提供直接查找。

例如,如果您知道a = 1,b = 2和c = 3,则可以将它们连接到字符串“123”,这是字典查找的关键字。这本质上就是HttpContext缓存和.NET 4.0 MemoryCache的工作方式。

编辑:

如果您并不总是拥有所有3个值,请使用string.Format,其键结构在值之间提供分隔符/分隔符。无论如何,这通常是最佳实践,否则您可以轻松地进行关键碰撞:

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

private string GenerateKey(object a, object b, object c)
{
    return string.Format(_keyFormat, a, b, c);
}