使用可变键从集合中检索项的有效方法

Efficient way of retrieving an item from a collection with a mutable key

提问人:XavierAM 提问时间:6/23/2020 最后编辑:XavierAM 更新时间:6/23/2020 访问量:52

问:

我有一个具有 FooPosition 属性的 Foos 项目集合,我需要通过它们的位置快速访问 Foos

例如:检索位于 X=0 和 Y=1 的 Foo

我的第一个想法是为此目的使用字典,并使用 FooPosition 作为字典键。我知道我收藏中的每个 FooPosition 都是独一无二的,如果不是这种情况,我不介意抛出一个异常。

只要 Foos 不到处移动,这就可以很好地工作。

但是,正如我想出了艰难的方法,并且由于这篇文章这篇文章而理解,如果 FooPosition 更新,这将不再起作用。我不应该在字典中使用可变键:字典将 FooPosition HashCode 保存在内存中,但在修改底层 FooPosition 时不会更新它。因此,调用 dic[Position(0,1)] 会给我生成字典时处于此位置的 Foo

所以,我现在想知道我应该使用什么来有效地检索 Foos 的位置。

通过高效,我的意思是每次我按位置查询 Foo 时,不要遍历整个集合。是否有合适的结构来容纳可变键?

感谢您的帮助

编辑

正如评论中正确提到的,我的问题中缺少一个部分:我无法控制 Foo Moves。该软件实际上通过COM协议连接到另一个软件(通过VSTO的Excel),该协议更改了FooPosition(Excel范围),而不通知更改。

因此,如果发生移动,我无法采取任何行动,因为我不知道确实发生了更改。

public class FooManager
{
    public void DoSomething(IList<Foo> foos) {
        Dictionary<FooPosition, Foo> fooPositionDictionary = foos.ToDictionary(x => x.Position, x => x); //I know that position is unique

        //Move Foos all around the place by changing their positions. 

        FooPosition queryPosition = new FooPosition(0, 1);

        fooPositionDictionary.TryGetValue(queryPosition, out var foo1); //DOES NOT WORK
        var foo2 = foos.FirstOrDefault(x => x.Position == queryPosition); //NOT EFFICIENT

        //Any better idea?
    }
}

public class Foo
{
    public string Name { get; set; }
    public FooPosition Position { get; set; }
}

public class FooPosition : IEquatable<FooPosition>
{
    public int X { get; set; }
    public int Y { get; set; }

    public FooPosition(int x, int y)
    {
        X = x;
        Y = y;
    }

    public void MoveBy(int i)
    {
        X = X + i;
        Y = Y + i;
    }

    public bool Equals(FooPosition other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return X == other.X && Y == other.Y;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((FooPosition) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (X * 397) ^ Y;
        }
    }

    public static bool operator ==(FooPosition left, FooPosition right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(FooPosition left, FooPosition right)
    {
        return !Equals(left, right);
    }
}
c# 胜过 字典 VSTO的 可变

评论

1赞 Fildor 6/23/2020
“(这是一个 COM 协议接口)”这是您真正应该在问题中提到的一个关键部分。
1赞 MakePeaceGreatAgain 6/23/2020
好吧,如果您无法控制位置的更新时间,那么如果数据存储甚至不知道发生了任何更改,您如何期望它更新其内容?无法使存储保持同步。因此,唯一的方法是在地图中拥有一个常量标识符并迭代该地图中的项目 - 即使这看起来不合适。
1赞 Corak 6/23/2020
我们(大致)在谈论多少个条目?在下一次仓位变动发生之前,您查询了多少个项目?你介绍过吗?您是否对“效率低下”的方式有性能问题?
1赞 XavierAM 6/23/2020
@Fildor你是对的,我更新了它。
1赞 Charles Williams 6/23/2020
我无法判断这是否适用于您的情况,但解决此类 Excel 问题的常用方法是使用命名范围并在代码中引用命名范围,而不是工作表和单元格坐标 - Excel 在移动命名范围时更新命名范围的 RefersTo 公式属性。或者,您可以使用 excel 事件在发生移动时告知代码。

答:

2赞 MakePeaceGreatAgain 6/23/2020 #1

从某种意义上说,字典 - 就像任何其他基于哈希的数据存储一样 - 使用某种缓存。在这种情况下,将缓存哈希值。但是,对于每个缓存,您需要一些在该数据存储的生命周期内不会更改的常量数据。如果没有这样的常量数据,就无法有效地缓存该数据。

因此,您最终会将所有项目存储在某个线性集合中(例如 a),并一次又一次地迭代该列表。List<T>

评论

1赞 MakePeaceGreatAgain 6/23/2020
感谢@Fildor的编辑,虽然我喜欢我的错别字;)
0赞 Fildor 6/23/2020
是的,如果我们能兑现一些数据,那就太棒了...... :D