ランダムな文字列を使用するこのコードが「hello world」を出力するのはなぜですか?


1769

次のprintステートメントは「hello world」を出力します。誰かこれを説明できますか?

System.out.println(randomString(-229985452) + " " + randomString(-147909649));

そしてrandomString()、このようになります:

public static String randomString(int i)
{
    Random ran = new Random(i);
    StringBuilder sb = new StringBuilder();
    while (true)
    {
        int k = ran.nextInt(27);
        if (k == 0)
            break;

        sb.append((char)('`' + k));
    }

    return sb.toString();
}

158
まあ、それらの特定の種はたまたま完璧に機能します。ランダムは真にランダムではなく、疑似ランダムです。
tckmn 2013

341
ランダムはそうではないので、他の人が言ったように、それは機能します。私にとって、より興味深い質問は、それを書いた人、それを総当たりにするか、または与えられたシードの次のN個の値に対してランダムが生成するものを予測する簡単な方法があります。ブルートフォーシングは簡単で、最新のハードウェアではそれほど長くはかかりません。そのため、これは実行可能な方法でした。静的であるため、検索をネットワーク全体に簡単に分散することもできます。
jmoreno 2013

78
私はの目的不思議nではfor (int n = 0; ; n++)for(;;)またはwhile(true)代わりに使用できます!
Eng.Fouad 2013

13
真にランダムなシーケンスでは、すべての可能な文字列が最終的に表示されます。高品質の擬似ランダムシーケンスでは、長さ(log_s(N)-n)ビットのすべての可能な文字列を合理的に期待できます(NはPRNGの内部状態のビット数、nは小さい数です。便宜上8を選択します)サイクルに表示されます。このコードは、自由に選択されたハードコードされた開始点(文字のバックティックの値)を使用することである程度の助けを得ます。
dmckee ---元モデレーターの子猫2013

13
これは私が数年前に書いた投稿からです。vanillajava.blogspot.co.uk/2011/10/randomly-no-so-random.html
Peter Lawrey

回答:


917

のインスタンスがjava.util.Random特定のシードパラメータ(この場合は-229985452または-147909649)で構築されると、そのシード値から始まる乱数生成アルゴリズムに従います。

Random同じシードで構築されたものはすべて、毎回同じパターンの数字を生成します。


8
@Vulcan-javadocによると、シードは48ビットです。 docs.oracle.com/javase/7/docs/api/java/util/Random.html。また、実際のシードは32ビット値です。
スティーブンC

80
乱数列の各要素は27を法として取られ、およびのそれぞれに6つの要素が"hello\0"あり"world\0"ます。真にランダムなジェネレータを想定した場合、オッズはあなたが探していたシーケンスを取得する27 ^ 6の1(387,420,489)になるでしょう。そのため、それはかなり印象的ですが、それほど驚くべきものではありません。
Russell Borogove 2013

17
@RussellBorogove:しかし、これらのオッズと2 ^ 64の可能なシードがある場合、そのシーケンスを与えると予想される476億のシード値があります。それを見つけるだけの問題です。
dan04 2013

8
@ dan04-私はその見積もりをすることを全く望んでいませんでした。PRNGの実装によっては、シードワードのサイズが状態のサイズと等しくなく、シーケンスパスが均等に分散されない場合があります。しかし、それでもまだ、オッズは確かに優れている、とあなたは別のケーシング(と再び試みることができるのペアを見つけることができなかった場合"Hello" "World")、または使用する122-k代わりに96+k...、あるいは
ラッセルBorogove

7
ThorbjørnRavnAndersen@ のJavadoc「とは、特定のアルゴリズムはランダム。Java実装は、Javaコードの絶対的な移植性のためにRandomクラスのためにここに示されているすべてのアルゴリズムを使用する必要がありますクラスに指定されている。」ことを指定します
FThompson 2013

1137

他の回答はその理由を説明していますが、ここではその方法を示します。

のインスタンスが与えられたRandom

Random r = new Random(-229985452)

