多键数据结构

时间:2013-01-11 16:49:30

标签: c# generics dictionary generic-collections

我正在寻找一种可以用多个键搜索的数据结构。用例子更容易解释:

var myDataStructure = new MultiKeyDataStructure<int, string, MyType>();
myDataStructure.Add(1, "some string 1", new MyType());
myDataStructure.Add(2, "some string 2", new MyType());

var myType = new MyType();
myDataStructure.Add(3, "some string 3", myType);

Tuple<string, MyType> t1 = myDataStructure[1];
Tuple<int, MyType> t2 = myDataStructure["some string 1"];
Tuple<int, string> t3 = myDataStructure[myType];

这样的事情是否可能,如果是这样,有没有什么可以做到的?你会如何实现像键一样对待所有东西的东西,并通过搜索它们中的任何一个来返回所有相关的键?

理想情况下,您还可以使用任何数量和/或类型的参数:

var myDataStructure = new MultiKeyDataStructure<int, string, Foo, Bar>();

8 个答案:

答案 0 :(得分:5)

所以这里有一个适用于三个键。您可以按照列出的模式为4,5,6等键创建一个。这将是很多代码,但不是一项特别困难的任务(只是单调乏味)。

请注意,由于密钥的每个部分都有一个字典,因此会占用大量内存;这是您为任何密钥的实际访问灵活性所支付的价格。

public class MultiKeyDictionary<T1, T2, T3>
{
    private Dictionary<T1, Tuple<T1, T2, T3>> firstLookup = new Dictionary<T1, Tuple<T1, T2, T3>>();
    private Dictionary<T2, Tuple<T1, T2, T3>> secondLookup = new Dictionary<T2, Tuple<T1, T2, T3>>();
    private Dictionary<T3, Tuple<T1, T2, T3>> thirdLookup = new Dictionary<T3, Tuple<T1, T2, T3>>();

    public void Add(Tuple<T1, T2, T3> values)
    {
        if (!firstLookup.ContainsKey(values.Item1) &&
            !secondLookup.ContainsKey(values.Item2) &&
            !thirdLookup.ContainsKey(values.Item3))
        {
            firstLookup.Add(values.Item1, values);
            secondLookup.Add(values.Item2, values);
            thirdLookup.Add(values.Item3, values);
        }
        else
        {
            //throw an exeption or something.
        }
    }

    public Tuple<T1, T2, T3> GetFirst(T1 key)
    {
        return firstLookup[key];
    }

    public Tuple<T1, T2, T3> GetSecond(T2 key)
    {
        return secondLookup[key];
    }

    public Tuple<T1, T2, T3> GetThird(T3 key)
    {
        return thirdLookup[key];
    }

    public void RemoveFirst(T1 key)
    {
        var values = GetFirst(key);

        firstLookup.Remove(values.Item1);
        secondLookup.Remove(values.Item2);
        thirdLookup.Remove(values.Item3);
    }

    public void RemoveSecond(T2 key)
    {
        var values = GetSecond(key);

        firstLookup.Remove(values.Item1);
        secondLookup.Remove(values.Item2);
        thirdLookup.Remove(values.Item3);
    }

    public void RemoveThird(T3 key)
    {
        var values = GetThird(key);

        firstLookup.Remove(values.Item1);
        secondLookup.Remove(values.Item2);
        thirdLookup.Remove(values.Item3);
    }
}

以下是一种完全不同的方法。它不是为每个键填充查找,而是将所有值存储在单个集合中,并执行线性搜索以查找给定键的项。它将有O(n)搜索/删除时间,但O(1)添加。之前的实现有O(1)添加,删除和搜索,但占用的内存要多得多。

public class MultiKeyDictionary2<T1, T2, T3>
{
    private HashSet<Tuple<T1, T2, T3>> lookup = new HashSet<Tuple<T1, T2, T3>>();
    private HashSet<T1> firstKeys = new HashSet<T1>();
    private HashSet<T2> secondKeys = new HashSet<T2>();
    private HashSet<T3> thirdKeys = new HashSet<T3>();

