从IIFE内部进行递归

时间:2018-09-04 10:16:05

标签: javascript recursion iife

我有一段代码生成所有可能的字符串,这些字符串可以通过在给定字符串的字母之间放置空格来生成,该代码使用递归来实现。这是我的代码(这是我适应了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声明为!或括号是同一回事,但显然不是。所以我的问题是,这三种情况基本上是怎么回事?递归是问题吗?

希望我的信息不要太长。

谢谢您的帮助。

1 个答案:

答案 0 :(得分:0)

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'
// ]

您还将注意到,无需跟踪nij等其他几个状态变量,也无需使用不同的值递增它们。使用更少的表达式和变量的程序更易于维护和调试。

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是一个具有明确定义的域(输入)和共域(输出)的纯函数,因此不需要立即调用的函数表达式(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'
// ]