【问题标题】:Java: simplest integer hashJava:最简单的整数哈希
【发布时间】:2012-03-08 21:07:39
【问题描述】:

我需要一个快速的整数哈希函数:

 int hash(int n) { return ...; }

Java 中是否已经存在一些东西?

我需要的最少属性是:

  • hash(n) & 1 与一堆连续的 n 值一起使用时不会出现周期性。
  • hash(n) & 1 近似等于 0 或 1。

【问题讨论】:

  • 好奇你想在哪里使用这个
  • @Miserable:我只需要一堆假值,它们是表索引的函数。 (并且非周期性很重要,原因还不能很快解释)
  • 也许是数字中为 1 的位数?
  • 使用你的整数作为 RNG 的种子,然后取第一个(或每次相同的第 N 个)随机数。
  • 为什么不直接使用 SHA?

标签: java hash


【解决方案1】:

HashMap,以及 Guava 的基于哈希的utilities,在hashCode() 结果上使用以下方法来改善位分布并防御较弱的哈希函数:

  /*
   * This method was written by Doug Lea with assistance from members of JCP
   * JSR-166 Expert Group and released to the public domain, as explained at
   * http://creativecommons.org/licenses/publicdomain
   * 
   * As of 2010/06/11, this method is identical to the (package private) hash
   * method in OpenJDK 7's java.util.HashMap class.
   */
  static int smear(int hashCode) {
    hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12);
    return hashCode ^ (hashCode >>> 7) ^ (hashCode >>> 4);
  }