    public void Add(Tuple<T1, T2, T3> values)
    {
        if (lookup.Any(multiKey => object.Equals(multiKey.Item1, values.Item1) ||
            object.Equals(multiKey.Item2, values.Item2) ||
            object.Equals(multiKey.Item3, values.Item3)))
        {
            //throw an exception or something
        }
        else
        {
            lookup.Add(values);
        }
    }

    public Tuple<T1, T2, T3> GetFirst(T1 key)
    {
        return lookup.FirstOrDefault(values => object.Equals(values.Item1, key));
    }

    public Tuple<T1, T2, T3> GetSecond(T2 key)
    {
        return lookup.FirstOrDefault(values => object.Equals(values.Item2, key));
    }

    public Tuple<T1, T2, T3> GetThird(T3 key)
    {
        return lookup.FirstOrDefault(values => object.Equals(values.Item3, key));
    }

    public void RemoveFirst(T1 key)
    {
        var values = GetFirst(key);
        if (values != null)
            lookup.Remove(values);
    }

    public void RemoveSecond(T2 key)
    {
        var values = GetSecond(key);
        if (values != null)
            lookup.Remove(values);
    }

    public void RemoveThird(T3 key)
    {
        var values = GetThird(key);
        if (values != null)
            lookup.Remove(values);
    }
}

答案 1 :(得分:2)

