4

基本上我需要的是不同 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 建议,但我对它并不是特别熟悉。

4

5 回答 5

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

现在假设您想使用某种“事实”或“查询键”类来查询它。由于您使用在 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) 的哈希码。您也无法通过使用任何类型的基/派生类型来解决此问题。

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

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));
    }
于 2013-06-04T19:52:46.870 回答
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 中定义的完整创建,而不是简化的示例。

于 2013-06-04T19:58:14.007 回答
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";
        }
    }
}
于 2013-06-04T19:32:23.587 回答
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();
}
于 2013-06-05T17:53:36.483 回答
-2

如果您可以提前知道您的“密钥结构”,那么使用 aDictionary<string, string>并生成一个由 3 个部分串联而成的密钥可能会更便宜:“ABC”... 避免嵌套并提供直接查找。

例如,如果您知道 a = 1、b = 2 和 c = 3,则可以将它们连接到字符串“123”,这就是字典查找的关键。这本质上是 HttpContext Caching 和 .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);
}
于 2013-06-04T19:10:40.143 回答