【讨论】:

  • 在前 500 个号码中,smear(i) & 1 仅重复 13 次,因此对于大多数连续号码,smear(i) & 1 将出现周期性。对吗?
  • -1 不幸的是:smear & 1 在 0 和 1 之间交替。但我讨厌投票,因为这是一个很有希望的领先!!!! :-(
  • @JasonS 并非总是如此,但大多数时候。
  • 必须有某种按位 + 算术函数的简单组合才能做到。叹息。
  • 老实说,我强烈怀疑你会找到更好的东西。
【解决方案2】:

所以,我读了这个问题,认为嗯,这是一个非常数学的问题,它可能超出了我的范围。然后,我最终花了很多时间思考它,以至于我相信我已经得到了答案:没有函数可以满足 f(n) & 1 对于 n 的连续值是非周期性的标准。​​

希望有人能告诉我我的推理有多荒谬,但在那之前我相信它是正确的。

这里说:任何二进制整数 n 都可以表示为 1...0 或 1...1,并且只有该位图的最低有效位会影响 n & 1 的结果。此外,下一个连续的整数 n + 1 将始终包含相反的最低有效位。因此,很明显,任何一系列连续整数在传递给函数 n 和 1 时都会表现出 2 的周期。那么,是否有任何函数 f(n) 可以充分分布连续整数系列,从而消除周期性?

任何函数 f(n) = n + c 都会失败,因为 c 必须以 0 或 1 结尾,因此 LSB 将翻转或保持不变,具体取决于所选的常数。

上面也消除了所有琐碎情况的减法,但是我还没有花时间分析进位行为,所以这里可能存在裂缝。

任何函数 f(n) = c*n 都会失败,因为如果 c 以 0 结尾,LSB 将始终为 0,如果 c 以 1 结尾,则始终等于 n 的 LSB。

通过类似的推理,任何函数 f(n) = n^c 都会失败。幂函数总是具有与 n 相同的 LSB。

出于同样的原因,任何函数 f(n) = c^n 都会失败。

除法和模数对我来说不太直观,但基本上,任一选项的 LSB 最终都由减法确定(已经排除)。模数显然也有一个周期等于除数。

不幸的是,我没有足够的严谨性来证明这一点,但我相信上述操作的任何组合最终也会失败。这让我相信我们可以排除任何超越函数,因为这些是用多项式实现的(泰勒级数?不是术语专家)。

最后,我在回家的火车上抱了希望,数比特会起作用;然而,这实际上也是一个周期函数。我的想法是,想象一下任何十进制数的数字之和。这个总和显然会从 0 到 9,然后下降到 1,从 1 到 10,然后下降到 2……它有一个周期,范围只是不断移动,我们计数越高。我们实际上可以对二进制数字的总和做同样的事情,在这种情况下,我们得到类似:0,1,1,2,2,....5,5,6,6,7,7,8 ,8....

我遗漏了什么吗?

TL;DR 我认为您的问题没有答案。

【讨论】:

  • Galois 域运行良好(例如 LFSR),但计算起来并不简单,除了移位寄存器,您基本上从 f(n-1) 计算 f(n)。我不是在寻找 true 非周期性,只是在寻找具有相对较长周期的东西(例如 2^12 甚至 2^8 在我的情况下都可以)。
  • @JasonS:如果 2^8 可以,那么您可以创建一个 new bool[256],然后使用随机数生成随机选择其中一半的条目并将其设置为 true。 (注意:由于碰撞,这比听起来稍微复杂,但仍然非常易于管理。)然后您可以将其用作i & 255 上的查找表。
  • @JasonS 我认为可能是这样。我以前没有听说过伽罗瓦域,但它们看起来确实很有趣。对于模拟非周期性的方法,我最好的猜测是使用多个三角函数的非常大的倍数,从而使连续的结果相差很远。显然,这也会相对缓慢。祝你好运!如果您确实找到答案,请发布。
【解决方案3】:

[所以决定将我的“琐碎答案”转换为评论。试着在里面加点小文字,看看能不能上当]

除非你需要散列函数的范围更宽..

NumberOfSetBits 函数的变化似乎比 hashCode 大得多,因此似乎更适合您的需求。原来在 SO 上已经有一个相当有效的算法。

Best algorithm to count the number of set bits in a 32-bit integer

【讨论】:

  • StackExchange 将答案转换为 cmets?这很酷。 >:(
  • 我的第一个答案正是您在评论中看到的:“使用我上面的评论, 似乎是一个很好的解决方案”。我想这是为了防止业力占用。我更大的抱怨是它在转换为评论时没有正确转换链接:)
【解决方案4】:

我做了一些实验(见下面的测试程序);在伽罗瓦域中计算 2^n 和 floor(A*sin(n)) 都很好地产生了“随机”位序列。我尝试了乘法同余随机数生成器和一些代数和 CRC(类似于伽罗瓦域中的 k*n),但没有一个做得很好。

floor(A*sin(n)) 方法是最简单和最快的; GF32 中的 2^n 计算在最坏情况下大约需要 64 次乘法和 1024 次 XOR,但在线性反馈移位寄存器的上下文中,输出位的周期性是非常容易理解的。

package com.example.math;

public class QuickHash {
    interface Hasher
    {
        public int hash(int n); 
    }
    static class MultiplicativeHasher1 implements Hasher
    {
        /* multiplicative random number generator
         * from L'Ecuyer is x[n+1] = 1223106847 x[n] mod (2^32-5)
         * http://dimsboiv.uqac.ca/Cours/C2012/8INF802_Hiv12/ref/paper/RNG/TableLecuyer.pdf
         */
        final static long a = 1223106847L;
        final static long m = (1L << 32)-5;
        /*
         * iterative step towards computing mod m
         *   (j*(2^32)+k) mod (2^32-5)
         * = (j*(2^32-5)+j*5+k) mod (2^32-5)
         * = (j*5+k) mod (2^32-5)
         * repeat twice to get a number between 0 and 2^31+24 
         */
        private long quickmod(long x)
        {
            long j = x >>> 32;
            long k = x & 0xffffffffL;
            return j*5+k;
        }
        // treat n as unsigned before computation
        @Override public int hash(int n) {
            long h = a*(n&0xffffffffL);
            long h2 = quickmod(quickmod(h));            
            return (int) (h2 >= m ? (h2-m) : h2);
        }       
        @Override public String toString() { return getClass().getSimpleName(); } 
    }

    /** 
     * computes (2^n) mod P where P is the polynomial in GF2
     * with coefficients 2^(k+1) represented by the bits k=31:0 in "poly";
     * coefficient 2^0 is always 1
     */
    static class GF32Hasher implements Hasher
    {
        static final public GF32Hasher CRC32 = new GF32Hasher(0x82608EDB, 32);

        final private int poly;
        final private int ofs;
        public GF32Hasher(int poly, int ofs) {
            this.ofs = ofs;
            this.poly = poly;
        }

        static private long uint(int x) { return x&0xffffffffL; }
        // modulo GF2 via repeated subtraction
        int mod(long n) {
            long rem = n;
            long q = uint(this.poly);
            q = (q << 32) | (1L << 31);
            long bitmask = 1L << 63;
            for (int i = 0; i < 32; ++i, bitmask >>>= 1, q >>>= 1)
            {
                if ((rem & bitmask) != 0)
                    rem ^= q;
            }
            return (int) rem;
        }
        int mul(int x, int y)
        {
            return mod(uint(x)*uint(y));
        }
        int pow2(int n) {
            // compute 2^n mod P using repeated squaring
            int y = 1;
            int x = 2;
            while (n > 0)
            {
                if ((n&1) != 0)
                    y = mul(y,x);
                x = mul(x,x);
                n = n >>> 1;
            }
            return y;
        }
        @Override public int hash(int n) {
            return pow2(n+this.ofs);
        }
        @Override public String toString() { 
            return String.format("GF32[%08x, ofs=%d]", this.poly, this.ofs); 
        }
    }
    static class QuickHasher implements Hasher
    {
        @Override public int hash(int n) {
            return (int) ((131111L*n)^n^(1973*n)%7919);
        }
        @Override public String toString() { return getClass().getSimpleName(); }       
    }

    // adapted from http://www.w3.org/TR/PNG-CRCAppendix.html
    static class CRC32TableHasher implements Hasher
    {
        final private int table[];
        static final private int polyval = 0xedb88320;

        public CRC32TableHasher() 
        {           
            this.table = make_table();
        }

        /* Make the table for a fast CRC. */
        static public int[] make_table()
        {
            int[] table = new int[256]; 
            int c;
            int n, k;

            for (n = 0; n < 256; n++) {
                c = n;
                for (k = 0; k < 8; k++) {
                    if ((c & 1) != 0)
                        c = polyval ^ (c >>> 1);
                    else
                        c = c >>> 1;
                }
                table[n] = (int) c;
            }
            return table;
        }

        public int iterate(int state, int i)
        {
            return this.table[(state ^ i) & 0xff] ^ (state >>> 8);
        }
        @Override public int hash(int n) {
            int h = -1;
            h = iterate(h, n >>> 24); 
            h = iterate(h, n >>> 16); 
            h = iterate(h, n >>> 8); 
            h = iterate(h, n); 
            return h ^ -1;
        }
        @Override public String toString() { return getClass().getSimpleName(); } 
    }

    static class TrigHasher implements Hasher
    {       
        @Override public String toString() { return getClass().getSimpleName(); }
        @Override public int hash(int n) { 
            double s = Math.sin(n);
            return (int) Math.floor((1<<31)*s);
        }       
    }

    private static void test(Hasher hasher) {
        System.out.println(hasher+":");
        for (int i = 0; i < 64; ++i)
        {
            int h = hasher.hash(i);
            System.out.println(String.format("%08x -> %08x   %%2 = %d", 
                    i,h,(h&1)));
        }
        for (int i = 0; i < 256; ++i)
        {
            System.out.print(hasher.hash(i) & 1);
        }
        System.out.println();       
        analyzeBits(hasher);
    }

    private static void analyzeBits(Hasher hasher) {
        final int N = 65536;
        final int maxrunlength=32;
        int[][] runs = {new int[maxrunlength], new int[maxrunlength]};
        int[] count = new int[2];
        int prev = -1;
        System.out.println("Run length test of "+N+" bits");
        for (int i = 0; i < maxrunlength; ++i)
        {
            runs[0][i] = 0;
            runs[1][i] = 0;
        }
        int runlength_minus1 = 0;
        for (int i = 0; i < N; ++i)
        {
            int b = hasher.hash(i) & 0x1;
            count[b]++;
            if (b == prev)
                ++runlength_minus1;
            else if (i > 0)
            {
                ++runs[prev][runlength_minus1];
                runlength_minus1 = 0;
            }
            prev = b;
        }
        ++runs[prev][runlength_minus1];

        System.out.println(String.format("%d zeros, %d ones", count[0], count[1]));
        for (int i = 0; i < maxrunlength; ++i)
        {
            System.out.println(String.format("%d runs of %d zeros, %d runs of %d ones", runs[0][i], i+1, runs[1][i], i+1));         
        }
    }

    public static void main(String[] args) {
        Hasher[] hashers = {
            new MultiplicativeHasher1(), 
            GF32Hasher.CRC32, 
            new QuickHasher(),
            new CRC32TableHasher(),
            new TrigHasher()
        };
        for (Hasher hasher : hashers)
        {
            test(hasher);
        }
    }
}

【讨论】:

    【解决方案5】:

    int 值的最简单哈希是 int 值。

    Java Integer class

    public int hashCode()  
    public static int hashCode(int value) 
    

    返回:

    此对象的哈希码值,等于此 Integer 对象表示的原始 int 值。

    【讨论】:

    • 谢谢,但请参阅我的描述中的两个条件。
    猜你喜欢
    • 2013-01-02
    • 2017-12-01
    • 2011-11-17
    • 2012-03-09
    • 2020-12-08
    • 2013-06-12
    • 2021-09-21
    • 1970-01-01
    • 2011-04-10
    相关资源
    最近更新 更多