java - with - Por que esse código usando strings aleatórias imprime “hello world”?




randomstringutils (10)

A seguinte declaração de impressão imprimiria "hello world". Alguém poderia explicar isso?

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

E randomString() é assim:

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

É sobre "semente". As mesmas sementes dão o mesmo resultado.


A maioria dos geradores de números aleatórios são, na verdade, "pseudo-aleatórios". Eles são Geradores Congruenciados Lineares ou LCGs ( http://en.wikipedia.org/wiki/Linear_congruential_generator )

LCGs são bastante previsíveis, dada uma semente fixa. Basicamente, use uma semente que lhe dê sua primeira letra, depois escreva um aplicativo que continue a gerar o próximo int (char) até que você atinja a próxima letra em sua string de destino e anote quantas vezes você precisou invocar o LCG. Continue até que você tenha gerado cada letra.


Aleatório sempre retorna a mesma seqüência. É usado para baralhar arrays e outras operações como permutações.

Para obter sequências diferentes, é necessário inicializar a sequência em alguma posição, chamada "seed".

O randomSting obtém o número aleatório na posição i (seed = -229985452) da sequência "aleatória". Em seguida, usa o código ASCII para o próximo caractere 27 na sequência após a posição da semente até que esse valor seja igual a 0. Isso retorna o "olá". A mesma operação é feita para "mundo".

Eu acho que o código não funcionou para outras palavras. O cara que programou que conhece muito bem a sequência aleatória.

É um ótimo código geek!


Aqui está uma pequena melhoria para a answer Denis Tulskiy. Corta o tempo pela metade

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

Como o multi-threading é muito fácil com o Java, aqui está uma variante que procura por uma semente usando todos os núcleos disponíveis: 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();
    }
  }
}

Derivado da resposta de Denis Tulskiy , este método gera a semente.

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

Eu vou deixar aqui. Quem tem muito tempo (CPU) de sobra, sinta-se livre para experimentar :) Além disso, se você tiver dominado alguns fork-join-fu para fazer essa coisa queimar todos os núcleos de CPU (apenas threads são chatos, certo?), Por favor compartilhe seu código. Eu apreciaria muito.

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

Saída:

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

Fiquei intrigado com isso, eu corri este gerador de palavras aleatórias em uma lista de palavras do dicionário. Intervalo: Integer.MIN_VALUE para Integer.MAX_VALUE

Eu tenho 15131 acessos.

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

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

Impressões

the quick browny fox jumps over a lazy dog 

Quando uma instância de java.util.Random é construída com um parâmetro de semente específico (neste caso, -229985452 ou -147909649 ), segue o algoritmo de geração de número aleatório que começa com esse valor de semente.

Cada Random construído com a mesma semente gerará o mesmo padrão de números todas as vezes.


Todos aqui fizeram um ótimo trabalho explicando como o código funciona e mostrando como você pode construir seus próprios exemplos, mas aqui está uma resposta teórica de informações mostrando por que podemos razoavelmente esperar que exista uma solução que a busca por força bruta acabará encontrando.

As 26 letras minúsculas diferentes formam o nosso alfabeto Σ . Para permitir a geração de palavras de comprimentos diferentes, adicionamos ainda um símbolo terminador para produzir um alfabeto estendido Σ' := Σ ∪ {⊥} .

Seja α um símbolo e X uma variável aleatória distribuída uniformemente sobre Σ' . A probabilidade de obter esse símbolo, P(X = α) , e seu conteúdo informacional, I(α) , são dados por:

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

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

Para uma palavra ω ∈ Σ* e sua contraparte terminada em ω' := ω · ⊥ ∈ (Σ')* , temos

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

Como o gerador de números pseudo-aleatórios (PRNG) é inicializado com uma semente de 32 bits, podemos esperar a maioria das palavras de comprimento até

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

para ser gerado por pelo menos uma semente. Mesmo se fôssemos procurar por uma palavra de 6 caracteres, ainda teríamos sucesso em 41,06% do tempo. Não é muito pobre.

Por 7 letras estamos olhando mais perto de 1,52%, mas eu não tinha percebido isso antes de tentar:

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

Veja a saída: http://ideone.com/JRGb3l





random