C# 具有两个键的对象集合

C# 具有两个键的对象集合,c#,collections,C#,Collections,想象一下这个场景:我需要从Book类型的对象列表中操作(添加、搜索和删除)项 class Book{ int Id {get; set;} string Title {get; set;} string Author {get; set;} int Year {get; set;} // more properties } 承包商: Id在书籍集合中应该是唯一的 标题在书籍集合中应是唯一的 到目前为止,我拥有的是一本字典,它以Id作为键,以Book作为值。但是在这种情况下,如果我

想象一下这个场景:我需要从
Book
类型的对象列表中操作(添加、搜索和删除)项

class Book{
 int Id {get; set;}
 string Title {get; set;}
 string Author {get; set;}
 int Year {get; set;}
 // more properties
}
承包商:

  • Id
    书籍集合中应该是唯一的
  • 标题在
    书籍集合中应是唯一的
到目前为止,我拥有的是一本
字典
,它以
Id
作为键,以
Book
作为值。但是在这种情况下,如果我想在字典中添加一本新书,我必须遍历所有的值,以检查
标题是否重复

我开始考虑只为标题创建一个
哈希集
,或者创建第二个字典
字典
,该字典将
标题
作为键

有没有关于如何处理这种情况的建议

编辑:


正如@David提到的,我忘了告诉大家我主要关心的是性能。我想以最快的方式(O(1))按Id和标题查找对象。

您可以使用元组作为键:

var collection = new Dictionary<Tuple<int, string>, Book> (...);
var key = new Tuple<int, string>(1, "David");  // <<-----------
if(!collection.ContainsKey(key))
    collection [key] = new Book(...);
var集合=新字典(…);

var key=新元组(1,“David”);// 似乎
ID
Name
都将是唯一的,因为在中,您不应该两次使用相同的ID,不管名称是否已经被使用过。否则,我们最终将使用
dict[3]
引用两个不同的值

元组或结构不能给出这种行为,仍然需要您循环。相反,您应该使用与我创建的类类似的类:

public class TwoKeyDictionary<TKey1, TKey2, TValue>
{
    public readonly List<TKey1> firstKeys  = new List<TKey1>();
    public readonly List<TKey2> secondKeys = new List<TKey2>();
    public readonly List<TValue> values    = new List<TValue>();


    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
        if (firstKeys.Contains(key1))  throw new ArgumentException();
        if (secondKeys.Contains(key2)) throw new ArgumentException();

        firstKeys.Add(key1);
        secondKeys.Add(key2);
        values.Add(value);
    }

    public void Remove(TKey1 key) => RemoveAll(firstKeys.IndexOf(key));
    public void Remove(TKey2 key) => RemoveAll(secondKeys.IndexOf(key));
    private void RemoveAll(int index)
    {
        if (index < 1) return;

        firstKeys.RemoveAt(index);
        secondKeys.RemoveAt(index);
        values.RemoveAt(index);
    }


    public TValue this[TKey1 key1]
    {
        get
        {
            int index = firstKeys.IndexOf(key1);
            if (index < 0) throw new IndexOutOfRangeException();

            return values[firstKeys.IndexOf(key1)];
        }
    }

    public TValue this[TKey2 key2]
    {
        get
        {
            int index = secondKeys.IndexOf(key2);
            if (index < 0) throw new IndexOutOfRangeException();

            return values[secondKeys.IndexOf(key2)];
        }
    }
}
公共类TwoKeyDictionary
{
public readonly List firstKeys=new List();
public readonly List secondkey=new List();
公共只读列表值=新列表();
公共无效添加(TKey1键1、TKey2键2、TValue值)
{
if(firstKeys.Contains(key1))抛出新的ArgumentException();
if(secondKeys.Contains(key2))抛出新的ArgumentException();
firstKeys.Add(键1);
secondkey.Add(键2);
增加(价值);
}
public void Remove(TKey1 key)=>RemoveAll(firstKeys.IndexOf(key));
public void Remove(TKey2 key)=>RemoveAll(secondKeys.IndexOf(key));
私有void RemoveAll(整数索引)
{
如果(指数<1)返回;
firstKeys.RemoveAt(索引);
secondKeys.RemoveAt(索引);
移除值(索引);
}
公共TValue此[TKey1 key1]
{
得到
{
int index=firstKeys.IndexOf(key1);
如果(索引<0)抛出新的IndexOutOfRangeException();
返回值[firstKeys.IndexOf(key1)];
}
}
公共TValue this[TKey2 key2]
{
得到
{
int index=secondKeys.IndexOf(key2);
如果(索引<0)抛出新的IndexOutOfRangeException();
返回值[secondKeys.IndexOf(key2)];
}
}
}
然后你可以这样使用它:

var twoDict = new TwoKeyDictionary<int, string, float>();
twoDict.Add(0, "a", 0.5f);
twoDict.Add(2, "b", 0.25f);