r.nextInt(27)生成される最初の6つの数値は次のとおりです。

8
5
12
12
15
0

そして、r.nextInt(27)与えられた最初の6つの数値Random r = new Random(-147909649)は次のとおりです。

23
15
18
12
4
0

次に、それらの数値を文字の整数表現`(96)に追加します。

8  + 96 = 104 --> h
5  + 96 = 101 --> e
12 + 96 = 108 --> l
12 + 96 = 108 --> l
15 + 96 = 111 --> o

23 + 96 = 119 --> w
15 + 96 = 111 --> o
18 + 96 = 114 --> r
12 + 96 = 108 --> l
4  + 96 = 100 --> d

48
new Random(-229985452).nextInt(27)
経験上

1
@immibisなんで?私はRandom()が毎回乱数を返す必要があることを意味します。
roottraveller 2017年

5
@rootTravellerはじめにnew Random()、数値をまったく返しません。
user253751 2017年

2
これらの種子を計算する方法はありますか?なんらかの論理があるに違いありません...あるいは、それはただの力です。
Sohit Gore

2
@SohitGore JavaのデフォルトRandomは暗号的に安全ではないことを考えると(これはMersenne Twisterだと確信していますが、私には引用しないでください)、「これらの数値が欲しい」から「これは私が使う種」。標準のC線形合同ジェネレーターで同様のことを行いました。
モニカの訴訟に資金を

280

ここでそのままにしておきます。(CPU)時間の余裕がある人は、自由に試してみてください:)また、fork-join-fuを習得してすべてのCPUコアを燃焼させた場合(スレッドだけが退屈だと思いますか?)、共有してください。あなたのコード。よろしくお願いします。

public static void main(String[] args) {
    long time = System.currentTimeMillis();
    generate("stack");
    generate("over");
    generate("flow");
    generate("rulez");

    System.out.println("Took " + (System.currentTimeMillis() - time) + " ms");
}

private static void generate(String goal) {
    long[] seed = generateSeed(goal, Long.MIN_VALUE, Long.MAX_VALUE);
    System.out.println(seed[0]);
    System.out.println(randomString(seed[0], (char) seed[1]));
}

public static long[] generateSeed(String goal, long start, long finish) {
    char[] input = goal.toCharArray();
    char[] pool = new char[input.length];
    label:
    for (long seed = start; seed < finish; seed++) {
        Random random = new Random(seed);

        for (int i = 0; i < input.length; i++)
            pool[i] = (char) random.nextInt(27);

        if (random.nextInt(27) == 0) {
            int base = input[0] - pool[0];
            for (int i = 1; i < input.length; i++) {
                if (input[i] - pool[i] != base)
                    continue label;
            }
            return new long[]{seed, base};
        }

    }

    throw new NoSuchElementException("Sorry :/");
}

public static String randomString(long i, char base) {
    System.out.println("Using base: '" + base + "'");
    Random ran = new Random(i);
    StringBuilder sb = new StringBuilder();
    for (int n = 0; ; n++) {
        int k = ran.nextInt(27);
        if (k == 0)
            break;

        sb.append((char) (base + k));
    }

    return sb.toString();
}

出力:

-9223372036808280701
Using base: 'Z'
stack
-9223372036853943469
Using base: 'b'
over
-9223372036852834412
Using base: 'e'
flow
-9223372036838149518
Using base: 'd'
rulez
Took 7087 ms

24
@OneTwoThree nextInt(27)は範囲内を意味します[0, 26]
Eng.Fouad 2013

30
@Vulcan 1〜1000の乱数を選択する場合と同様に、ほとんどのシードは最大値に非常に近く、選択するほとんどの数値は3桁です。それについて考えるとき、それは驚くべきことではありません:)
トーマス

18
@Vulcan実際、計算を行うと、それらはゼロとほぼ同じ最大値に近いことがわかります(生成コードではシードが符号なしとして解釈されていると思います)。しかし、桁数は実際の値に対して対数的にのみ増加するため、実際にはそうでない場合でも、数値は非常に近くに見えます。
Thomas

10
すばらしい答えです。ボーナスポイントとして、ランダムを初期化するシードを見つけて、最終的なランダムの初期化に必要な4つのシードのシーケンスを生成できますか?
Marek

13
@マレク:疑似ランダムの神々がそのような振る舞いを承認することはないと思います。
Denis Tulskiy 2013年

254

ここの誰もがコードがどのように機能するかを説明し、独自の例を構築する方法を示すのに優れた仕事をしましたが、ここでは、ブルートフォース検索が最終的に見つけるソリューションが存在することを合理的に期待できる理由を示す情報理論的回答を示します。

26種類の小文字がアルファベットを形成しますΣ。さまざまな長さの単語を生成できるようにするために、終端記号をさらに追加して、拡張アルファベットを生成しますΣ' := Σ ∪ {⊥}

αシンボルとし、Xを一様分布のランダム変数としΣ'ます。そのシンボルP(X = α)とその情報コンテンツを取得する確率はI(α)、次の式で与えられます。

P(X =α)= 1 / |Σ '| = 1/27

I(α)=-log₂[P(X =α)] =-log₂(1/27)=log₂(27)

言葉ω ∈ Σ*とそれに⊥-対応するものω' := ω · ⊥ ∈ (Σ')*については、

I(ω):= I(ω ')= |ω' | *log₂(27)=(|ω| + 1)*log₂(27)

Pseudorandom Number Generator(PRNG)は32ビットのシードで初期化されるので、長さのほとんどの単語は

λ= floor [32 /log₂(27)]-1 = 5

少なくとも1つのシードによって生成されます。6文字の単語を検索したとしても、約41.06%の確率で成功します。汚すぎる格好はやめて。

7文字については、1.52%近くを調べていますが、試してみる前にそのことに気づきませんでした。

#include <iostream>
#include <random>

int main()
{
    std::mt19937 rng(631647094);
    std::uniform_int_distribution<char> dist('a', 'z' + 1);

    char alpha;
    while ((alpha = dist(rng)) != 'z' + 1)
    {
        std::cout << alpha;
    }
}

出力を参照してください:http : //ideone.com/JRGb3l


私の情報理論はちょっと弱いですが、私はこの証明が大好きです。誰かがラムダラインを私に説明できますか?明らかに、一方の情報コンテンツを他方で分割していますが、なぜこれが私たちの語長を与えるのですか?私が言ったように私はちょっと錆びているので明白に尋ねることをお詫びします(NBはシャノン制限と関係がある-コード出力から)
Mike HR

1
@ MikeH-Rラムダ線は、I(⍵)式を並べ替えたものです。I(⍵)32(ビット)であり、|⍵|5(シンボル)であることがわかります。
アイスマン、

67

私はこれらの種を見つけるための簡単なプログラムを書きました:

import java.lang.*;
import java.util.*;
import java.io.*;

public class RandomWords {
    public static void main (String[] args) {
        Set<String> wordSet = new HashSet<String>();
        String fileName = (args.length > 0 ? args[0] : "/usr/share/dict/words");
        readWordMap(wordSet, fileName);
        System.err.println(wordSet.size() + " words read.");
        findRandomWords(wordSet);
    }

    private static void readWordMap (Set<String> wordSet, String fileName) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader(fileName));
            String line;
            while ((line = reader.readLine()) != null) {
                line = line.trim().toLowerCase();
                if (isLowerAlpha(line)) wordSet.add(line);
            }
        }
        catch (IOException e) {
            System.err.println("Error reading from " + fileName + ": " + e);
        }
    }

    private static boolean isLowerAlpha (String word) {
        char[] c = word.toCharArray();
        for (int i = 0; i < c.length; i++) {
            if (c[i] < 'a' || c[i] > 'z') return false;
        }
        return true;
    }

    private static void findRandomWords (Set<String> wordSet) {
        char[] c = new char[256];
        Random r = new Random();
        for (long seed0 = 0; seed0 >= 0; seed0++) {
            for (int sign = -1; sign <= 1; sign += 2) {
                long seed = seed0 * sign;
                r.setSeed(seed);
                int i;
                for (i = 0; i < c.length; i++) {
                    int n = r.nextInt(27);
                    if (n == 0) break;
                    c[i] = (char)((int)'a' + n - 1);
                }
                String s = new String(c, 0, i);
                if (wordSet.contains(s)) {
                    System.out.println(s + ": " + seed);
                    wordSet.remove(s);
                }
            }
        }
    }
}

私は今それをバックグラウンドで実行していますが、古典的なパングラムに十分な単語がすでに見つかりました:

import java.lang.*;
import java.util.*;

public class RandomWordsTest {
    public static void main (String[] args) {
        long[] a = {-73, -157512326, -112386651, 71425, -104434815,
                    -128911, -88019, -7691161, 1115727};
        for (int i = 0; i < a.length; i++) {
            Random r = new Random(a[i]);
            StringBuilder sb = new StringBuilder();
            int n;
            while ((n = r.nextInt(27)) > 0) sb.append((char)('`' + n));
            System.out.println(sb);
        }
    }
}

