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())
checkif(!(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>());