Perché questo codice usando stringhe casuali stampa "ciao mondo"?


1769

La seguente dichiarazione di stampa stampa "ciao mondo". Qualcuno potrebbe spiegarlo?

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

E si randomString()presenta così:

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
Bene, quei semi particolari funzionano perfettamente. Il casuale non è veramente casuale, è pseudocasuale.
martedì

341
Funziona, come altri hanno già detto, perché random non lo è. Per me, una domanda più interessante sarebbe la persona che l'ha scritto, forzarla brutalmente o c'è un modo semplice per prevedere quale casuale genererebbe per i prossimi valori di N per un dato seme. La forzatura bruta è facile e con l'hardware moderno non dovrebbe richiedere troppo tempo, quindi era sicuramente un modo fattibile per farlo. Dato che è statico, potresti persino distribuire facilmente la ricerca attraverso una rete.
jmoreno,

78
Mi chiedo lo scopo di nin for (int n = 0; ; n++). Potrebbero usare for(;;)o while(true)invece!
Ing

13
In una sequenza veramente casuale alla fine apparirà ogni possibile stringa. In una sequenza pseudo casuale di alta qualità, si può ragionevolmente aspettarsi ogni possibile stringa di bit di lunghezza (log_s (N) - n) (dove N è il numero di bit nello stato interno dei PRNG e n è un numero piccolo, selezioniamo 8 per comodità ) per apparire nel ciclo. Questo codice ottiene un po 'di aiuto dall'uso di un punto di inizio hardcoded liberamente scelto (il valore del backtick del carattere) che recupera quasi tutti gli 8 bit.
dmckee --- ex gattino moderatore

13
Questo è da un post che ho scritto un paio di anni fa. vanillajava.blogspot.co.uk/2011/10/randomly-no-so-random.html
Peter Lawrey

Risposte:


917

Quando un'istanza di java.util.Randomviene costruita con un parametro seed specifico (in questo caso -229985452o -147909649), segue l'algoritmo di generazione di numeri casuali che inizia con quel valore seed.

Ogni Randomcostruito con lo stesso seme genererà ogni volta lo stesso modello di numeri.


8
@Vulcan - il javadoc afferma che il seme è di 48 bit. docs.oracle.com/javase/7/docs/api/java/util/Random.html . Inoltre, i semi effettivi hanno valori a 32 bit.
Stephen C

80
Ogni elemento della sequenza numerica casuale è preso modulo 27 e ci sono 6 elementi in ciascuno di "hello\0"e "world\0". Se assumessi un generatore veramente casuale, le probabilità sarebbero 1 su 27 ^ 6 (387.420.489) di ottenere la sequenza che stavi cercando - quindi è abbastanza impressionante ma non del tutto strabiliante!
Russell Borogove,

17
@RussellBorogove: Ma con quelle probabilità e 2 ^ 64 possibili semi, ci sono 47,6 miliardi di valori seme previsti che danno quella sequenza. È solo una questione di trovarne uno.
dan04

8
@ dan04 - Non ero abbastanza disposto a fare quella stima; a seconda dell'implementazione del PRNG, la dimensione della parola seme potrebbe non essere uguale alla dimensione dello stato e i percorsi della sequenza potrebbero non essere distribuiti uniformemente. Tuttavia, le probabilità sono decisamente buone, e se non riuscissi a trovare una coppia potresti riprovare con un involucro diverso ( "Hello" "World"), o usando al 122-kposto di 96+k, o ...
Russell Borogove

7
@ ThorbjørnRavnAndersen Javadoc specifica che "determinati algoritmi sono specificati per la classe Casuale. Le implementazioni Java devono utilizzare tutti gli algoritmi mostrati qui per la classe Casuale, per motivi di portabilità assoluta del codice Java."
FThompson,

1137

Le altre risposte spiegano perché, ma ecco come.

Data un'istanza di Random:

Random r = new Random(-229985452)

I primi 6 numeri che r.nextInt(27)genera sono:

8
5
12
12
15
0

e i primi 6 numeri che r.nextInt(27)genera dati Random r = new Random(-147909649)sono:

23
15
18
12
4
0

