以下打印语句将打印“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();
}
for (int n = 0; ; n++)
中 n
的用途。他们可以改用 for(;;)
或 while(true)
!
fixedAndNotSoRandomString
或其他东西......
其他答案解释了原因,但这里是如何。
给定 Random
的一个实例:
Random r = new Random(-229985452)
r.nextInt(27)
生成的前 6 个数字是:
8
5
12
12
15
0
给定 Random r = new Random(-147909649)
,r.nextInt(27)
生成的前 6 个数字是:
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
当使用特定种子参数(在本例中为 -229985452
或 -147909649
)构造 java.util.Random
的实例时,它会以该种子值开始遵循随机数生成算法。
使用相同种子构造的每个 Random
每次都会生成相同的数字模式。
"hello\0"
和"world\0"
各有6个元素。如果您假设一个真正的随机生成器,那么获得您正在寻找的序列的几率是 27^6 (387,420,489) 分之一——所以它非常令人印象深刻,但并不令人兴奋!
"Hello"
"World"
) 再试一次,或者使用 122-k
而不是 96+k
,或者...
我就把它留在这里。谁有很多(CPU)时间空闲,请随意尝试:)另外,如果您掌握了一些 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
nextInt(27)
表示在 [0, 26]
范围内。
这里的每个人都很好地解释了代码是如何工作的,并展示了如何构建自己的示例,但这里有一个信息理论答案,说明了为什么我们可以合理地期望存在暴力搜索最终会找到的解决方案。
26 个不同的小写字母组成了我们的字母表 Σ
。为了允许生成不同长度的单词,我们进一步添加了一个终止符 ⊥
来产生一个扩展的字母表 Σ' := Σ ∪ {⊥}
。
令 α
为符号,X 为 Σ'
上的均匀分布随机变量。获得该符号 P(X = α)
及其信息内容 I(α)
的概率由下式给出:
P(X = α) = 1/|Σ'| = 1/27 I(α) = -log₂[P(X = α)] = -log₂(1/27) = log₂(27)
对于单词 ω ∈ Σ*
及其 ⊥-
终止的对应物 ω' := ω · ⊥ ∈ (Σ')*
,我们有
I(ω) := I(ω') = |ω'| * log₂(27) = (|ω| + 1) * log₂(27)
由于伪随机数生成器 (PRNG) 是使用 32 位种子初始化的,因此我们可以预期大多数字的长度可达
λ = floor[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;
}
}
我写了一个快速程序来找到这些种子:
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);
}
}
}
}
}
我现在让它在后台运行,但它已经为经典的 pangram 找到了足够的单词:
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);
}
}
}
附言。 -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)
给定固定种子,LCG 是相当可预测的。基本上,使用一个给你第一个字母的种子,然后编写一个继续生成下一个 int (char) 的应用程序,直到你点击目标字符串中的下一个字母并写下你必须调用 LCG 的次数。继续,直到你生成了每一个字母。
由于 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();
}
}
}
L
并将参数类型更改为long
,即randomString(long i)
才能玩。 :)
随机总是返回相同的序列。它用于将数组和其他操作作为排列进行混洗。
要获得不同的序列,有必要在某个位置初始化序列,称为“种子”。
randomSting 获取“随机”序列的第 i 个位置(种子 = -229985452)的随机数。然后将 ASCII 代码用于种子位置之后序列中的下一个 27 个字符,直到该值等于 0。这将返回“hello”。对“世界”进行相同的操作。
我认为该代码不适用于其他任何单词。编程的人非常了解随机序列。
这是非常棒的极客代码!
原则是使用相同种子构造的随机类每次都会生成相同的数字模式。
源自 Denis Tulskiy 的答案,此方法生成种子。
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 :/");
}
在 Java 文档中,这是为 Random 类指定种子值时有意使用的功能。
如果使用相同的种子创建 Random 的两个实例,并且为每个实例进行相同的方法调用序列,它们将生成并返回相同的数字序列。为了保证这一特性,为类 Random 指定了特定的算法。为了 Java 代码的绝对可移植性,Java 实现必须使用此处为类 Random 显示的所有算法。
http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Random.html
奇怪的是,您会认为拥有可预测的“随机”数字存在隐含的安全问题。
Random
的默认构造函数“将随机数生成器的种子设置为一个很可能与此构造函数的任何其他调用不同的值”(javadoc)。在当前实现中,这是当前时间和计数器的组合。
这是关于“种子”的。相同的种子产生相同的结果。
这是 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 :/");
}
这都是关于输入种子的。相同的种子始终给出相同的结果。即使您一次又一次地重新运行程序,它的输出也是相同的。
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
new Random(-229985452).nextInt(27)
总是返回 8。new Random()
根本不返回数字。Random
不是加密安全的(我很确定它是 Mersenne Twister,但不要引用我的话),有可能从“我想要这些数字”倒退到“这是我要使用的种子”。我用标准的 C 线性同余生成器做了类似的事情。