将重复键添加到我的字典中
Getting duplicate keys added to my Dictionary
我正在尝试将一些代码从 Java 移植到 C#。在 C# 中,我使用字典而不是 Java HashMap。但是,我发现当我调用 Dictionary.Add() 时,我得到了重复的密钥。我怀疑这是因为键是不同的引用类型,但对象应该具有相等性。我在这里创建了一个最小的例子:
摘要Class:
using System;
using System.Text;
public abstract class HandState
{
public int[] _cards; // ranks only
public int _index; // into _allStates array
public char _levelIndex; // into _levelArrays[n]
private static String _cardValues = "23456789TJQKA";
private static int SUIT_COUNT = 4;
public abstract bool IsFinal();
public abstract char Value(); // the generated array value
public override String ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("[");
for (int i = 0; i < _cards.Length; i++)
{
int card = _cards[i];
sb.Append(_cardValues[card]);
if (i != _cards.Length - 1)
sb.Append(" ");
}
sb.Append("]");
return sb.ToString();
}
public bool LegalTransistion(int card)
{
int rankCount = 0;
for (int i = 0; i < _cards.Length; i++)
{
if (_cards[i] == card)
{
rankCount++;
}
}
return rankCount != SUIT_COUNT;
}
public void Init(HandState prev, int card)
{
int prevlen = prev._cards.Length;
_cards = new int[prevlen + 1];
Array.Copy(prev._cards, 0, _cards, 0, prevlen);
_cards[prevlen] = card;
Array.Sort(_cards);
}
}
分机 Class:
using System;
using System.Collections;
using System.Collections.Generic;
public class NonFinalHandState : HandState //extends
{
public HandState[] _transistions;
private static List<HandState> _allStatesVec = new List<HandState>();
private static int RANK_SIZE = 13;
public NonFinalHandState(int[] cards)
{
_cards = (int[])cards.Clone();
}
public NonFinalHandState(NonFinalHandState prev, int card)
{
Init(prev, card);
}
public NonFinalHandState()
{
_cards = new int[0];
_index = _allStatesVec.Count;
_transistions = new HandState[RANK_SIZE];
_allStatesVec.Add(this);
}
public override bool IsFinal()
{
return false;
}
public override char Value()
{
return _levelIndex;
}
public override bool Equals(Object o)
{
if (!(o.GetType() == typeof(NonFinalHandState)))
return false;
NonFinalHandState other = (NonFinalHandState)o;
return Array.Equals(_cards, other._cards);
}
public override int GetHashCode()
{
return ((IStructuralEquatable)_cards).GetHashCode(EqualityComparer<int>.Default); //JAVA: Arrays.hashCode(_cards);
}
}
计划:
using System;
using System.Collections.Generic;
namespace ConsoleAppTest
{
class Program
{
static void Main(string[] args)
{
int[] cards = { 1, 2, 3, 4 };
NonFinalHandState nfhs1 = new NonFinalHandState(cards);
NonFinalHandState nfhs2 = new NonFinalHandState(cards);
Dictionary<HandState, HandState> dictionary = new Dictionary<HandState, HandState>();
dictionary.Add(nfhs1, nfhs1);
//dictionary.Add(nfhs2, nfhs2);
if (!dictionary.ContainsKey(nfhs2))
{
dictionary.Add(nfhs2, nfhs2);
}
else
{
dictionary[nfhs2] = nfhs2;
}
Console.WriteLine(dictionary.Keys.Count);
}
}
}
输出为 2,但我需要将键识别为相同。我认为 Dictionary.Add() 检查键的相等性但即便如此,我在添加新条目之前检查 containsKey 。我怀疑我的 GetHashCode 的 C# 实现可能有问题? Java 版本显示为注释。
有什么想法吗?
我认为问题在于您的GetHashCode
不return相等数组的值:
public override int GetHashCode()
{
return ((IStructuralEquatable)_cards).GetHashCode(EqualityComparer<int>.Default); //JAVA: Arrays.hashCode(_cards);
}
这会起作用:
public override int GetHashCode()
{
if(_cards == null) return int.MinValue;
unchecked
{
int hash = 17;
foreach(int c in _cards)
hash = hash * 23 + c.GetHashCode();
return hash;
}
}
你还要修正Equals
中的逻辑,这是错误的,Array.Equals
比较参考:
public override bool Equals(Object o)
{
if (!(o.GetType() == typeof(NonFinalHandState)))
return false;
NonFinalHandState other = (NonFinalHandState)o;
return Array.Equals(_cards, other._cards);
}
使用这个:
public override bool Equals(Object o)
{
NonFinalHandState other = o as NonFinalHandState;
if(other == null) return false;
if(this._cards?.Equals(other._cards) == true) return true; // reference comparison
if(_cards == null || other._cards == null) return false;
return _cards.SequenceEqual(other._cards); // add using System.Linq
}
我将向您展示另一种方法,您可以使用适用于任何类型序列(如数组或列表)的自定义 IEqualityComparer<T>
:
public class EnumerableComparer<T> : IEqualityComparer<IEnumerable<T>>
{
private IEqualityComparer<T> m_comparer;
public EnumerableComparer()
{
m_comparer = EqualityComparer<T>.Default;
}
public EnumerableComparer(IEqualityComparer<T> comparer)
{
m_comparer = comparer ?? EqualityComparer<T>.Default;
}
public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
{
return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y, m_comparer));
}
public int GetHashCode(IEnumerable<T> items)
{
if(items == null) return 0;
unchecked
{
int hash = 17;
foreach (T item in items)
hash = hash * 23 + m_comparer.GetHashCode(t);
return hash;
}
}
}
有了这个 EnumerableComparer
你也可以“喂养”你的 NonFinalHandState
:
public class NonFinalHandState : HandState //extends
{
private EnumerableComparer<int> _cardsComparer = new EnumerableComparer<int>();
// ...
public override bool Equals(Object o) => o is NonFinalHandState nfhs2 && _cardsComparer.Equals(_cards, nfhs2._cards);
public override int GetHashCode() => _cardsComparer.GetHashCode(_cards);
}
我正在尝试将一些代码从 Java 移植到 C#。在 C# 中,我使用字典而不是 Java HashMap。但是,我发现当我调用 Dictionary.Add() 时,我得到了重复的密钥。我怀疑这是因为键是不同的引用类型,但对象应该具有相等性。我在这里创建了一个最小的例子:
摘要Class:
using System;
using System.Text;
public abstract class HandState
{
public int[] _cards; // ranks only
public int _index; // into _allStates array
public char _levelIndex; // into _levelArrays[n]
private static String _cardValues = "23456789TJQKA";
private static int SUIT_COUNT = 4;
public abstract bool IsFinal();
public abstract char Value(); // the generated array value
public override String ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append("[");
for (int i = 0; i < _cards.Length; i++)
{
int card = _cards[i];
sb.Append(_cardValues[card]);
if (i != _cards.Length - 1)
sb.Append(" ");
}
sb.Append("]");
return sb.ToString();
}
public bool LegalTransistion(int card)
{
int rankCount = 0;
for (int i = 0; i < _cards.Length; i++)
{
if (_cards[i] == card)
{
rankCount++;
}
}
return rankCount != SUIT_COUNT;
}
public void Init(HandState prev, int card)
{
int prevlen = prev._cards.Length;
_cards = new int[prevlen + 1];
Array.Copy(prev._cards, 0, _cards, 0, prevlen);
_cards[prevlen] = card;
Array.Sort(_cards);
}
}
分机 Class:
using System;
using System.Collections;
using System.Collections.Generic;
public class NonFinalHandState : HandState //extends
{
public HandState[] _transistions;
private static List<HandState> _allStatesVec = new List<HandState>();
private static int RANK_SIZE = 13;
public NonFinalHandState(int[] cards)
{
_cards = (int[])cards.Clone();
}
public NonFinalHandState(NonFinalHandState prev, int card)
{
Init(prev, card);
}
public NonFinalHandState()
{
_cards = new int[0];
_index = _allStatesVec.Count;
_transistions = new HandState[RANK_SIZE];
_allStatesVec.Add(this);
}
public override bool IsFinal()
{
return false;
}
public override char Value()
{
return _levelIndex;
}
public override bool Equals(Object o)
{
if (!(o.GetType() == typeof(NonFinalHandState)))
return false;
NonFinalHandState other = (NonFinalHandState)o;
return Array.Equals(_cards, other._cards);
}
public override int GetHashCode()
{
return ((IStructuralEquatable)_cards).GetHashCode(EqualityComparer<int>.Default); //JAVA: Arrays.hashCode(_cards);
}
}
计划:
using System;
using System.Collections.Generic;
namespace ConsoleAppTest
{
class Program
{
static void Main(string[] args)
{
int[] cards = { 1, 2, 3, 4 };
NonFinalHandState nfhs1 = new NonFinalHandState(cards);
NonFinalHandState nfhs2 = new NonFinalHandState(cards);
Dictionary<HandState, HandState> dictionary = new Dictionary<HandState, HandState>();
dictionary.Add(nfhs1, nfhs1);
//dictionary.Add(nfhs2, nfhs2);
if (!dictionary.ContainsKey(nfhs2))
{
dictionary.Add(nfhs2, nfhs2);
}
else
{
dictionary[nfhs2] = nfhs2;
}
Console.WriteLine(dictionary.Keys.Count);
}
}
}
输出为 2,但我需要将键识别为相同。我认为 Dictionary.Add() 检查键的相等性但即便如此,我在添加新条目之前检查 containsKey 。我怀疑我的 GetHashCode 的 C# 实现可能有问题? Java 版本显示为注释。
有什么想法吗?
我认为问题在于您的GetHashCode
不return相等数组的值:
public override int GetHashCode()
{
return ((IStructuralEquatable)_cards).GetHashCode(EqualityComparer<int>.Default); //JAVA: Arrays.hashCode(_cards);
}
这会起作用:
public override int GetHashCode()
{
if(_cards == null) return int.MinValue;
unchecked
{
int hash = 17;
foreach(int c in _cards)
hash = hash * 23 + c.GetHashCode();
return hash;
}
}
你还要修正Equals
中的逻辑,这是错误的,Array.Equals
比较参考:
public override bool Equals(Object o)
{
if (!(o.GetType() == typeof(NonFinalHandState)))
return false;
NonFinalHandState other = (NonFinalHandState)o;
return Array.Equals(_cards, other._cards);
}
使用这个:
public override bool Equals(Object o)
{
NonFinalHandState other = o as NonFinalHandState;
if(other == null) return false;
if(this._cards?.Equals(other._cards) == true) return true; // reference comparison
if(_cards == null || other._cards == null) return false;
return _cards.SequenceEqual(other._cards); // add using System.Linq
}
我将向您展示另一种方法,您可以使用适用于任何类型序列(如数组或列表)的自定义 IEqualityComparer<T>
:
public class EnumerableComparer<T> : IEqualityComparer<IEnumerable<T>>
{
private IEqualityComparer<T> m_comparer;
public EnumerableComparer()
{
m_comparer = EqualityComparer<T>.Default;
}
public EnumerableComparer(IEqualityComparer<T> comparer)
{
m_comparer = comparer ?? EqualityComparer<T>.Default;
}
public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
{
return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y, m_comparer));
}
public int GetHashCode(IEnumerable<T> items)
{
if(items == null) return 0;
unchecked
{
int hash = 17;
foreach (T item in items)
hash = hash * 23 + m_comparer.GetHashCode(t);
return hash;
}
}
}
有了这个 EnumerableComparer
你也可以“喂养”你的 NonFinalHandState
:
public class NonFinalHandState : HandState //extends
{
private EnumerableComparer<int> _cardsComparer = new EnumerableComparer<int>();
// ...
public override bool Equals(Object o) => o is NonFinalHandState nfhs2 && _cardsComparer.Equals(_cards, nfhs2._cards);
public override int GetHashCode() => _cardsComparer.GetHashCode(_cards);
}