4

文字列と long で構成されるキーに基づく適切な疑似乱数が必要です。同じキーを使用してクエリを実行すると、同じ乱数を取得する必要があります。また、キーの long が 1 ずれている場合でも、わずかに異なるキーを使用してクエリを実行すると、非常に異なる番号を取得する必要があります。このコードを試しました乱数は一意ですが、同様の数の場合、それらは相関しているように見えます。

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);
        }
    }
}

私がつなぎ合わせた解決策。これはかなりうまく機能しますが、これほど冗長にする必要があるのだろうかと思います。

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);
        }
    }
}
4

4 に答える 4

2

「同じキーを使用してクエリを実行すると、同じ乱数を取得する必要があります。また、わずかに異なるキーを使用してクエリを実行すると、非常に異なる番号を取得する必要があります」とおっしゃいました。私があなたの質問を正しく理解しているなら、あなたは乱数ではなく、暗号化ハッシュコードのようなものが必要です。

SHAやMD5などのハッシュ関数を介してデータを渡すことを検討する必要があります。これにより、入力に関して一見ランダムに見えるものが得られますが、同じ入力が与えられた場合は常に同じであり、入力の変化がごくわずかであっても大きく変化します。

編集:一貫してdouble値を取得するには、次のようなものを試してください(擬似コード):

SHAHashValue v = ComputeSHA( yourObject);
Random r = new Random(v);
the_random_value = r.getNext();

ここでの考え方は、SHAハッシュ値をシードとして使用してランダムジェネレーターを初期化することです。これはほとんどあなたが持っているものですが、HashBuilderがさまざまな値で何を生成するのかわかりません。したがって、代わりにSHAハッシュを使用すると、状況が改善される可能性があります。

また、0と1の間のdoubleの「非常に異なる」値は、すぐには明らかにならない可能性があることも考慮する必要があります。

于 2012-06-10T15:58:19.767 に答える
2

キーのハッシュ自体を「乱数」として使用するだけです。賢明なハッシュ実装を想定すると、言及したすべてのプロパティが含まれます。

于 2012-06-10T15:44:50.187 に答える
2

ちょっと意外な結果です。シードの小さな違いが、乱数の流れに大きな違いをもたらすはずだと思っていたでしょう。よくよく考えると、なぜそう思ったのかわからない。

それでも、簡単に修正できます!

おそらく最も簡単な方法は、使用する前に乱数発生器を少しウォームアップさせることです。さまざまなシードによって生成されたビットストリームは最初は似ていますが、すぐに分岐するため、ビットストリームの初期部分を単に破棄するだけでうまくいくはずです。を作成した行の直後に、次をRandom追加します。

rGen.nextLong();

または、さらに分岐するには:

for (int j = 0; j < 10; ++j) rGen.nextLong();

簡単なテストでは、これがはるかに幅広い種類の数値を取得することが示されています。

別のオプションはjava.security.SecureRandom、乱数ジェネレーターとして a を使用することです。これにより、同様の入力から異なる出力を生成するというより良い仕事ができます。バイト配列でシードします。のようなことを言って作成できます(str + time).getBytes()

さらなるオプションは、シードを取得し、SHA-256 などの暗号化ハッシュを使用してハッシュし、その一部をシードとして使用することです。ハッシュは非常によく似た入力を取り、非常に異なる出力を生成します。これにより、適切に異なるランダム ビットストリームが得られます。

于 2012-06-10T15:46:11.707 に答える
0

私が理解していることは次のとおりです。

  • オブジェクトには 2 つのインスタンス変数があります -乱数を計算するために考慮する必要があるlongtimeと stringstr
  • 乱数がtimeパーツに非常に敏感になるようにします。
  • 同じtime+のstr組み合わせは、同じ乱数を生成する必要があります。
  • 2 つの異なるtime+strの組み合わせが同じ乱数を生成しても問題ありません。

あなたが投稿したコードから、HashCodeBuilder()はあなたが望むほど敏感ではないようですtime

他の人が提案したこととは別にtime、一貫した方法でそれ自体を変更することが 1 つのアイデアかもしれません。

time(キーの一部)の最後の桁を取得してlong、番号の途中に移動することができます。たとえば、次のhashCode()ことができます。

@Override
public int hashCode() {
    return (new org.apache.commons.lang.builder.HashCodeBuilder()
            .append(time+((time%10)*100000000)).append(str).toHashCode());
}

(コードは最後の桁を真ん中に正確に移動しているわけではありませんが、質問のコンテキストで同様のことをしています)

しかし、これはちょっと遅いでしょう。したがって、ビット演算子に変換できます。

@Override
public int hashCode() {
    return (new org.apache.commons.lang.builder.HashCodeBuilder()
            .append(time+((time & 63l) << 57)).append(str).toHashCode());
}

時間の最後の 6 ビット ( time & 63l) を抽出し、それらのビットを先頭に配置するようなものです (これ57は一種のランダムです。これらのビットをより重要な位置に移動したいだけです)。これは、「数字を真ん中のどこかに移動する」アナロジーと正確には一致しませんが、概念的には似ています。

最後の 5 ビットのみを抽出すると、分散が大きくなります ( time & 31l)。さまざまな値を試すことができます。質問に投稿されたコードの場合、time & 63lバージョンは次の出力を返します。

1339343005559:-1084202043:0.339762681480
1339343005585:1801482883:0.323979029483
1339343005586:559968862:0.786162684846
1339343005587:-681545159:0.241820545267
1339343005588:-580881900:0.692788956755
1339343005590:1231057354:0.624686671170
1339343005591:-10456667:0.530394885899
1339343005592:1700819920:0.894868466104
1339343005593:459305899:0.149584882259
1339343005595:-2023722143:0.289584988289

longこれは、予想どおり、キーの一部の小さな変更に対してより多くの分散を示しています。

于 2012-06-10T15:32:41.603 に答える