web-dev-qa-db-fra.com

Pourquoi ce code utilisant des chaînes aléatoires affiche-t-il "hello world"?

La déclaration d'impression suivante afficherait "hello world". Quelqu'un pourrait-il expliquer cela?

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

Et randomString() ressemble à ceci:

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

Lorsqu'une instance de Java.util.Random est construite avec un paramètre de départ spécifique (dans ce cas, -229985452 ou -147909649), elle suit l'algorithme de génération de nombres aléatoires commençant à avec cette valeur de départ.

Chaque Random construite avec la même graine générera le même motif de nombres à chaque fois.

898
FThompson

Les autres réponses expliquent pourquoi, mais voici comment.

Étant donné une instance de Random:

Random r = new Random(-229985452)

Les 6 premiers chiffres générés par r.nextInt(27) sont:

8
5
12
12
15
0

et les 6 premiers chiffres générés par r.nextInt(27) étant donné Random r = new Random(-147909649) sont les suivants:

23
15
18
12
4
0

Ajoutez simplement ces nombres à la représentation entière du caractère ` (qui est 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
1124
Eng.Fouad

Je vais juste le laisser ici. Si vous avez beaucoup de temps (CPU) à votre disposition, n'hésitez pas à expérimenter :) De plus, si vous avez maîtrisé quelques fonctions fork-join-fu pour que cette chose brûle tous les cœurs de la CPU (les threads sont ennuyeux, n'est-ce pas?), Partagez-les votre code. Je l'apprécierais beaucoup.

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

Sortie:

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

Tout le monde ici a très bien expliqué le fonctionnement du code et vous a montré comment construire vos propres exemples, mais voici une réponse théorique qui explique pourquoi nous pouvons raisonnablement nous attendre à ce qu'il existe une solution que la recherche par force brute finira par trouver.

Les 26 lettres minuscules différentes forment notre alphabet Σ. Pour permettre de générer des mots de différentes longueurs, nous ajoutons en outre un symbole de terminaison pour obtenir un alphabet étendu Σ' := Σ ∪ {⊥}.

Soit α un symbole et X une variable aléatoire uniformément distribuée sur Σ'. La probabilité d'obtenir ce symbole, P(X = α), et son contenu informatif, I(α), est donnée par:

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

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

Pour un mot ω ∈ Σ* et son ⊥-motice terminé ω' := ω · ⊥ ∈ (Σ')*, nous avons

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

Étant donné que le générateur de nombre pseudo-aléatoire (PRNG) est initialisé avec une graine 32 bits, nous pouvons nous attendre à la plupart des mots de longueur

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

être généré par au moins une graine. Même si nous recherchions un mot de 6 caractères, nous aurions encore du succès environ 41,06% du temps. Pas trop mal.

Pour 7 lettres, on se rapproche de 1,52%, mais je ne m'en étais pas rendu compte avant d'essayer:

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

Voir la sortie: http://ideone.com/JRGb3l

253
xDD

J'ai écrit un programme rapide pour trouver ces graines:

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

Je l'ai en cours d'exécution en arrière-plan, mais il a déjà trouvé assez de mots pour un pangram classique:

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

( Démo sur ideone. )

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

66
Ilmari Karonen

Cela m'a intrigué, j'ai lancé ce générateur de mots au hasard sur une liste de mots du dictionnaire. Plage: Integer.MIN_VALUE à Integer.MAX_VALUE

J'ai eu 15131 hits.

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

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

Impressions

the quick browny fox jumps over a lazy dog 
32
Puru--

La plupart des générateurs de nombres aléatoires sont, en fait, "pseudo aléatoires". Ce sont des générateurs linéaires congruentiels, ou LCG ( http://en.wikipedia.org/wiki/Linear_congruential_generator )

Les GCL sont assez prévisibles avec une graine fixe. Fondamentalement, utilisez une graine qui vous donne votre première lettre, puis écrivez une application qui continue de générer le prochain int (caractère) jusqu'à ce que vous frappiez la lettre suivante dans votre chaîne cible et notez le nombre de fois que vous avez dû appeler le LCG. Continuez jusqu'à ce que vous ayez généré chaque lettre.

25

Comme le multi-threading est très facile avec Java, voici une variante qui recherche une graine en utilisant tous les cœurs disponibles: 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();
    }
  }
}
22
TwoThe

Aléatoire retourne toujours la même séquence. Il est utilisé pour mélanger des tableaux et d'autres opérations en tant que permutations.

Pour obtenir différentes séquences, il est nécessaire d’initialiser la séquence dans une position, appelée "graine".

RandomSting obtient le nombre aléatoire en position i (graine = -229985452) de la séquence "aléatoire". Utilise ensuite le code ASCII pour les 27 caractères suivants de la séquence après la position d'origine jusqu'à ce que cette valeur soit égale à 0. Cela renvoie le "bonjour". La même opération est faite pour "monde".

Je pense que le code n'a pas fonctionné pour d'autres mots. Le gars qui a programmé connaît très bien la séquence aléatoire.

C'est un très bon code geek!

Le principal est la classe aléatoire construite avec la même graine générera le même motif de nombres à chaque fois.

14
tomj0101

Dérivée de la réponse de Denis Tulskiy , cette méthode génère la graine.

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 :/");
}
12
sulai

Dans les documents Java, il s'agit d'une fonctionnalité intentionnelle lors de la spécification d'une valeur de départ pour la classe Random.

Si deux instances de Random sont créées avec la même graine et que la même séquence d'appels de méthode est effectuée pour chacune d'elles, elles généreront et renverront des séquences identiques de nombres. Afin de garantir cette propriété, des algorithmes particuliers sont spécifiés pour la classe Random. Java les implémentations doivent utiliser tous les algorithmes présentés ici pour la classe Random, dans un souci de portabilité absolue du code Java.

http://docs.Oracle.com/javase/1.4.2/docs/api/Java/util/Random.html

Curieusement, on pourrait penser qu’il ya des problèmes de sécurité implicites à ce que les nombres "aléatoires" soient prévisibles.

10
deed02392

Il s'agit de "graine". Les mêmes graines donnent le même résultat.

8
Burak Keceli

Voici une amélioration mineure pour Denis Tulskiy réponse . Ça réduit le temps de moitié

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 :/");
}
3
Ilya Gazman

Tout dépend de l'entrée graine. La même graine donne les mêmes résultats tout le temps. Même si vous relancez votre programme encore et encore, c'est la même sortie.

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

}

Sortie

-755142161
-1073255141
-369383326
1592674620
-1524828502
------------
-755142161
-1073255141
-369383326
1592674620
-1524828502
0
nagendra547