为什么这个使用随机string的代码打印“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(); } 

当使用特定的种子参数(在本例中为-229985452-147909649 )构造java.util.Random的实例时,它遵循该种子值开始的随机数生成algorithm。

每个用相同的种子构build的Random数将每次生成相同的数字模式。

另一个答案解释了为什么,但这里是如何。

给定一个Random的实例:

 Random r = new Random(-229985452) 

r.nextInt(27)生成的前6个数字是:

 8 5 12 12 15 0 

r.nextInt(27)生成的前6个数字给定Random r = new Random(-147909649)为:

 23 15 18 12 4 0 

然后,只需将这些数字添加到字符` (它是96)的整数表示forms:

 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 

我会把它留在这里 如果你已经掌握了一些fork-join-fu来烧毁所有CPU核心(只是线程很无聊,对吗?),请分享一下你的代码。 我将不胜感激。

 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 

这里的每个人都很好地解释了代码是如何工作的,并展示了如何构build自己的例子,但是这里有一个信息理论答案,说明为什么我们可以合理地期望一个解决scheme存在,即powershellsearch将最终find。

26个不同的小写字母组成我们的字母表Σ 。 为了允许生成不同长度的单词,我们进一步添加一个结束符来产生一个扩展的字母表Σ' := Σ ∪ {⊥}

α是一个符号,X是Σ'的均匀分布的随机variables。 获得该符号的概率P(X = α)及其信息内容I(α)由下式给出:

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

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

对于一个词⊥- ω ∈ Σ*和它的⊥-终止对应ω' := ω · ⊥ ∈ (Σ')* ⊥- ω' := ω · ⊥ ∈ (Σ')* ,我们有

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

由于伪随机数发生器(PRNG)是用一个32位的种子初始化的,所以我们可以期望长度最多的单词

λ= floor [32 / log 2(27)] – 1 = 5

由至less一个种子产生。 即使我们要search一个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

我写了一个快速程序来find这些种子:

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

现在我已经在后台运行了,但是它已经find了一个经典的庞然大物的话:

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

( 关于ideone的演示 )

PS。 -727295876, -128911, -1611659, -235516779

我对此很感兴趣,我在字典单词列表上运行这个随机的单词生成器。 范围: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 

实际上,大多数随机数发生器是“伪随机的”。 他们是线性同余发电机,或LCG( http://en.wikipedia.org/wiki/Linear_congruential_generator

LCGs是固定的种子是相当可预测的。 基本上,使用一个种子,给你你的第一个字母,然后编写一个应用程序,继续生成下一个int(字符),直到你击中目标string中的下一个字母,并记下多less次,你不得不调用LCG。 继续,直到你生成每一个字母。

随机总是返回相同的序列。 它用于排列数组和其他操作。

要获得不同的序列,有必要在某个位置初始化序列,称为“种子”。

randomSting获得“random”序列的i位置(seed = -229985452)中的随机数。 然后在种子位置之后的序列中使用ASCII码作为下一个27个字符,直到这个值等于0.这将返回“hello”。 同样的操作是为“世界”完成的。

我认为这个代码不适用于其他的单词。 编程的那个人非常熟悉随机序列。

这是非常伟大的怪胎代码!

由于Java的multithreading非常简单,下面是使用所有可用内核search种子的变体: 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(); } } } 

来自丹尼斯·图尔斯基的回答,这种方法产生了种子。

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

主体是用相同的种子构build的随机类将每次生成相同的数字模式。

从Java文档中,为Random类指定种子值时,这是一个有意向的function。

如果使用相同的种子创build了Random的两个实例,并为每个实例调用相同的方法调用序列,则它们将生成并返回相同的数字序列。 为了保证这个属性,特定的algorithm是为Random类指定的。 为了Java代码的绝对可移植性,Java实现必须使用此处显示的所有algorithmRandom。

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

奇怪的是,你会认为有可预见的“随机”数字隐含的安全问题。

这是关于“种子”。 相同的种子给出相同的结果。

Denis Tulskiy的回答是一个小小的改进。 它削减了一半的时间

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