ideoneのデモ。

Ps。 -727295876, -128911, -1611659, -235516779


35

私はこれに興味をそそられました。このランダムワードジェネレーターを辞書の単語リストで実行しました。範囲:Integer.MIN_VALUEからInteger.MAX_VALUE

15131ヒットしました。

int[] arrInt = {-2146926310, -1885533740, -274140519, 
                -2145247212, -1845077092, -2143584283,
                -2147483454, -2138225126, -2147375969};

for(int seed : arrInt){
    System.out.print(randomString(seed) + " ");
}

プリント

the quick browny fox jumps over a lazy dog 

7
あなたは私の日男を作成しました:DIはLong.Min / Maxでそれを試して、私の同僚の名前を検索しましたが、唯一のピーターが見つかりました:(peter 4611686018451441623 peter 24053719 peter -4611686018403334185 peter -9223372036830722089 peter -4611686017906248127 peter 521139777 peter 4611686018948520706peter 3601860189485207063633636031 4611686017645756173ピーター781631731ピーター4611686019209019635ピーター-9223372036073144077ピーター-4611686017420317288ピーター1007070616ピーター-9223372035847705192)
マルセル

25

ほとんどの乱数ジェネレータは、実際には「疑似乱数」です。それらは線形合同生成器、またはLCG(http://en.wikipedia.org/wiki/Linear_congruential_generator)です。

LCGは、固定されたシードが与えられれば、かなり予測可能です。基本的に、最初の文字を与えるシードを使用し、次にターゲット文字列の次の文字に到達するまで次のint(char)を生成し続けるアプリを記述し、LCGを呼び出す必要があった回数を書き留めます。すべての文字を生成するまで続けます。


3
非疑似乱数ジェネレータの例は何
ですか

1
@chiliNUTそのようなジェネレーターは外部ガジェットです。一部の電子ランプ。または、0または1が読み取られる不正に書き込まれたビット。乱数の純粋なデジタルジェネレーターを実行することはできません。デジタルアルゴリズムはランダムではなく、正確です。
Gangnus 2017

@chiliNUT多くのオペレーティングシステムがエントロピーを収集します。たとえばLinuxでは、/dev/urandomデバイスを使用してランダムデータを読み取ることができます。ただし、これは希少なリソースです。そのため、このようなランダムデータは通常、PRNGのシードに使用されます。
エイドリアンW

@AdrianWウィキペディアurandomはまだ疑似ランダムであるen.wikipedia.org/wiki//dev/random
chiliNUT

1
はい。ただし、暗号的に安全です。つまり、から作成されたランダムシーケンスを使用して、ブルートフォース攻撃(「ランダム」シーケンス「hello world」のシードを見つけるなど)を行うことはできません/dev/random。私は上記の引用論文は述べているLinuxのカーネルは、キーボードのタイミング、マウスの動き、およびIDEタイミングからエントロピーを生成し、特殊ファイル/ dev / randomと/ dev / urandomを介して、他のオペレーティング・システム・プロセスへのランダムな文字データを利用できるようになります。それは本当にランダムだと信じさせてくれました。それは完全に正しくないかもしれません。しかし/dev/random、少なくともある程度のエントロピーが含まれています。
エイドリアンW

23

マルチスレッドはJavaで非常に簡単なので、利用可能なすべてのコアを使用してシードを検索するバリアントを次に示します。http//ideone.com/ROhmTA

import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

public class SeedFinder {

  static class SearchTask implements Callable<Long> {

    private final char[] goal;
    private final long start, step;

    public SearchTask(final String goal, final long offset, final long step) {
      final char[] goalAsArray = goal.toCharArray();
      this.goal = new char[goalAsArray.length + 1];
      System.arraycopy(goalAsArray, 0, this.goal, 0, goalAsArray.length);
      this.start = Long.MIN_VALUE + offset;
      this.step = step;
    }

    @Override
    public Long call() throws Exception {
      final long LIMIT = Long.MAX_VALUE - this.step;
      final Random random = new Random();
      int position, rnd;
      long seed = this.start;

      while ((Thread.interrupted() == false) && (seed < LIMIT)) {
        random.setSeed(seed);
        position = 0;
        rnd = random.nextInt(27);
        while (((rnd == 0) && (this.goal[position] == 0))
                || ((char) ('`' + rnd) == this.goal[position])) {
          ++position;
          if (position == this.goal.length) {
            return seed;
          }
          rnd = random.nextInt(27);
        }
        seed += this.step;
      }

      throw new Exception("No match found");
    }
  }

  public static void main(String[] args) {
    final String GOAL = "hello".toLowerCase();
    final int NUM_CORES = Runtime.getRuntime().availableProcessors();

    final ArrayList<SearchTask> tasks = new ArrayList<>(NUM_CORES);
    for (int i = 0; i < NUM_CORES; ++i) {
      tasks.add(new SearchTask(GOAL, i, NUM_CORES));
    }

    final ExecutorService executor = Executors.newFixedThreadPool(NUM_CORES, new ThreadFactory() {

      @Override
      public Thread newThread(Runnable r) {
        final Thread result = new Thread(r);
        result.setPriority(Thread.MIN_PRIORITY); // make sure we do not block more important tasks
        result.setDaemon(false);
        return result;
      }
    });
    try {
      final Long result = executor.invokeAny(tasks);
      System.out.println("Seed for \"" + GOAL + "\" found: " + result);
    } catch (Exception ex) {
      System.err.println("Calculation failed: " + ex);
    } finally {
      executor.shutdownNow();
    }
  }
}

私のようなJavaのnoobのには、次のようにして出力番号をサフィックスする必要があるLとの引数の型に変更long、すなわちをrandomString(long i)遊ぶために。:)
Fruit

