Подтвердить что ты не робот

Использовать байт [] в качестве ключа в словаре

Мне нужно использовать byte[] как ключ в Dictionary. Поскольку byte[] не переопределяет метод по умолчанию GetHashCode, два отдельных объекта byte[], которые содержат одни и те же данные, будут использовать два отдельных слота в словаре. В основном я хочу это:

Dictionary<byte[], string> dict = new Dictionary<byte[], string>();
dict[new byte[] {1,2,3}] = "my string";
string str = dict[new byte[] {1,2,3}];
// I'd like str to be set to "my string" at this point

Есть ли простой способ сделать это? Единственное, о чем я могу подумать, - создать класс-оболочку, содержащий только byte[] и переопределить GetHashCode на основе содержимого byte[], но это похоже на склонность к ошибкам.

4b9b3361

Ответ 1

По умолчанию byte[] будет сравниваться по ссылке, которая не является тем, что вы хотите в этом случае. Что вам нужно сделать, это указать пользовательский IEqualityComparer<byte[]> и выполнить требуемое сравнение.

Например

public class ByteArrayComparer : IEqualityComparer<byte[]> {
  public bool Equals(byte[] left, byte[] right) {
    if ( left == null || right == null ) {
      return left == right;
    }
    return left.SequenceEqual(right);
  }
  public int GetHashCode(byte[] key) {
    if (key == null)
      throw new ArgumentNullException("key");
    return key.Sum(b => b);
  }
}

Затем вы можете сделать

var dict = new Dictionary<byte[], string>(new ByteArrayComparer());

Решение для 2.0

public class ByteArrayComparer : IEqualityComparer<byte[]> {
  public bool Equals(byte[] left, byte[] right) {
    if ( left == null || right == null ) {
      return left == right;
    }
    if ( left.Length != right.Length ) {
      return false;
    }
    for ( int i= 0; i < left.Length; i++) {
      if ( left[i] != right[i] ) {
        return false;
      }
    }
    return true;
  }
  public int GetHashCode(byte[] key) {
    if (key == null)
      throw new ArgumentNullException("key");
    int sum = 0;
    foreach ( byte cur in key ) {
      sum += cur;
    }
    return sum;
  }
}

Ответ 2

Не могли бы вы преобразовать байт [] в строку и использовать это как ключ?

Что-то вроде:

        ASCIIEncoding enc = new ASCIIEncoding();
        byte[] input;
        string demo = new string(enc.GetChars(input));
        byte[] decode = enc.GetBytes(demo.ToCharArray());

Ответ 3

Итак, ответ JaredPar неплох, но может быть лучше несколькими способами. Прежде всего, страница IEqualityComparer говорит: "Мы рекомендуем вам выходить из класса EqualityComparer вместо реализации интерфейса IEqualityComparer".

Во-вторых, реализация GetHashCode должна быть быстрой. Он использовался для быстрого устранения явно разных объектов, что, очевидно, было бы пустой тратой времени для запуска Equals on. Таким образом, GetHashCode должен быть намного быстрее, чем фактически использовать Equals.

В-третьих, возвращая сумму байтового массива, как это делал JaredPar, скорее всего, вызовет столкновения - если байты находятся в другом порядке или относительные различия отменяют друг друга и т.д.

Поэтому я рекомендовал бы такое решение вместо этого:

public class ByteArrayComparer : EqualityComparer<byte[]>
{
    public override bool Equals(byte[] first, byte[] second)
    {
        if (first == null || second == null) {
            // null == null returns true.
            // non-null == null returns false.
            return first == second;
        }
        if (ReferenceEquals(first, second)) {
            return true;
        }
        if (first.Length != second.Length) {
            return false;
        }
        // Linq extension method is based on IEnumerable, must evaluate every item.
        return first.SequenceEqual(second);
    }
    public override int GetHashCode(byte[] obj)
    {
        if (obj == null) {
            throw new ArgumentNullException("obj");
        }
        // quick and dirty, instantly identifies obviously different
        // arrays as being different
        return obj.Length;
    }
}

Выше, возвращая obj.Length, действительно быстро и грязно, но также склонно возвращать много столкновений. Я думаю, что мы можем сделать лучше.

Если вы собираетесь исследовать все байты, что-то вроде этого меньше, чем простая сумма байтов, как в ответе JaredPar. Но опять же, это рассматривает все элементы, поэтому он не будет работать лучше, чем на самом деле работает Equals. Вы могли бы просто вернуть 0 безоговорочно и всегда принудительно использовать Equals.

Я подчеркиваю: это лучше, чем возврат суммы, как в ответе JaredPar. И всегда возвращение 0 лучше, чем это. И возврат obj.Length лучше, чем возврат 0.

