Create a uniform random number based on a hash

fodon picture fodon · Jun 10, 2012 · Viewed 7.3k times · Source

I need a good pseudo random number based on a key consisting of a string and a long. I should get the same random number when I query using the same key and also, I should get a very different number if I query using a slightly different key, even when say the long in the key is off by 1. I tried this code and the random numbers are unique but for similar numbers they seem correlated.

import java.util.Date;
import java.util.Random;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class HashKeyTest {
    long time;
    String str;
    public HashKeyTest(String str, long time) {
        this.time = time;
        this.str = str;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(time).append(str).toHashCode();
    }

    public static void main(String[] args) throws Exception {
        for(int i=0; i<10; i++){
            long time = new Date().getTime();
            HashKeyTest hk = new HashKeyTest("SPY", time);
            long hashCode = (long)hk.hashCode();
            Random rGen = new Random(hashCode);
            System.out.format("%d:%d:%10.12f\n", time, hashCode, rGen.nextDouble());
            Thread.sleep(1);
        }
    }
}

Solution I pieced together. This works pretty well, but I wonder if it needs to be this verbose.

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

public class HashKeyTest implements Serializable{

    long time;
    String str;

    public HashKeyTest(String str, long time) {
        this.time = time;
        this.str = str;
    }

    public double random() throws IOException, NoSuchAlgorithmException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(bos);
        out.writeObject(this);
        byte[] bytes = bos.toByteArray();
        MessageDigest md5Digest = MessageDigest.getInstance("MD5");
        byte[] hash = md5Digest.digest(bytes);
        ByteBuffer bb = ByteBuffer.wrap(hash);
        long seed = bb.getLong();

        return new Random(seed).nextDouble();
    }

    public static void main(String[] args) throws Exception {
        long time = 0;
        for (int i = 0; i < 10; i++) {
            time += 250L;
            HashKeyTest hk = new HashKeyTest("SPY", time);
            System.out.format("%d:%10.12f\n", time, hk.random());
            Thread.sleep(1);
        }
    }
}

Answer

Oliver Charlesworth picture Oliver Charlesworth · Jun 10, 2012

I would just use the key's hash itself as the "random" number. Assuming a sensible hash implementation, it will have all the properties you've mentioned.