【问题标题】:Good hash for a set of numbers and a special number?一组数字和一个特殊数字的好散列?
【发布时间】:2017-03-17 13:16:41
【问题描述】:

我有整数数组,例如

[1, 3, 5], 
[7, 2, 10],
[50, 12, 10],
[20, 1, 34],

我正在尝试创建一个散列算法,给定其中一个数组将为每个数组返回一个唯一的散列,以便我可以快速查看它们是否存在于 HashMap 中。

如果两个数组包含相同的一组数字并且数组中的最后一个数字相同,则哈希应该相同。

例如

// these are the same because they contain the same numbers and have same last number (5)
Hash([3, 1, 5]) -> 5678326 
Hash([1, 3, 5]) -> 5678326

// different hash because the last number in the array is different
Hash([5, 1, 3]) -> 9877124

// different hash because different set of values
Hash([7, 1, 5]) -> 2123466

数组中的值在 0 - 100 范围内,它们都是唯一的(因此数组中不能有重复项),数组的最大大小为 100。

什么是真正好的散列算法?

【问题讨论】:

  • 这似乎并不特定于 Java 语言。另外,到目前为止,您尝试过做什么?
  • 好的,所以将前两个与一个交换函数结合起来(所以它们的顺序无关紧要),然后将它与最后一个结合起来。
  • 您所描述的需要是哈希码。哈希码不必保证唯一——只是不太可能产生冲突。
  • 如果您这样做是为了将您的值用作 JDK 的 HashMap 中的键,那么哈希码将毫无用处,除非您也按照相同的方式实现 equals()
  • 从问题看来,您使用错误的数据结构来存储您的值。为什么不创建一个包含Set<Integer>(或Multiset<Integer>)和单独的int 的类并正常实现equals()hashCode()

标签: java algorithm math hash


【解决方案1】:

计算输入的哈希码,就像它是一个集合一样,乘以一个素数,然后添加最后一个元素的哈希码。

沿线

new HashSet<Integer>(Arrays.asList(input)).hashCode() * 31 + input[input.length - 1]

但为了提高性能,您可能希望通过在循环中添加输入项而不是创建 HashSet 来手动执行此操作。

请注意,这不会按照您的要求“为每个 [输入] 返回一个唯一的哈希值” - 您需要 perfect hash function 来完成此操作,这可能会有点矫枉过正。

【讨论】:

  • 如果inputint[],则不能使用Arrays.asList()
【解决方案2】:

您描述的是一个奇怪的设置,但实现它的一种方法是使用自定义对象:

public class YourCustomObject {
    private final int[] allButLast;
    private final int last;

    public YourCustomObject(int[] value){
        this.value = value;
        this.allButLast = Arrays.copyOfRange(value, 0, value.length-1);
        Arrays.sort(allButLast);
        this.last = value[value.length-1];
    }
    private final int[] value;

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }else if (( o instanceof YourCustomObject)) {
            YourCustomObject that = (YourCustomObject) o;
            return last == that.last && Arrays.equals(allButLast, that.allButLast);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return Objects.hash(allButLast, last);
    }

    public int[] getValue() {
        return value;
    }
}

该对象的 equals/hashCode 属性依赖于任何顺序的相同数组元素,不包括最后一个元素,它必须相同。您可以将此对象用作 HashMap 中的键,它将按指定的方式工作。

另外,由于数组是可变的,我可能会在构造函数和 getter 中创建一个防御性副本。

【讨论】:

    【解决方案3】:

    不是最优化的解决方案,但它应该做你想做的:

    int hash(int[] array) {
        array = array.clone();
        Arrays.sort(array, 0, array.length - 1);
        return Arrays.hashCode(array);
    }
    

    另一种选择是将元素添加到集合并调用Objects.hash(set, array[array.length - 1])

    【讨论】:

      【解决方案4】:

      如果您只需要生成一个散列来将数组存储在 Map 或 Set 的存储桶中,则无需创建自己的散列函数。 java.util.Arrays 中的那个就可以了。它们是专门为此目的而设计的。

      此外,哈希码不必保证唯一——只是不太可能产生冲突。事实上,保证它们是唯一的会比偶尔的碰撞更慢地降低地图的速度。

      无需重新发明轮子——只需使用java,util.Arrays.hashCode 计算数组的哈希码。

      【讨论】:

      • 没有。这与 equals 方法不一致。
      • @Henry:相等的对象必须产生相同的哈希码。不能保证反过来。
      • 没错,据我所知,现在 [3, 1, 5] 和 [1, 3, 5] 是相等的,但它们可能具有不同的哈希码。
      • @Henry:大概吧?为什么不试试Arrays.equalsArrays.hashCode 看看它们是否会产生不一致的结果?由于这将是 java 核心中的一个相当大的错误,我认为这不太可能。这种用法正是该实用函数的用途。
      • 我不是在谈论 Arrays.equals,而是在谈论 OP 想要拥有的平等语义。
      猜你喜欢
      • 2021-12-30
      • 1970-01-01
      • 1970-01-01
      • 2018-07-22
      • 1970-01-01
      • 2019-03-02
      • 2013-12-28
      • 2012-06-01
      相关资源
      最近更新 更多