【问题标题】:C# Best method to create unique int32 identifier for complex objectC# 为复杂对象创建唯一 int32 标识符的最佳方法
【发布时间】:2020-10-10 00:19:42
【问题描述】:

我在获取具有此属性的唯一 int32 标识符时遇到问题:

  • 对于当前程序实例中的相同对象,它必须始终相同
  • 对于不同的对象,程序的当前实例必须始终不同,因此根本不会发生冲突。

我需要该唯一标识符来比较复杂对象并使用 Dictionary 或 HashSet 等类。

我非常希望避免使用任何类型的哈希表或任何类型的预计算,而是使用一种可以即时执行此操作的算法,以便排除外部依赖并使单元测试更容易

对象的伪代码:

    class ComplexObject
    {
        public readonly FirstEnum First; // ~50 different values
        public readonly IFirstModificator FirstModificator; // 4 implementations x 15 values (~60 values total)
        public readonly InternalObject[] Internal; //1-10 values in array 
    }

    class InternalObject
    {
        public readonly SecondEnum Second; // ~30 different values
        public readonly SecondModificator SecondModificator; //  ~15 different values
    }

如果它很重要,我的域模型包含大约 100 000 个 ComplexObject 类型的唯一对象

我已经试过了:

  • 将对象序列化为 json 并获取该字符串的哈希(通过使用 string.GetHashCode() 方法)。即使在程序的当前实例中,它也会产生冲突。
  • 这样的代码也会产生很多冲突:
    unchecked
    {
        int hash = 17;
        hash = hash * 31 + firstField.GetHashCode();
        hash = hash * 31 + secondField.GetHashCode();
        return hash;
    }

    unchecked 
    {
        int hash = (int) 17;
        hash = (hash * 31) ^ field1.GetHashCode();
        hash = (hash * 31) ^ field2.GetHashCode();
        return hash;
    }

更新:

IFirstModificator 有不同的实现,但总的来说它看起来像这样:

    class FirstModificator : IFirstModificator
    {
        public int Value {get;set;} //~15 values
    }

实现IFirstModificator的其他参数影响\仅适用于(不确定我的英语是否清楚)数据处理。

    class SecondModificator 
    {
        public int Value {get;set;} //~15 values
    }

创建类实例所需的外部接口和数据与实现 IFirstModificator 类似,但它们实际上是不同的类。

【问题讨论】:

  • Int32 只有 32 位可供使用,而 simply not enough 可以保证这么多对象的无冲突散列(假设您不能使用值分布的任何特殊属性)。但是请注意,为了正确使用 Dictionary 等类,不需要无冲突散列;碰撞只是意味着性能会有所降低,因为多个对象将占用同一个桶。在列表中搜索 2 或 3 个碰撞的对象并不比仅获取一个慢多少
  • 如果您对不同值的数量的评论是准确的,那么您实际上似乎没有超过 2^31 个唯一的 possible 对象,这意味着您绝对可以产生唯一散列唯一对象的散列。这实际上涉及为每个组合分配一个唯一编号,因此请根据您的分布对其进行调整(即通过将所有 SecondModificator 值映射到 0-15 和 Second 到 0-30 来散列 InternalObject,然后执行 SecondModificator * 16 + Second)。当然,这可能比普通哈希要困难得多。
  • 请注意,如果您的相等比较非常慢并且是碰撞的瓶颈(可能是非常大的对象的情况),您可以通过计算第二个更大的哈希来加快速度(即使是Int64 就足够了),将其与您的对象(或单独的ConditionalWeakTable)一起存储,并在进行完全相等比较之前检查它是否匹配。但是,您的对象似乎不足以保证这一点,最多占用几个字节。在这成为真正的问题之前,碰撞必须变得非常糟糕。
  • 您能否提供一些有关IFirstModificatorSecondModificator 及其实现的详细信息?还有什么构成对象的身份?是否可以有重复的InternalObject 对象或重复的ComplexObject 对象,必须被视为不同的对象?
  • @jeroen-mostert 感谢您的建议。在这种情况下,由于多种原因,不同的(对于域模型参数的重要性不相等)对象不会被放入同一个桶中,但有时会发生冲突,这一点至关重要。

标签: c# hash uid


【解决方案1】:

所以这是@JeroenMostert 建议的示例实现。

首先,您可以根据不同字段的可能值创建InternalObject 哈希码:

class InternalObject { // ~450 different values
    public readonly SecondEnum Second; // ~30 different values
    public readonly SecondModificator SecondModificator; //  ~15 different values

    public override int GetHashCode() {
        var hc = (int)Second; // use 5 bits
        // assume SecondModificator.Value values range from 0 - 15 or can be normalized
        hc = hc << 5 + SecondModificator.Value;
        return hc;
    }
}

然后您可以根据那里每个字段的可能值为ComplexObject 创建一个哈希码。此哈希码实现假定所有 IFirstModificator.Value 字段的范围为 0 到 15,并且您不想将新的 int 字段添加到 IFirstModificator 以表示哪个实现存储在 ComplexObject 中,因此我使用反射将实际实现类型映射到从 1 到 4 的 int。 如果任何 Value 属性不是 0 到 15 的简单范围,则必须使用它们已知的可能值将它们标准化到该范围。

class ComplexObject {
    public readonly FirstEnum First; // ~50 different values
    public readonly IFirstModificator FirstModificator; // 4 implementations x 15 values (~60 values total)
    public readonly InternalObject[] Internal; //1-10 values in array => ~4500 different values

    static Dictionary<Type, int> FirstModMap = new[] { typeof(FirstModificator1), typeof(FirstModificator2), typeof(FirstModificator3), typeof(FirstModificator4) }
                                                .Select((t, n) => new { t, n })
                                                .ToDictionary(tn => tn.t, tn => tn.n + 1);
    public override int GetHashCode() {
        var hc = (int)First; // use 6 bits
        // assume IFirstModificator.Value values are 0 - 14 or normalize to be so
        hc = hc << 6 + (FirstModificator.Value * FirstModMap[FirstModificator.GetType()]); // uses 6 bits
        // assume InternalObject[] order matters
        hc = hc << 12 + Internal.Select((io, n) => io.GetHashCode() * (n + 1)).Sum(); // uses 13 bits

        return hc;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多