¿Por qué este código que usa cadenas aleatorias imprime "hola mundo"?

La siguiente declaración de impresión imprimiría "hola mundo". ¿Alguien podría explicar esto?

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

Y randomString() Se ve como esto:

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

preguntado el 03 de marzo de 13 a las 04:03

Bueno, esas semillas en particular funcionan perfectamente. Random no es verdaderamente aleatorio, es pseudoaleatorio. -

Funciona, como han dicho otros, porque el azar no lo es. Para mí, una pregunta más interesante sería si la persona que escribió eso, lo hizo por fuerza bruta, o si hay una manera fácil de predecir qué generaría aleatoriamente para los próximos N valores para una semilla dada. La fuerza bruta es fácil y con el hardware moderno no debería tomar mucho tiempo, por lo que fue una forma viable de hacerlo. Dado que es estático, incluso podría distribuir fácilmente la búsqueda a través de una red. -

Me pregunto el propósito de n in for (int n = 0; ; n++). Ellos podrían usar for(;;) or while(true) ¡en lugar de! -

En una secuencia verdaderamente aleatoria, eventualmente aparecerán todas las cadenas posibles. En una secuencia pseudoaleatoria de alta calidad, se puede esperar razonablemente cada cadena posible de longitud (log_s(N) - n) bits (donde N es el número de bits en el estado interno de los PRNG y n es un número pequeño, elijamos 8 por conveniencia ) para aparecer en el ciclo. Este código recibe alguna ayuda del uso de un punto de inicio codificado libremente elegido (el valor de la tilde grave del carácter) que recupera casi la totalidad de los 8 bits. -

Si tuviera que refactorizar esto, además de refactorizar las llaves, solo cambiaría el nombre del método por uno más descriptivo: fixedAndNotSoRandomString o algo... -

15 Respuestas

Las otras respuestas explican por qué, pero aquí está cómo.

Dada una instancia de Random:

Random r = new Random(-229985452)

Los primeros 6 números que r.nextInt(27) genera son:

8
5
12
12
15
0

y los primeros 6 números que r.nextInt(27) genera dado Random r = new Random(-147909649) son:

23
15
18
12
4
0

