【问题标题】:Recursion from inside IIFE从 IIFE 内部递归
【发布时间】:2018-09-04 10:16:04
【问题描述】:

我有一段代码可以通过在给定字符串的字母之间放置空格来生成所有可能的字符串,该代码使用递归来实现这一点。这是我的代码(它是C++ source,我已经适应了 JavaScript,它按预期工作):

    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 用 !或括号是同一件事,但显然不是。所以我的问题基本上是在这三种情况下发生了什么不同?递归是问题吗?

希望我的信息不要太长。

感谢您的帮助。

【问题讨论】:

  • 你忘记了分号,所以genStrings 因为你的括号而被立即调用。 VTC'ing 错字。考虑使用 linter
  • 谢谢。我没有看到我在哪里缺少分号?根据右括号的位置,我会收到两条不同的错误消息。如果它在参数 ("ABCDE") 之前,即使我在 ("ABCDE")之前添加分号,我也会得到 j is undefined >。对于第二种情况,我看不到我缺少分号的地方。
  • 整个genStrings函数后面需要一个分号,否则它会被立即调用,因为function() { }后面紧跟着(...)(解释为调用)
  • 对不起,我在验证我的答案时才意识到你的意思。现在可以了。感谢您的帮助。最后一个问题,你所说的 VTC'ing as typo 是什么意思
  • VTC = 投票结束。 (错字问题与 SO 无关,应关闭)

标签: javascript recursion iife


【解决方案1】:

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

【讨论】:

  • 感谢您的回复。我开始研究它,但我不明白 rest.length === 0genStrings 之前的 : 冒号语法是什么?
  • 不客气。我更新了答案以回复您的评论。
  • 谢谢!我知道三元运算符,但我不知道您可以通过链接它们来实现类似 else if 的结构。太棒了:)
猜你喜欢
  • 2017-01-03
  • 2015-07-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-23
  • 2021-04-24
  • 2012-10-08
相关资源
最近更新 更多