Почему этот код использует случайные строки для печати "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