21

ランダムは常に同じシーケンスを返します。配列の入れ替えやその他の操作に順列として使用されます。

異なるシーケンスを取得するには、「シード」と呼ばれるある位置でシーケンスを初期化する必要があります。

randomStingは、「ランダム」シーケンスのi位置(シード= -229985452)の乱数を取得します。次に、シード位置の後のシーケンスの次の27文字のASCIIコードを使用して、この値が0になるまでこれを返します。これにより、「hello」が返されます。同じ操作が "world"に対しても行われます。

他の単語ではコードが機能しなかったと思います。ランダムなシーケンスをよく知っているプログラムを作った人。

これは非常に優れたオタクコードです。


10
彼が「ランダムシーケンスをよく知っている」かどうかは疑わしい。より可能性が高いのは、有効なシードが見つかるまで、数十億の可能なシードを試したところです。
dan04 2013年

24
@ dan04実際のプログラマーはPRNGを使用するだけでなく、期間全体を暗記し、必要に応じて値を列挙します。
トーマス

1
「ランダムは常に同じシーケンスを返す」-ランダムの後に()を置くか、コードとして表示します。それ以外の場合、文は偽です。
Gangnus 2017

14

プリンシパルは、同じシードで構築されたランダムクラスであり、毎回同じパターンの数値を生成します。