Luego simplemente agregue esos números a la representación entera del carácter ` (que es 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

Respondido 08 Abr '17, 00:04

Con pedantería, new Random(-229985452).nextInt(27) siempre devuelve 8. - user253751

@rootTraveller Para empezar, new Random() no devuelve un número en absoluto. - user253751

@roottraveller "Random" es un generador de números pseudoaleatorios determinista. Si lo inicializa con una semilla fija, producirá una secuencia fija de números. - enchufar

¿Hay alguna forma de calcular estas semillas? Debe haber algo de lógica... o es solo fuerza bruta. - Sohit Gore

@SohitGore Dado que el valor predeterminado de Java Random no es criptográficamente seguro (estoy bastante seguro de que es un Mersenne Twister, pero no me cites al respecto), probablemente sea posible retroceder desde "Quiero estos números" hasta "esta es la semilla que usaría". He hecho algo similar con el generador congruencial lineal C estándar. - Nic

Cuando una instancia de java.util.Random se construye con un parámetro semilla específico (en este caso -229985452 or -147909649), sigue el algoritmo de generación de números aleatorios comienzo con ese valor semilla.

Cada Random construido con la misma semilla generará el mismo patrón de números cada vez.

respondido 03 mar '13, 04:03

@Vulcan: el javadoc dice que la semilla es de 48 bits. docs.oracle.com/javase/7/docs/api/java/util/Random.html. Y además, las semillas reales son valores de 32 bits. - Stephen C

Cada elemento de la secuencia de números aleatorios se toma módulo 27, y hay 6 elementos en cada uno de "hello\0" y "world\0". Si asumiera un generador verdaderamente aleatorio, las probabilidades serían de 1 en 27 ^ 6 (387,420,489 XNUMX XNUMX) de obtener la secuencia que estaba buscando, ¡así que es bastante impresionante pero no del todo alucinante! - russell borogove

@RussellBorogove: Pero con esas probabilidades y 2^64 semillas posibles, se esperan 47.6 millones de valores de semillas que dan esa secuencia. Es solo cuestión de encontrar uno. - dan04

@ dan04: no estaba dispuesto a hacer esa estimación; dependiendo de la implementación de PRNG, el tamaño de la palabra semilla puede no ser igual al tamaño del estado y las rutas de secuencia pueden no estar distribuidas uniformemente. Pero aún así, las probabilidades son definitivamente buenas, y si no puede encontrar un par, puede intentarlo nuevamente con una carcasa diferente ("Hello" "World"), o usando 122-k en lugar de 96+k, o... - russell borogove

@ ThorbjørnRavnAndersen El Javadoc especifica que "se especifican algoritmos particulares para la clase Random. Las implementaciones de Java deben usar todos los algoritmos que se muestran aquí para la clase Random, en aras de la portabilidad absoluta del código Java". - F.Thompson

Lo dejaré aquí. Quien tenga mucho tiempo (de CPU) de sobra, siéntase libre de experimentar :) Además, si ha dominado algunos fork-join-fu para hacer que esta cosa queme todos los núcleos de CPU (solo los hilos son aburridos, ¿verdad?), por favor comparta tu codigo. Me sería de gran aprecio.

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

Salida:

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

respondido 30 mar '13, 05:03

@Uno dos tres nextInt(27) significa dentro del rango [0, 26]. - Ing.Fouad

@Vulcan La mayoría de las semillas están muy cerca del valor máximo, al igual que si selecciona números aleatorios entre 1 y 1000, la mayoría de los números que termine eligiendo tendrán tres dígitos. No es sorprendente, cuando lo piensas :) - Thomas

@Vulcan De hecho, si hace los cálculos, verá que están tan cerca del valor máximo como cero (supongo que la semilla se interpreta como sin firmar en el código de generación). Pero debido a que la cantidad de dígitos crece solo logarítmicamente con el valor real, el número parece muy cercano cuando en realidad no lo es. - Thomas

Gran respuesta. Y para obtener puntos de bonificación, ¿puedes encontrar una semilla que inicialice una aleatoria que produzca la secuencia de 4 semillas requerida para la inicialización de las aleatorias finales? - Marek

@Marek: No creo que los dioses del pseudoaleatorio aprueben tal comportamiento. - Denis Tulsky

Todos aquí hicieron un gran trabajo al explicar cómo funciona el código y mostrar cómo puede construir sus propios ejemplos, pero aquí hay una respuesta teórica de información que muestra por qué podemos esperar razonablemente que exista una solución que la búsqueda de fuerza bruta eventualmente encontrará.

Las 26 letras minúsculas diferentes forman nuestro alfabeto Σ. Para permitir la generación de palabras de diferentes longitudes, agregamos además un símbolo de terminador para producir un alfabeto extendido Σ' := Σ ∪ {⊥}.

Asegúrate de que α ser un símbolo y X una variable aleatoria uniformemente distribuida sobre Σ'. La probabilidad de obtener ese símbolo, P(X = α), y su contenido de información, I(α), están dadas por:

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

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

por una palabra ω ∈ Σ* y su ⊥-contraparte terminada ω' := ω · ⊥ ∈ (Σ')*, Tenemos

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

Dado que el generador de números pseudoaleatorios (PRNG) se inicializa con una semilla de 32 bits, podemos esperar que la mayoría de las palabras tengan una longitud de hasta

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

ser generado por al menos una semilla. Incluso si tuviéramos que buscar una palabra de 6 caracteres, tendríamos éxito aproximadamente el 41.06 % de las veces. No está nada mal.

Para 7 letras estamos más cerca del 1.52 %, pero no me había dado cuenta de eso antes de intentarlo:

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

Ver el resultado: http://ideone.com/JRGb3l

Respondido el 20 de junio de 20 a las 10:06

Escribí un programa rápido para encontrar estas semillas:

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

Lo tengo ejecutándose en segundo plano ahora, pero ya ha encontrado suficientes palabras para un pangrama clásico:

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

(Demostración en ideone.)

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

respondido 03 mar '13, 18:03

Estaba intrigado por esto, ejecuté este generador de palabras aleatorias en una lista de palabras del diccionario. Rango: Entero.MIN_VALUE a Entero.MAX_VALUE

Obtuve 15131 visitas.

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

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

Huellas dactilares

the quick browny fox jumps over a lazy dog 

Respondido 14 Abr '14, 04:04

You made my day man :DI tried it with Long.Min/Max and search for names of my colleagues and only found peter : ( peter 4611686018451441623 peter 24053719 peter -4611686018403334185 peter -9223372036830722089 peter -4611686017906248127 peter 521139777 peter 4611686018948527681 peter -9223372036333636031 peter - 4611686017645756173 pedro 781631731 pedro 4611686019209019635 pedro -9223372036073144077 pedro -4611686017420317288 pedro 1007070616 pedro -9223372035847705192 -XNUMX) Marcel

La mayoría de los generadores de números aleatorios son, de hecho, "pseudoaleatorios". Son Generadores Lineales Congruenciales, o LCGs (http://en.wikipedia.org/wiki/Linear_congruential_generator)

Los LCG son bastante predecibles dada una semilla fija. Básicamente, use una semilla que le dé su primera letra, luego escriba una aplicación que continúe generando el siguiente int (char) hasta que presione la siguiente letra en su cadena de destino y escriba cuántas veces tuvo que invocar el LCG. Continúe hasta que haya generado todas y cada una de las letras.

respondido 04 mar '13, 10:03

Como los subprocesos múltiples son muy fáciles con Java, aquí hay una variante que busca una semilla usando todos los núcleos 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();
    }
  }
}

Respondido 05 Oct 13, 00:10

Para un novato de Java como yo, debe agregar el sufijo del número de salida con L y cambiar el tipo de argumento a long, Es decir, randomString(long i) para jugar. :) - 林果 皞

Random siempre devuelve la misma secuencia. Se utiliza para barajar matrices y otras operaciones como permutaciones.

Para obtener diferentes secuencias, es necesario inicializar la secuencia en alguna posición, llamada "semilla".

RandomSting obtiene el número aleatorio en la posición i (semilla = -229985452) de la secuencia "aleatoria". Luego usa el ASCII código para los siguientes 27 caracteres en la secuencia después de la posición inicial hasta que este valor sea igual a 0. Esto devuelve el "hola". La misma operación se realiza para "mundo".

Creo que el código no funcionó para ninguna otra palabra. El tipo que programó eso conoce muy bien la secuencia aleatoria.

¡Es un código geek muy bueno!

respondido 30 mar '13, 05:03

Dudo que "conozca muy bien la secuencia aleatoria". Lo más probable es que haya probado miles de millones de posibles semillas hasta encontrar una que funcionara. - dan04

@ dan04 Los verdaderos programadores no solo usan el PRNG, recuerdan todo el período de memoria y enumeran los valores según sea necesario. - Thomas

"Random siempre devuelve la misma secuencia": coloque () después de Random o muéstrelo como un código. En caso contrario la oración es falsa. - gangnus

El principal es que la clase aleatoria construida con la misma semilla generará el mismo patrón de números cada vez.

contestado el 10 de mayo de 15 a las 05:05

Derivado de Denis TulskyLa respuesta de este método genera la semilla.

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

contestado el 23 de mayo de 17 a las 12:05

De los documentos de Java, esta es una característica intencional cuando se especifica un valor semilla para la clase Random.

Si se crean dos instancias de Random con la misma semilla y se realiza la misma secuencia de llamadas de método para cada una, generarán y devolverán secuencias idénticas de números. Para garantizar esta propiedad, se especifican algoritmos particulares para la clase Random. Las implementaciones de Java deben usar todos los algoritmos que se muestran aquí para la clase Random, en aras de la portabilidad absoluta del código Java.

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

Sin embargo, es extraño pensar que hay problemas de seguridad implícitos en tener números 'aleatorios' predecibles.

respondido 06 mar '13, 10:03

Es por eso que el constructor por defecto de Random "establece la semilla del generador de números aleatorios en un valor muy probablemente distinto de cualquier otra invocación de este constructor" (javadoc). En la implementación actual, esta es una combinación de la hora actual y un contador. - martín

Por cierto. Presumiblemente, hay casos prácticos de uso para especificar el valor semilla inicial, entonces. Supongo que ese es el principio operativo de esos llaveros pseudoaleatorios que puedes conseguir (¿los RSA?) - escritura02392

@deed02392 Por supuesto, existen casos prácticos de uso para especificar un valor inicial. Si está simulando datos para usar algún tipo de enfoque Monte Carlo para resolver un problema, entonces es bueno poder reproducir sus resultados. Establecer una semilla inicial es la forma más fácil de hacerlo. - dason

Se trata de "semilla". Las mismas semillas dan el mismo resultado.

Respondido 31 Jul 13, 02:07

Aquí hay una mejora menor para Denis Tulskiy https://www.youtube.com/watch?v=xB-eutXNUMXJtA&feature=youtu.be. Reduce el tiempo a la mitad

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

contestado el 23 de mayo de 17 a las 13:05

Se trata de la entrada semilla. La misma semilla da los mismos resultados todo el tiempo. Incluso si vuelve a ejecutar su programa una y otra vez, es el mismo resultado.

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

}

Salida

-755142161
-1073255141
-369383326
1592674620
-1524828502
------------
-755142161
-1073255141
-369383326
1592674620
-1524828502

Respondido 28 Jul 19, 14:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.