既然你说你想要编译时类型安全,那么你必须放弃许多事情:

  1. 具有任意数量参数的能力(C#没有可变参数)
  2. 具有相同类型的多个键的能力(编译器会抱怨模糊的重载)
  3. 使用基于反射的方法可以解决这两个限制,但是你会失去编译时类型的安全性。

    因此,根据您的约束,这是您将使用的解决方案(仅当所有泛型类型都不同时才有效!)

    class TripleKeyDictionnary<TKey1, TKey2, TKey3>
    {
        public Tuple<TKey2, TKey3> this[TKey1 key]
        {
            get
            {
                return _key1Lookup[key];
            }
        }
    
        public Tuple<TKey1, TKey3> this[TKey2 key]
        {
            get
            {
                return _key2Lookup[key];
            }
        }
    
        public Tuple<TKey1, TKey2> this[TKey3 key]
        {
            get
            {
                return _key3Lookup[key];
            }
        }
    
        private Dictionary<TKey1, Tuple<TKey2, TKey3>> _key1Lookup = new Dictionary<TKey1, Tuple<TKey2, TKey3>>();
        private Dictionary<TKey2, Tuple<TKey1, TKey3>> _key2Lookup = new Dictionary<TKey2, Tuple<TKey1, TKey3>>();
        private Dictionary<TKey3, Tuple<TKey1, TKey2>> _key3Lookup = new Dictionary<TKey3, Tuple<TKey1, TKey2>>();
    
        public void Add(TKey1 key1, TKey2 key2, TKey3 key3)
        {
            _key1Lookup.Add(key1, Tuple.Create(key2, key3));
            _key2Lookup.Add(key2, Tuple.Create(key1, key3));
            _key3Lookup.Add(key3, Tuple.Create(key1, key2));
        }
    }
    

答案 2 :(得分:2)

首先,遗憾的是没有任何内置功能,因此您必须亲自实施。

这里的问题是你不能拥有一个具有未指定数量的泛型类型定义的类,即不存在这样的类:

class MultiKeyDictionary<T1, ...>
{}

因此,您可以决定实施某些情况(2键,3键等,使用类似于Tuple<>实现的方法),或者您应该放弃类型安全。

如果你决定使用第一种方法,你应该做这样的事情(例如3键):

class ThreeKeysDict<T1,T2,T3>
{
   var dict1 = new Dictionary<T1,Tuple<T2,T3>>();
   var dict2 = new Dictionary<T2,Tuple<T1,T3>>();
   var dict3 = new Dictionary<T3,Tuple<T1,T2>>();
   public void Add(T1 key1,T2 key2, T3 key3)
   {
      dict1.Add(key1,Tuple.Create(key2,key3));
      dict2.Add(key2,Tuple.Create(key1,key3));
      dict3.Add(key3,Tuple.Create(key1,key2));
   }
   public Tuple<T2,T3> GetByKey1(T1 key1)
   {
      return dict1[key1];
   }
   public Tuple<T1,T3> GetByKey2(T2 key2)
   {
      return dict2[key2];
   }
   public Tuple<T1,T2> GetByKey3(T3 key3)
   {
      return dict3[key3];
   }
}

非通用版本将是这样的:

class MultiKeyDict
{
    Dictionary<object, object[]>[] indexesByKey;
    public MultiKeyDict(int nKeys)
    {
        indexesByKey = new Dictionary<object, object[]>[nKeys];
    }
    public void Add(params object[] values)
    {
        if (values.Length != indexesByKey.Length)
            throw new ArgumentException("Wrong number of arguments given");
        var objects = values.ToArray();
        for (int i = 0; i < indexesByKey.Length; i++)
            this.indexesByKey[i].Add(values[i], objects);
    }
    public object[] Get(int keyNum, object key)
    {
        return this.indexesByKey[keyNum][key];
    }
}

如果不同密钥的数量增加(因为它们为每个密钥采用一个字典),这两个接近都使用大量内存。


<强>声明:

未对代码进行测试,缺少空格/超出范围的检查等 他们只是为了给你一个整体的想法。

答案 3 :(得分:1)

当我遇到这样的情况时,我只使用两个字典而不是试图想出一些新的数据结构。每个Dictionary都有一个映射到该值的可能键。

如果您真的希望它被抽象化,您可以随时创建一个内部使用两个或更多字典的类,具体取决于您需要多少种不同类型的键。

答案 4 :(得分:0)

你最接近的可能是HashSet<Tuple<int, string, MyType>>。 HashSets会自动检查重复项,Tuple检查它的等值值。

class MultiKey<T1, T2, T3> : HashSet<Tuple<T1, T2, T3>>
{
    public bool Add(T1 t1, T2 t2, T3 t3)
    {
        return this.Add(Tuple.Create(t1, t2, t3));
    }
    public T1 Get(T2 t2, T3 t3)
    {
        var match = this.SingleOrDefault(x => x.Item2.Equals(t2) && x.Item3.Equals(t3));
        if (match == null) return default(T1);
        else return match.Item1;
    }
    public T2 Get(T1 t1, T3 t3)
    {
        var match = this.SingleOrDefault(x => x.Item1.Equals(t1) && x.Item3.Equals(t3));
        if (match == null) return default(T2);
        else return match.Item2;
    }
    public T3 Get(T1 t1, T2 t2)
    {
        var match = this.SingleOrDefault(x => x.Item1.Equals(t1) && x.Item2.Equals(t2));
        if (match == null) return default(T3);
        else return match.Item3;
    }
}

用法:

key.Add(1, "Foo", new MyType("foo"));
key.Add(2, "Bar", new MyType("bar"));
key.Add(2, "Bar", new MyType("bar")); // Does not add, because it already exists.
var key1 = key.Get("Bar", new Foo("bar")); // Returns 2
var defaultKey = key.Get("Bar", new Foo("foo")); // Returns 0, the default value for an int
var key2 = key.Get(1, new Foo("foo")); // returns "Foo"

如果您将MyType与大量new一起使用,则需要确保{{1}}比较其值的相等性。否则,创建一个新的将保证唯一的值。

答案 5 :(得分:0)

我不确定是否存在任何此类数据结构,但您可以创建一个。

假设键/子键是唯一的

以下是MultiKeyDictionary(使用2个内部词典,一个用于键(作为object),一个用于值)。

public class MultiKeyDictionary<TValue> 
{
    private Dictionary<Guid, TValue> values;
    private Dictionary<Object, Guid> keys;

    public MultiKeyDictionary()
    {
        keys = new Dictionary<Object,Guid>();
        values = new Dictionary<Guid,TValue>();
    }
    public IEnumerable<Object> Keys
    {
        get { return keys.Keys.AsEnumerable();} // May group according to values here
    }

    public IEnumerable<TValue> Values
    {
        get { return values.Values;}
    }

    public TValue this[object key]
    {
        get 
        {
            if (keys.ContainsKey(key)) 
            {
                var internalKey = keys[key];
                return values[internalKey];
            }
            throw new KeyNotFoundException();
        }
    }


    public void Add(TValue value,object key1, params object[] keys) // key1 to force minimum 1 key
    {
        Add(key1 , value);
        foreach( var key in keys)
        {
        Add (key, value);
        }
    }

    private void Add(Object key, TValue value)
    {
        var internalKey = Guid.NewGuid();
        keys.Add( key, internalKey);
        values.Add(internalKey, value);     
    }   
}

可以用作

MultiKeyDictionary<string> dict = new MultiKeyDictionary<string>();
dict.Add("Hello" , 1,2,3,"StringKey"); // First item is value, remaining all are keys
Console.WriteLine(dict[1]); // Note 1 is key and not intex
Console.WriteLine(dict[2]); // Note 2 is key and not index
Console.WriteLine(dict["StringKey"]);

答案 6 :(得分:0)

怎么样

class MultiKeyLookup<A, B, C> : IEnumerable<Tuple<A, B, C>>
{
    private readonly ILookup<A, Tuple<B, C>> a;
    private readonly ILookup<B, Tuple<A, C>> b;
    private readonly ILookup<C, Tuple<A, B>> c;
    private readonly IEnumerable<Tuple<A, B, C>> order;

    public MultiKeyLookup(IEnumerable<Tuple<A, B, C>> source)
    {
        this.order = source.ToList();
        this.a = this.order.ToLookup(
            o => o.Item1, 
            o => new Tuple<B, C>(o.Item2, o.Item3));
        this.b = this.order.ToLookup(
            o => o.Item2, 
            o => new Tuple<A, C>(o.Item1, o.Item3));
        this.c = this.order.ToLookup(
            o => o.Item3, 
            o => new Tuple<A, B>(o.Item1, o.Item2));
    }

    public ILookup<A, Tuple<B, C>> Item1
    {
        get
        {
            return this.a
        }
    }

    public ILookup<B, Tuple<A, C>> Item2
    {
        get
        {
            return this.b
        }
    }

    public ILookup<C, Tuple<A, B>> Item3
    {
        get
        {
            return this.c
        }
    }

    public IEnumerator<Tuple<A, B, C>> GetEnumerator()
    {
        this.order.GetEnumerator();
    }

    public IEnumerator IEnumerable.GetEnumerator()
    {
        this.order.GetEnumerator();
    }
}

你会喜欢哪个,

var multiKeyLookup = new MultiKeyLookup(
    new[] {
        Tuple.Create(1, "some string 1", new MyType()),
        Tuple.Create(2, "some string 2", new MyType())}); 

var intMatches = multiKeyLookup.Item1[1];
var stringMatches = multiKeyLookup.Item2["some string 1"];
var typeMatches = multiKeyLookup.Item3[myType];

答案 7 :(得分:-2)

添加了

System.Tuple,并将其作为字典键使用。 用法:

var dict = new Dictionary<Tuple<string, int>, DateTime>();
dict.Add(Tuple.Create("Louis", 14), new DateTime(1638, 9, 5));

虽然Tuple语法很麻烦,但静态工厂方法会在创建网站上带来很多痛苦。