基本上我需要的是将不同的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);
编辑:这不是一个必需的声明语法,但它简短易懂,这就是我正在寻找的。 p>
编辑:或LINQ,我已经看到很多关于类似问题的LINQ建议,但我并不是特别熟悉它。
答案 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
。它将有一些辅助方法来创建叶子和节点,这样可以为此类指定一次泛型参数。此外,由于这些构建器不需要Leaf
和Node
,因此除了包含这两个类外,我还将这两个类都设为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);
}