Console.WriteLine(twoDict[0]);     // Prints "0.5"
Console.WriteLine(twoDict[2]);     // Prints "0.25"
Console.WriteLine(twoDict["a"]);   // Prints "0.5"
Console.WriteLine(twoDict["b"]);   // Prints "0.25"

twoDict.Add(0, "d", 2);            // Throws exception: 0 has already been added, even though "d" hasn't
twoDict.Add(1, "a", 5);            // Throws exception: "a" has already been added, even though "1" hasn't
var twoDict=new TwoKeyDictionary();
添加(0,“a”,0.5f);
添加(2,“b”,0.25f);
Console.WriteLine(twoDict[0]);//打印“0.5”
Console.WriteLine(twoDict[2]);//打印“0.25”
Console.WriteLine(twoDict[“a”]);//打印“0.5”
Console.WriteLine(twoDict[“b”]);//打印“0.25”
加上(0,“d”,2);//抛出异常:0已被添加,即使“d”尚未添加
添加(1,“a”,5);//抛出异常:“a”已添加,即使“1”尚未添加

TwoKeyDictionary
需要实现
ICollection
IEnumerable
等,以完成完整的行为操作

,以确保复合键的两侧也是唯一的,元组不会剪切它。而是制作自己的密钥,在平等检查器中检查这一点

public struct CompositeKey<T1, T2> : IEquatable<CompositeKey<T1, T2>>
{
    private static readonly EqualityComparer<T1> t1Comparer = EqualityComparer<T1>.Default;
    private static readonly EqualityComparer<T2> t2Comparer = EqualityComparer<T2>.Default;

    public T1 Key1;
    public T2 Key2;

    public CompositeKey(T1 key1, T2 key2)
    {
        Key1 = key1;
        Key2 = key2;
    }

    public override bool Equals(object obj) => obj is CompositeKey<T1, T2> && Equals((CompositeKey<T1, T2>)obj);

    public bool Equals(CompositeKey<T1, T2> other)
    {
        return t1Comparer.Equals(Key1, other.Key1)
            && t2Comparer.Equals(Key2, other.Key2);
    }

    public override int GetHashCode() => Key1.GetHashCode();
}

非常聪明和优雅!虽然我更喜欢valuetuple。我认为
字典
不如
字典
干净@Mhd,你在OP中哪里提到了你的内存问题?@David我想我需要编辑我的OP来强调性能。我知道LINQ,我可以使用Contains,但仍然是O(N)操作(对em来说,这是一个循环),而不是O(1)像查找字典一样约束是id和标题在集合中必须是唯一的,而不是像@Slai指出的那样,Tuple不会有帮助,因为我们可以添加重复的id和标题。拥有两把钥匙(1,“David”)和(1,“Mhd”)是不可接受的,我看不到任何更好的选择。。如果需要按标题查找,则另一个字典,如果不是我所能想到的最大值(但它不会像您所希望的那样优化),则HashSet是您将字典的键设置为
+“-”+
,而不是检查
if(Dict.ContainsKey())
check
if(!(Dict.Keys.Any(x=>x.Split(“-”.ToCharArray())[0]==newKey.Split(“-”).ToCharArray())[0]| | x.Split(“-”.ToCharArray())[1]==newKey.Split(“-”.ToCharArray())[1])
其中
newKey
又是
字典查找不是
O(1)
,它是
O(logn)
。如果你想
O(1)
您需要使用一个数组。使用两个字典有什么问题?在查找
Id
Title
时,它的性能可能最好。空间成本应该可以忽略不计,因为字典只存储对
Book
类的实际实例的引用。因此您只需要
numberOfBooks*sizeOf(reference)
为字典增加了一点开销,也许键会有更多的空间。--您可能需要创建一个类似于
DoubleKeyedDictionary
的包装器,使两个单独的字典保持组织/同步(添加、删除、按键访问等)。@CameronMacFarland dictionary/HashTable lookup is“”构造函数正在分别检查
firstKeys.Contains()
secondKeys.Contains()
,这会是(O(1))复杂性吗?首先,这不是构造函数,这是添加新元素的方法,而是als
public class CompositeValueTupleComparer<T1, T2> : IEqualityComparer<(T1, T2)>
{
    private static readonly EqualityComparer<T1> t1Comparer = EqualityComparer<T1>.Default;
    private static readonly EqualityComparer<T2> t2Comparer = EqualityComparer<T2>.Default;

    public bool Equals((T1, T2) x, (T1, T2) y) => 
        t1Comparer.Equals(x.Item1, y.Item1) && t2Comparer.Equals(x.Item2, y.Item2);

    public int GetHashCode((T1, T2) obj) => obj.Item1.GetHashCode();
}

new Dictionary<(int, string), Book>(new CompositeValueTupleComparer<int, string>());