12

由来デニスTulskiyの答えは、この方法では、シードを生成します。

public static long generateSeed(String goal, long start, long finish) {
    char[] input = goal.toCharArray();
    char[] pool = new char[input.length];
    label:
        for (long seed = start; seed < finish; seed++) {
            Random random = new Random(seed);

            for (int i = 0; i < input.length; i++)
                pool[i] = (char) (random.nextInt(27)+'`');

            if (random.nextInt(27) == 0) {
                for (int i = 0; i < input.length; i++) {
                    if (input[i] != pool[i])
                        continue label;
                }
                return seed;
            }

        }

    throw new NoSuchElementException("Sorry :/");
}

10

Java docsから、これはRandomクラスのシード値を指定するときの意図的な機能です。

Randomの2つのインスタンスが同じシードで作成され、それぞれに対して同じメソッド呼び出しのシーケンスが行われる場合、それらは同じ数のシーケンスを生成して返します。このプロパティを保証するために、特定のアルゴリズムがRandomクラスに指定されています。Javaコードの絶対的な移植性のために、Java実装はRandomクラスに対してここに示すすべてのアルゴリズムを使用する必要があります。

http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Random.html

奇妙なことに、予測可能な「ランダムな」数を持つことには暗黙のセキュリティ問題があると思うでしょう。


