Почему этот код использует случайные строки для печати "hello world"?

Следующий оператор печати напечатает "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();
}

Ответ 1

Когда экземпляр java.util.Random создается с определенным параметром семени (в этом случае -229985452 или -147909649), он следует алгоритму генерации случайных чисел, начиная с этого начального значения.

Каждый Random, построенный с одним и тем же семенем, каждый раз генерирует одинаковый шаблон чисел.

Ответ 2

Другие ответы объясняют, почему, но вот как.

Для экземпляра Random:

Random r = new Random(-229985452)

Первые 6 чисел, которые r.nextInt(27) порождают:

8
5
12
12
15
0

а первые 6 чисел, которые r.nextInt(27) генерируют заданные 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

Ответ 3

Я просто оставлю это здесь. Тот, у кого есть много (CPU) времени, чтобы избавиться, не стесняйтесь экспериментировать:) Кроме того, если вы освоили некоторый fork-join-fu, чтобы заставить эту вещь записывать все ядра процессора (просто потоки скучны, правильно?), Пожалуйста, поделитесь ваш код. Я был бы очень признателен.

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

Ответ 4

Все здесь проделали большую работу по разъяснению того, как работает код, и показывая, как вы можете создавать свои собственные примеры, но здесь есть теоретический ответ на информацию, объясняющий, почему мы можем разумно ожидать, что существует решение, которое в конечном итоге найдет поиск грубой силы.

26 различных строчных букв образуют наш алфавит Σ. Чтобы разрешить генерировать слова разной длины, добавим еще символ терминатора , чтобы получить расширенный алфавит Σ' := Σ ∪ {⊥}.

Пусть α - символ, а X - равномерно распределенная случайная величина над Σ'. Вероятность получения этого символа P(X = α) и его информационного содержимого I(α) дается следующим образом:

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

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

Для слова ω ∈ Σ* и его ⊥- завершенного аналога ω' := ω · ⊥ ∈ (Σ')* имеем

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

Поскольку генератор псевдослучайных чисел (PRNG) инициализируется 32-битным семенем, мы можем ожидать, что большинство слов длиной до

λ = пол [32/log₂ (27)] - 1 = 5

чтобы сгенерировать по меньшей мере одно семя. Даже если мы будем искать 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

Ответ 5

Я написал краткую программу для поиска этих семян:

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

(Демо на идеоне.)

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

Ответ 6

Я был заинтригован этим, я запустил этот генератор случайных слов в списке словарных слов. Диапазон: 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

Большинство генераторов случайных чисел являются, по сути, "псевдослучайными". Это линейные конгруэнтные генераторы или LCG (http://en.wikipedia.org/wiki/Linear_congruential_generator)

LCG вполне предсказуемы при фиксированном семени. В основном, используйте семя, которое дает вам первую букву, а затем напишите приложение, которое продолжает генерировать следующий int (char), пока вы не нажмете следующую букву в целевой строке и не запишите, сколько раз вам приходилось вызывать LCG, Продолжайте, пока не сгенерируете каждую букву.

Ответ 8

Поскольку многопоточность с 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();
    }
  }
}

Ответ 9

Случайные всегда возвращают одну и ту же последовательность. Он использовался для перетасовки массивов и других операций в качестве перестановок.

Чтобы получить разные последовательности, необходимо инициализировать последовательность в некоторой позиции, называемой "семя".

Случайное значение получает случайное число в позиции я (seed = -229985452) "случайной" последовательности. Затем используется ASCII код для следующего 27 символов в последовательности после позиции семени, пока это значение не станет равно 0. Это возвращает "привет". Та же операция выполняется для "мира".

Я думаю, что код не работал ни на какие другие слова. Парень, который запрограммировал, что очень хорошо знает случайную последовательность.

Это очень классный код!

Ответ 10

Принцип действия - это случайный класс, построенный с одним и тем же семенем, каждый раз генерирует одинаковый шаблон чисел.

Ответ 11

Получено из Денис Тульский, этот метод генерирует семя.

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

В документах Java это преднамеренная функция при задании начального значения для класса Random.

Если два экземпляра Random созданы с одним и тем же семенем, а для каждой из них выполняется одна и та же последовательность вызовов методов, они будут генерировать и возвращать идентичные последовательности чисел. Чтобы гарантировать это свойства, определенные алгоритмы заданы для класса Random. Реализации Java должны использовать все алгоритмы, показанные здесь для class Random, для абсолютной переносимости Java-кода.

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

Однако, вы бы подумали, что существуют неявные проблемы безопасности при наличии предсказуемых "случайных" чисел.

Ответ 13

Это о "семени". Те же самые семена дают тот же результат.

Ответ 14

Вот небольшое улучшение для Denis Tulskiy answer. Он сокращает время наполовину

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

Ответ 15

  Все дело во вводе семени. Одно и то же семя дает одинаковые результаты всем время. Даже если вы снова запустите свою программу и снова получите тот же вывод.

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