问题描述
我目前正在制作一个程序,其中包含许多使用 Math.rand() 的函数。我正在尝试使用给定的关键字(在本例中为车床)生成一个字符串。我希望程序记录一个带有“车床”(或它的任何版本,有大写或没有大写)的字符串,但是我尝试过的所有操作都达到了程序的调用堆栈大小限制(我完全理解为什么,我想要程序生成一个带有单词的字符串,而不会达到其调用堆栈大小)。
我尝试过的:
function generateStringWithKeyword(randNum: number) {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMnopQRSTUVWXYZ0123456789+/";
let result = "";
for(let i = 0; i < randNum; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
if(result.includes("lathe")) {
continue;
} else {
generateStringWithKeyword(randNum);
}
}
console.log(result);
}
这就是我现在所拥有的,在对 stackoverflow 进行了简短的研究后,我了解到添加 if/else 块可能会更好,而不是使用
if(!result.includes("lathe")) return generateStringWithKeyword(randNum);
但两种方式我都达到了调用堆栈大小限制。
解决方法
你的算法的“正确”版本,写成一个迭代函数而不是一个递归函数,以免超过堆栈深度,看起来像这样:
function generateStringWithKeyword(randNum: number) {
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/";
let result = "";
let attemptCnt = 0;
while (!result.toLowerCase().includes("lathe")) {
attemptCnt++;
result = "";
for (let i = 0; i < randNum; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
if (attemptCnt > 1e6) {
console.log("I GIVE UP");
return;
}
}
console.log(result);
return result;
}
我不喜欢浏览器因为脚本无法完成而挂起,所以我在那里设置了最大尝试次数。一百万次机会似乎是合理的。当您尝试时,会发生这种情况:
generateStringWithKeyword(10); // I GIVE UP
这是有道理的;让我们进行粗略的粗略概率计算,看看我们预计需要多长时间。在某些情况下,"lathe"
出现在单词位置 1 的几率是 (2/64)×(2/64)×(2×64)×(2/64)×(2/64) ( “L”或“l”首先出现,然后是“A”或“a”等),大约为 3×10-8。对于长度为 10 的单词,"lathe"
可以出现在位置 1、2、3、4、5 或 6 处。虽然这不完全正确,但让我们将其视为将获得的机会乘以 6这个词在某处,所以获得有效结果的实际机会大约是 1.8×10-7。因此,我们可以预期您需要大约 1 ÷ 1.8×10-7 = 560 万次成功机会。
哦,该死,我只给了它一百万。让我们增加到 1000 万,然后再试一次:
generateStringWithKeyword(10); // "lATHELEYSc"
太好了!虽然,它有时还是会放弃。事实上,一个需要数百万次尝试才能成功的算法是非常非常低效的。您可能想了解 bogosort,这是一种排序算法,它通过随机打乱事物并检查它们是否已排序来工作,并且它会不断尝试直到成功为止。它用于教育目的,以强调此类技术的实际表现如何不够实用。没有人会想真正使用这样的算法。
那么您将如何以“正确”的方式做到这一点?好吧,我的建议是第一次正确构建您的结果。如果您有 10 个字符并且其中 5 个在某些情况下需要是 "lathe"
,那么您将需要 5 个真正随机的字符。所以随机决定 "lathe"
之前应该有多少个字母。例如,如果您选择 2,则输入 2 个随机字符,加上随机大小写中的 "lathe"
,再加上 3 个随机字符。
它可能是这样的,我主要使用与您相同的 for
-loops 和 +=
字符串连接样式:
function generateStringWithKeyword(randNum: number) {
const keyword = "lathe";
if (randNum < keyword.length) throw new Error(
"This is not possible; \"" + keyword + "\" doesn't fit in " + randNum + " characters"
);
const actuallyRandNum = randNum - keyword.length;
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/";
let result = "";
const kwInsertionPoint = Math.floor(Math.random() * (actuallyRandNum + 1));
for (let i = 0; i < kwInsertionPoint; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
for (let i = 0; i < keyword.length; i++) {
result += Math.random() < 0.5 ? keyword[i].toLowerCase() : keyword[i].toUpperCase();
}
for (let i = kwInsertionPoint; i < actuallyRandNum; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
如果你运行这个,你会发现它非常高效,并且永不放弃:
console.log(Array.from({ length: 4 },() => generateStringWithKeyword(5)).join(" "));
// "lathE LaThe lATHe LatHe"
console.log(Array.from({ length: 4 },() => generateStringWithKeyword(7)).join(" "));
// "p6lAtHe laThE01 nlaTheK lATHeRJ"
console.log(Array.from({ length: 4 },() => generateStringWithKeyword(10)).join(" "));
// "giMqzLaTHe 5klAthegBo oVdLatHe0q twNlATheCr"