我有一段代码生成所有可能的字符串,可以通过在给定字符串的字母之间放置空格来生成,代码使用递归来实现这一点。这是我的代码(它是我已经适应JavaScript的C++ source,它按预期工作):
var genStringsUtil = function (str,buf,i,j,n){
if(n == i){
buf[j] = " ";
console.log(buf.join(""));
return;
}
buf[j] = str[i];
genStringsUtil (str,buf,i+1,j+1,n);
buf[j] = " ";
buf[j+1] = str[i];
genStringsUtil (str,buf,i+1,j+2,n);
}
var genStrings = function(s){
var str = s;
var n =str.length;
var buf = [];
buf[0] = str[0];
genStringsUtil (str,buf,1,1,n);
};
function main(){
genStrings("ABCDE");
}
main();
现在,我已经像这样修改它仍然有效:
var genStrings = function (str,buf,i,j,n){
if(n == i){
buf[j] = " ";
console.log(buf.join(""));
return;
}
buf[j] = str[i];
genStrings (str,buf,i+1,j+1,n);
buf[j] = " ";
buf[j+1] = str[i];
genStrings (str,buf,i+1,j+2,n);
}
!function(s){
var str = s;
var n =str.length;
var buf = [];
buf[0] = str[0];
genStrings (str,buf,1,1,n);
}("ABCDE");
但是,当我将最后一部分更改为(带括号的IIFE)时:
(function(s){
var str = s;
var n =str.length;
var buf = [];
buf[0] = str[0];
genStrings (str,buf,1,1,n);
})("ABCDE");
我收到一个错误:
TypeError:j未定义
如果我把后面的括号(“ABCDE”)放在后面:
(function(s){
var str = s;
var n =str.length;
var buf = [];
buf[0] = str[0];
genStrings (str,buf,1,1,n);
}("ABCDE"));
我收到另一个错误:
TypeError:genStrings不是一个函数
我一直认为IIFE声明了!或括号是相同的,但显然不是。所以我的问题基本上是在这三种情况下发生了什么不同?递归问题是什么?
我希望我的信息不会太久。
谢谢您的帮助。
C ++有一些JavaScript没有的限制。有许多方法可以在JavaScript中对函数进行编码,但这里有一个使用continuation-passing style的方法。
在这种风格中,第二个参数send
被添加到你的genStrings
函数的签名中,并接受默认的identity
延续。这有效地将return
变成了用户可配置的功能。使用名称send
是因为return
是保留关键字。
此实现的另一个值得注意的方面是使用referential transparency,这是一种功能样式的属性,其中函数总是为相同的输入返回相同的结果。由于递归是一种功能性遗产,我们一定会保持这种特性以产生最佳结果。
const identity = x =>
x
const concat = (xs, ys) =>
xs .concat (ys)
const genStrings = ([ char, ...rest ], send = identity) =>
// base case: return empty set
char === undefined
? send ([])
// if there is only one char, return singleton result
: rest.length === 0
? send ([char])
// otherwise recur on rest
// 1) add char plus space to each combination
// 2) add char without space to each combination
// 3) concat the result
: genStrings
( rest
, combs =>
send ( concat ( combs .map (c => char + ' ' + c)
, combs .map (c => char + c)
)
)
)
console.log (genStrings ('ABC'))
// [ 'A B C'
// , 'A BC'
// , 'AB C'
// , 'ABC'
// ]
您还会注意到,不需要跟踪其他几个状态变量,如n
,i
和j
,也不需要使用不同的值来增加它们。使用较少表达式和变量的程序更易于维护且更易于调试。
genStrings
也可以使用更大的输入,就像你问题中的例子一样
console.log (genStrings ('ABCDE'))
// [ 'A B C D E'
// , 'A B C DE'
// , 'A B CD E'
// , 'A B CDE'
// , 'A BC D E'
// , 'A BC DE'
// , 'A BCD E'
// , 'A BCDE'
// , 'AB C D E'
// , 'AB C DE'
// , 'AB CD E'
// , 'AB CDE'
// , 'ABC D E'
// , 'ABC DE'
// , 'ABCD E'
// , 'ABCDE'
// ]
我们也小心地使genStrings
成为total program,这意味着它返回一个有效的结果,即使输入字符串是空的
console.log (genStrings (''))
// []
因为genStrings
是使用延续传递样式定义的,所以我们也可以在调用站点指定用户可配置的延续
genStrings ('ABC', console.log)
// [ 'A B C', 'A BC', 'AB C', 'ABC' ]
genStrings ('ABC', combs => combs.length)
// 4
genStrings ('ABC', combs => combs .join (', '))
// 'A B C, A BC, AB C, ABC'
由于genStrings
是一个具有良好定义的域(输入)和codomain(输出)的纯函数,因此不需要立即调用的函数表达式(IIFE),更不用说需要调试一个。
我开始研究它,但我无法理解什么是:rest.length === 0和genStrings之前的冒号语法?
?:
是JavaScript的conditional operator,也称为三元运算符。语法是conditionExpression ? trueExpression : falseExpression
。当conditionExpression
评估为truthy值时,仅评估trueExpression
,并跳过falseExpression
。相反,如果conditionExpression
评估为非真实值,则跳过trueExpression
并且仅评估falseExpression
。
它是if
-else
语句的表达等价物,但它不依赖于副作用,而是像所有其他表达式一样评估值。
// conditional expression
let someValue =
n === 0 // expression
? "n is zero" // expression
: "n is not zero" // expression
// if statement
let someValue
if (n === 0)
someValue = "n is zero" // side effect
else
someValue = "n is not zero" // side effect
在上面,if
语句更冗长,并依赖于副作用来设置someValue
的值。条件表达式求值为一个值,可以直接赋值给变量。
与if
-else if
-else
语句类似,条件表达式也可以链接在一起。这是您在上面的答案中看到的语法。
const animalSound =
animal === "dog" // if
? "woof" // then
: animal === "cat" // else if
? "meow" // then
: "unknown" // else
它帮助我看到以各种方式表示的相同程序。下面,我们使用命令式if
语句重写原始答案
const identity = x =>
x
const concat = (xs, ys) =>
xs .concat (ys)
const genStrings = ([ char, ...rest ], send = identity) =>
{ if (char === undefined)
return send ([])
else if (rest.length === 0)
return send ([char])
else
return genStrings
( rest
, combs =>
send ( concat ( combs .map (c => char + ' ' + c)
, combs .map (c => char + c)
)
)
)
}
console.log (genStrings ('ABC'))
// [ 'A B C'
// , 'A BC'
// , 'AB C'
// , 'ABC'
// ]