关于具有可变元素的字典/哈希集的注意事项

considerations on dictionaries/hashsets with mutable elements

提问人:Kjara 提问时间:10/5/2017 更新时间:10/5/2017 访问量:444

问:

我是字典和哈希集的忠实粉丝,因为它们允许快速检查包含性,如果是字典,还可以通过键快速访问元素。

它们的缺点是它们只适用于(准)不可变对象:由于它们基于哈希,我们必须保证哈希代码(字典的键,哈希集的元素)永远不会改变,否则它们将无法再找到元素。

但有时我需要字典/哈希集的好处,并且仍然能够以某种方式更改键/元素,以便它们的哈希代码也发生变化。示例:我想在集合中存储 2d 点。2d 点由两个 int 值组成,如果两个 2d 点的 int 值相等,则它们相等。我需要能够更改点的值,但我仍然需要一种快速方法来检查集合中是否包含某个点。

所以我在想一种方法来让它发挥作用。我需要什么?

  • 每当我想更改元素的数据时,我都需要 1。从字典/集合中删除元素, 2.更改数据,3.再次将元素添加到字典/集。然后,哈希码和存储元素的存储桶将始终保持同步。
  • 我还需要一种机制来自动为所有字典/集执行此操作。否则,如果我有 100 个字典/集包含一个将要更改的元素,我将需要跟踪它们并单独更新每个字典/集。

下面是一些示例代码,用于说明我计划如何解决 2D 点和集合的问题:

interface IChangeable<T>
{
    event EventHandler<T> OnStartChange; // must be run before changing data
    event EventHandler<T> OnEndChange;  // must be run after changing data
}

class Point : IChangeable<Point>
{
    private int x;
    private int y;

    public Point(int a, int b) { x = a; y = b; }

    public event EventHandler<Point> OnStartChange;
    public event EventHandler<Point> OnEndChange;

    public int X
    {
        get => x;
        set
        {
            if (x.Equals(value)) // no change => no need to do anything
                return;

            OnStartChange?.Invoke(this, this); // remove current point from set
            x = value;
            OnEndChange?.Invoke(this, this); // add changed point to set
        }
    }

    public int Y { ... } // analogous to X

    public override int GetHashCode() { return x + 2 * y; }
    public override bool Equals(object obj)
    {
        Point other = obj as Point;
        if (other == null)
            return false;
        return (x.Equals(other.x) && y.Equals(other.y));
    }
}

class MySet<T> : ISet<T> where T : class, IChangeable<T>
{
    HashSet<T> internalSet;

    public MySet() { internalSet = new HashSet<T>(); }

    public bool Add(T item)
    {
        if (!internalSet.Add(item)) // if already contained, do nothing
            return false;

        item.OnStartChange += TakeOut; // else subscribe to data change events
        item.OnEndChange += PutIn;
        return true;
    }

    public bool Remove(T item)
    {
        if (!internalSet.Remove(item)) // if not contained, do nothing
            return false;

        item.OnStartChange -= TakeOut; // else unsubscribe to data change events
        item.OnEndChange -= PutIn;
        return true;
    }

    public bool Contains(T item) { return internalSet.Contains(item); }

    private void TakeOut(object obj, T elem) { internalSet.Remove(elem); }

    private void PutIn(object obj, T elem) { internalSet.Add(elem); }

    ... // implement the rest of ISet<T> interface
}

因此,我需要使用而不是常规 ,并且我需要为要更改的每条数据使用两个事件来构建 setter(只要该数据已合并到 or 方法中)。MySetHashSetGetHashCodeEquals

我的问题是:

您是否发现我的方法有任何错误或此方法存在任何问题?

  • 代码可读性/可理解性问题
  • 实施问题
  • 性能问题
  • 正确性问题
  • ...

应该注意的是,更改数据不会经常发生。因此,我确信,能够快速检查包含性的性能提升将超过每次数据更改后更新集合对性能的影响。

c# 字典 哈希集 可变

评论

0赞 Fabjan 10/5/2017
我想说的是,基于事件的方法在这里似乎有点矫枉过正。为什么不简单地使用并检查是否存在某个具有 x;y 坐标的某个点,如果您需要更改它 - 只需删除它并添加一个新点......在 Winforms 类中,Point 是不可变的(以及 Dictionary 中的 KeyValuePair),这并非没有原因。Dictionary<Tuple<int, int>, Point>
1赞 Sinatr 10/5/2017
除非您有问题,否则这是代码审查的问题。对我来说,这样的要求是一种异国情调。字典对可变值没有问题,只有键是问题。但是我为什么要改变密钥呢?它们通常不是数据的一部分,仅与字典结合使用,这意味着:删除带有旧键的条目、更改键、添加新条目。
0赞 Kjara 10/5/2017
@Fabjan只是为了说明。我的实际数据与点无关,但它不适合一个小代码示例。Point
0赞 Kjara 10/5/2017
@Sinatr 在我的情况下,我的对象中的键实际上是值的一部分(对于这种情况,.NET 提供了一个名为 的抽象类,但我想从头开始执行此操作,以便更轻松地查找错误)。通常键是对象的名称,我需要能够重命名对象。KeyedCollection

答: 暂无答案