Quindi aggiungi quei numeri alla rappresentazione intera del carattere `(che è 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
Pedanticamente, new Random(-229985452).nextInt(27)restituisce sempre 8.
user253751

1
@immibis perché? intendo casuale () dovrebbe restituire ogni volta un numero casuale, non un set di numeri ordinati fissi?
roottraveller

5
@rootTraveller Per cominciare, new Random()non restituisce affatto un numero.
user253751

2
C'è un modo per calcolare questi semi? Deve esserci della logica ... o è solo forza bruta.
Sohit Gore,

2
@SohitGore Dato che il valore predefinito di Java Randomnon è crittograficamente sicuro (sono abbastanza sicuro che sia un Mersenne Twister, ma non citarmi su questo), è probabilmente possibile tornare indietro da "Voglio questi numeri" a "questo è il seme che userei ". Ho fatto qualcosa di simile con il generatore congruenziale lineare C standard.
Finanzi la causa di Monica il

280

Lo lascio qui. Chiunque abbia un sacco di tempo (CPU) da dedicare, sentiti libero di sperimentare :) Inoltre, se hai imparato un po 'di fork-join-fu per far bruciare tutti i core della CPU (solo i thread sono noiosi, giusto?), Per favore condividi il tuo codice. Lo apprezzerei molto.

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

Produzione:

-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)significa all'interno dell'intervallo [0, 26].
Ing

30
@Vulcan La maggior parte dei semi è molto vicina al valore massimo, proprio come se selezioni numeri casuali tra 1 e 1000, la maggior parte dei numeri che finisci per raccogliere avrà tre cifre. Non è sorprendente, quando ci pensi :)
Thomas

18
@Vulcan In effetti, se fai la matematica, vedrai che si avvicinano al valore massimo quanto a zero (suppongo che il seme venga interpretato come un unsigned nel codice di generazione). Ma poiché il numero di cifre cresce solo in modo logaritmico con il valore reale, il numero appare molto vicino quando non lo è.
Thomas

10
Bella risposta. E per i punti bonus, puoi trovare un seme che inizializzerà un Casuale che produrrà la sequenza di 4 semi necessari per l'inizializzazione dei random finali?
Marek,

13
@Marek: non credo che gli dei pseudo casuali approverebbero tale comportamento.
Denis Tulskiy l'

254

Tutti qui hanno fatto un ottimo lavoro nel spiegare come funziona il codice e nel mostrare come è possibile costruire i propri esempi, ma ecco una risposta teorica informativa che mostra perché possiamo ragionevolmente aspettarci che esista una soluzione che la ricerca della forza bruta alla fine troverà.

Le 26 diverse lettere minuscole formano il nostro alfabeto Σ. Per consentire la generazione di parole di lunghezze diverse, aggiungiamo inoltre un simbolo di terminazione per produrre un alfabeto esteso Σ' := Σ ∪ {⊥}.

Sia αun simbolo e X una variabile casuale distribuita uniformemente Σ'. La probabilità di ottenere quel simbolo, P(X = α)e il suo contenuto informativo I(α), sono date da:

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

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

Per una parola ω ∈ Σ*e la sua ⊥-controparte terminata ω' := ω · ⊥ ∈ (Σ')*, abbiamo

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

Poiché il generatore di numeri pseudorandom (PRNG) è inizializzato con un seme a 32 bit, possiamo aspettarci la maggior parte delle parole di lunghezza fino a

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

essere generato da almeno un seme. Anche se dovessimo cercare una parola di 6 caratteri, avremmo comunque successo circa il 41,06% delle volte. Non troppo malandato.

Per 7 lettere ci stiamo avvicinando all'1,52%, ma non mi ero reso conto che prima di provarlo:

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

Vedi l'output: http://ideone.com/JRGb3l


la mia teoria dell'informazione è piuttosto debole ma adoro questa prova. qualcuno può spiegarmi la linea lambda, chiaramente stiamo dividendo il contenuto informativo dell'uno con l'altro, ma perché questo ci dà la lunghezza delle nostre parole? come ho detto, sono un po 'arrugginito, quindi mi scuso per aver chiesto l'ovvio (NB è qualcosa a che fare con il limite di Shannon - dall'output del codice)
Mike HR,

1
@ MikeH-R La linea lambda è l' I(⍵)equazione riorganizzata. I(⍵)è 32 (bit) e |⍵|risulta essere 5 (simboli).
Iceman

67

Ho scritto un programma rapido per trovare questi semi:

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

Ora ho in esecuzione in background, ma ha già trovato abbastanza parole per un classico pangram:

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

( Demo su ideone. )

Ps. -727295876, -128911, -1611659, -235516779.


35

Sono stato incuriosito da questo, ho eseguito questo generatore di parole casuali su un elenco di parole del dizionario. Intervallo: Integer.MIN_VALUE a Integer.MAX_VALUE

Ho ottenuto 15131 colpi.

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

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

stampe

the quick browny fox jumps over a lazy dog 

7
Hai fatto il mio giorno uomo: DI l'ho provato con Long.Min / Max e ho cercato i nomi dei miei colleghi e ho trovato solo peter: (peter 4611686018451441623 peter 24053719 peter -4611686018403334185 peter -9223372036830723907337607337 4611686017645756173 peter 781631731 peter 4611686019209019635 peter -9223372036073144077 peter -4611686017420317288 peter 1007070616 peter -9223372035847705192)
Marcel

25

La maggior parte dei generatori di numeri casuali sono, infatti, "pseudo casuali". Sono generatori congruenziali lineari o LCG ( http://en.wikipedia.org/wiki/Linear_congruential_generator )

I LCG sono abbastanza prevedibili dato un seme fisso. Fondamentalmente, usa un seme che ti dà la tua prima lettera, quindi scrivi un'app che continua a generare l'int (char) successivo fino a quando non colpisci la lettera successiva nella stringa di destinazione e annota quante volte hai dovuto invocare il LCG. Continua fino a quando non hai generato ogni singola lettera.


3
qual è un esempio di un generatore di numeri casuali non pseudo
chiliNUT

1
@chiliNUT Tali generatori sono gadget esterni. Qualche lampada elettronica. O bit scritto male che viene letto 0 o 1. Non puoi fare un puro generatore digitale di numeri casuali, gli algoritmi digitali NON sono casuali, sono assolutamente precisi.
Gangnus,

@chiliNUT Molti sistemi operativi raccolgono entropia . Ad esempio in Linux è possibile utilizzare il /dev/urandomdispositivo per leggere dati casuali. Questa è una risorsa scarsa, tuttavia. Pertanto, tali dati casuali vengono normalmente utilizzati per eseguire il seeding di PRNG.
Adrian W,

@AdrianW Wikipedia dice che urandomè ancora pseudo casuale en.wikipedia.org/wiki//dev/random
chiliNUT

1
Sì, ma è crittograficamente sicuro, il che significa che non è possibile effettuare attacchi di forza bruta (come trovare il seme per la sequenza "casuale" "ciao mondo") con sequenze casuali create da /dev/random. L'articolo che ho citato sopra dice che il kernel Linux genera entropia da tempistiche della tastiera, movimenti del mouse e temporizzazioni IDE e rende i dati dei caratteri casuali disponibili ad altri processi del sistema operativo attraverso i file speciali / dev / random e / dev / urandom. Questo mi fa credere che sia davvero casuale. Potrebbe non essere del tutto corretto. Ma /dev/randomalmeno contiene un po 'di entropia.
Adrian W,

23

Poiché il multi-threading è molto semplice con Java, ecco una variante che cerca un seme usando tutti i core disponibili: 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();
    }
  }
}

Per java noob come me, è necessario aggiungere un suffisso al numero di output Le modificare il tipo di argomento long, ad esempio randomString(long i)per giocare. :)
Fruit

21

Casuale restituisce sempre la stessa sequenza. È usato per mescolare array e altre operazioni come permutazioni.

Per ottenere sequenze diverse, è necessario inizializzare la sequenza in una posizione, chiamata "seed".

RandomSting ottiene il numero casuale nella posizione i (seed = -229985452) della sequenza "random". Quindi utilizza l' ASCII codice per i successivi 27 caratteri nella sequenza dopo la posizione del seme fino a quando questo valore non è uguale a 0. Questo restituisce "ciao". La stessa operazione viene eseguita per "mondo".

Penso che il codice non abbia funzionato per altre parole. Il ragazzo che ha programmato che conosce molto bene la sequenza casuale.

È un ottimo codice geek!


10
Dubito che "conosca molto bene la sequenza casuale". Più probabilmente, ha appena provato miliardi di semi possibili fino a trovare quello che ha funzionato.
dan04

24
@ dan04 I veri programmatori non usano semplicemente il PRNG, ricordano a memoria l'intero periodo e enumerano i valori secondo necessità.
Thomas

1
"Casuale restituisce sempre la stessa sequenza" - put () dopo Casuale o mostralo come codice. Altrimenti la frase è falsa.
Gangnus,

14

Il principale è la Classe casuale costruita con lo stesso seme genererà ogni volta lo stesso modello di numeri.


12

Derivato dalla risposta di Denis Tulskiy , questo metodo genera il seme.

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

Dai documenti Java, questa è una funzione intenzionale quando si specifica un valore seed per la classe Random.

Se vengono create due istanze di Random con lo stesso seed e viene fatta la stessa sequenza di chiamate di metodo per ciascuna, esse genereranno e restituiranno identiche sequenze di numeri. Al fine di garantire questa proprietà, sono stati specificati algoritmi particolari per la classe Casuale. Le implementazioni Java devono utilizzare tutti gli algoritmi mostrati qui per la classe Casuale, per motivi di portabilità assoluta del codice Java.

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

Stranamente, potresti pensare che ci siano problemi di sicurezza impliciti nell'avere numeri "casuali" prevedibili.


3
Ecco perché il costruttore predefinito di Random"imposta il seme del generatore di numeri casuali su un valore che molto probabilmente sarà distinto da qualsiasi altra invocazione di questo costruttore" ( javadoc ). Nell'attuale implementazione questa è una combinazione dell'ora corrente e un contatore.
martin

Infatti. Presumibilmente ci sono casi d'uso pratici per specificare il valore iniziale del seme, quindi. Immagino sia il principio di funzionamento di quei telecomandi pseudocasuali che puoi ottenere (quelli RSA?)
deed02392

4
@ deed02392 Naturalmente ci sono casi d'uso pratici per specificare un valore seed. Se stai simulando i dati per utilizzare una sorta di approccio Monte Carlo per risolvere un problema, è una buona cosa poter riprodurre i tuoi risultati. L'impostazione di un seed iniziale è il modo più semplice per farlo.
Dason,


3

Ecco un piccolo miglioramento per la risposta di Denis Tulskiy . Dimezza il tempo

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

Riguarda il seme di input . Lo stesso seme fornisce sempre gli stessi risultati. Anche tu riesegui il tuo programma ancora e ancora, è lo stesso output.

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

}

Produzione

-755142161
-1073255141
-369383326
1592674620
-1524828502
------------
-755142161
-1073255141
-369383326
1592674620
-1524828502
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.