// This is not recommended. Performance is too horrible.
public override int GetHashCode(byte[] obj)
{
    // Inspired by fletcher checksum. Not fletcher.
    if (obj == null) {
        throw new ArgumentNullException("obj");
    }
    int sum = 0;
    int sumOfSum = 0;
    foreach (var val in obj) {
        sum += val; // by default, addition is unchecked. does not throw OverflowException.
        sumOfSum += sum;
    }
    return sum ^ sumOfSum;
}

Если вам известно, что массивы byte [], которые вы используете, поскольку ключ были самими криптографическими хэшами, вы можете использовать это предположение в свою пользу и просто вернуть первые 4 байта, преобразованные в int. Вероятно, это тоже хорошо работает для массивов байтов общего назначения:

// This implementation works great if you assume the byte[] arrays
// are themselves cryptographic hashes. It probably works alright too,
// for general-purpose byte arrays.
public override int GetHashCode(byte[] obj)
{
    if (obj == null) {
        throw new ArgumentNullException("obj");
    }
    if (obj.Length >= 4) {
        return BitConverter.ToInt32(obj, 0);
    }
    // Length occupies at most 2 bits. Might as well store them in the high order byte
    int value = obj.Length;
    foreach (var b in obj) {
        value <<= 8;
        value += b;
    }
    return value;
}

Ответ 4

Твоя мысль тоже была моей первой мыслью. Я не думаю, что это было бы ошибкой. Но если вам не нравится этот параметр, вы можете создать класс, который реализует IEqualityComparer и передать экземпляр его конструктору Dictionary.

Ответ 5

using System;
using System.Collections;
using System.Collections.Generic;

[Serializable]
class StructuralEqualityComparer : IEqualityComparer, IEqualityComparer<object>
{
    public new bool Equals(object x, object y)
    {
        var s = x as IStructuralEquatable;
        return s == null ? object.Equals(x, y) : s.Equals(y, this);
    }

    public int GetHashCode(object obj)
    {
        var s = obj as IStructuralEquatable;
        return s == null ? EqualityComparer<object>.Default.GetHashCode(obj) : s.GetHashCode(this);
    }
}

Ответ 6

Просто сделал EqualityComparer немного более общим, не работая над массивами, а на IEnumerable<T>.

В связи с тем, что теперь мы имеем T, нам нужно указать опциональный сопоставитель равенства для элементов.

И последнее, но не менее важное: GetHashCode() никогда не должен бросать, а иногда вам нужно быстро, а иногда вам нужно его более точно в первом прогоне. Таким образом, вы можете опционально определить точность от того, сколько элементов (максимум) хэш-код нужно учитывать для нашего собственного хэша.

public class EnumerableEqualityComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    private static readonly Lazy<IEqualityComparer<IEnumerable<T>>> Lazy = new Lazy<IEqualityComparer<IEnumerable<T>>>(() => new EnumerableEqualityComparer<T>());
    private int accuracy;
    private IEqualityComparer<T> comparer;

    public EnumerableEqualityComparer()
        : this(-1)
    {
    }

    public EnumerableEqualityComparer(int accuracy)
        : this(accuracy, null)
    {
    }

    public EnumerableEqualityComparer(IEqualityComparer<T> elementEqualityComparer)
        : this(-1, elementEqualityComparer)
    {
    }

    public EnumerableEqualityComparer(int accuracy, IEqualityComparer<T> elementEqualityComparer)
    {
        if (accuracy < 0)
        {
            accuracy = 4;
        }

        this.accuracy = accuracy;
        comparer = elementEqualityComparer ?? EqualityComparer<T>.Default;
    }

    public static IEqualityComparer<IEnumerable<T>> Default { get; private set; } = Lazy.Value;

    public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }

        if (ReferenceEquals(x, null)
            || ReferenceEquals(y, null))
        {
            return false;
        }

        return x.SequenceEqual(y, comparer);
    }

    public int GetHashCode(IEnumerable<T> obj)
    {
        if (ReferenceEquals(obj, null))
        {
            return -1;
        }

        var count = (obj as ICollection<T>)?.Count ?? 1;
        var hashCode = count * 49297;

        foreach (var item in obj.Take(accuracy))
        {
            hashCode += comparer.GetHashCode(item) * 17123;
        }

        return hashCode;
    }
}

Ответ 7

Когда вы извлекаете элементы из Словаря, вы используете новый оператор для байта []. Это будет искать другой (новый) байт [] экземпляр в словаре, которого нет.

Вот решение, которое будет работать:

 var dict = new Dictionary<byte[], string>();

            var b = new byte[] { 1,2,3};

            dict[b] = "my string";

            var value = dict[b]; 

            Console.WriteLine(value);