C# Лучший способ создания уникального идентификатора int32 для сложного объекта - PullRequest
0 голосов
/ 19 июня 2020

У меня проблема с получением уникального идентификатора int32 со следующими свойствами:

  • Он всегда должен быть одинаковым для одних и тех же объектов в текущем экземпляре программы
  • Он имеет всегда отличаться в текущем экземпляре программы для разных объектов, поэтому никаких коллизий.

Мне нужен этот уникальный идентификатор для сравнения сложных объектов и работы с такими классами, как Dictionary <> или HashSet <> et c.

Я бы очень хотел избежать использования каких-либо таблиц ha sh или любых предварительных вычислений, а вместо этого иметь алгоритм, который будет делать это на лету, чтобы исключить внешние зависимости и упростить модульное тестирование

Псевдокод объекта:

    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 и получение ha sh этой строки (с помощью метода 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 влияют \ применяются (не уверен, что мой английский sh ясен) только для обработки данных.

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

Внешний интерфейс и данные, необходимые для создания экземпляра класса, аналогичны реализации IFirstModificator, но на самом деле это разные классы.

1 Ответ

0 голосов
/ 30 июня 2020

Итак, это пример реализации @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, поэтому вместо этого я использую Reflection для сопоставления фактического типа реализации с int от 1 до 4. Если какое-либо из свойств 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;
    }
}
...