3
そのため、のデフォルトコンストラクターは、Random「乱数ジェネレーターのシードを、このコンストラクターの他の呼び出しとは異なる可能性が非常に高い値に設定します」(javadoc)。現在の実装では、これは現在の時刻とカウンターの組み合わせです。
マーティン2013年

確かに。その場合、おそらく初期シード値を指定するための実用的なユースケースがあるでしょう。これが、取得できる疑似ランダムキーフォブ(RSAのものか)の動作原理だと思います
deed02392

4
@ deed02392もちろん、シード値を指定するための実用的なユースケースがあります。問題を解決するためにある種のモンテカルロアプローチを使用するようにデータをシミュレーションしている場合、結果を再現できるのは良いことです。初期シードを設定するのが最も簡単な方法です。
Dason


3

以下は、Denis Tulskiyの回答のマイナーな改善です。時間を半分に短縮

public static long[] generateSeed(String goal, long start, long finish) {
    char[] input = goal.toCharArray();

    int[] dif = new int[input.length - 1];
    for (int i = 1; i < input.length; i++) {
        dif[i - 1] = input[i] - input[i - 1];
    }

    mainLoop:
    for (long seed = start; seed < finish; seed++) {
        Random random = new Random(seed);
        int lastChar = random.nextInt(27);
        int base = input[0] - lastChar;
        for (int d : dif) {
            int nextChar = random.nextInt(27);
            if (nextChar - lastChar != d) {
                continue mainLoop;
            }
            lastChar = nextChar;
        }
        if(random.nextInt(27) == 0){
            return new long[]{seed, base};
        }
    }

    throw new NoSuchElementException("Sorry :/");
}

1

それはすべて入力シードに関するものです。同じ種はいつも同じ結果を与えます。プログラムを何度も再実行しても、同じ出力になります。

public static void main(String[] args) {

    randomString(-229985452);
    System.out.println("------------");
    randomString(-229985452);

}

private static void randomString(int i) {
    Random ran = new Random(i);
    System.out.println(ran.nextInt());
    System.out.println(ran.nextInt());
    System.out.println(ran.nextInt());
    System.out.println(ran.nextInt());
    System.out.println(ran.nextInt());

}

出力

-755142161
-1073255141
-369383326
1592674620
-1524828502
------------
-755142161
-1073255141
-369383326
1592674620
-1524828502
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.