Javascript 尾部递归reduce函数返回[…,[Curcular]]

Javascript 尾部递归reduce函数返回[…,[Curcular]],javascript,node.js,function,recursion,circular-reference,Javascript,Node.js,Function,Recursion,Circular Reference,尝试编写一个reduce函数来过滤掉任何重复项。我知道还有其他方法可以解决这个问题,但我正在尝试使用递归函数 功能添加设置(a、b){ a、 添加(b); 返回a; } 设集合=新集合; 函数reduce([head,…last],fn,init){ if(head==未定义)返回init; 返回fn(fn(初始,头部),减少(最后,fn,初始)) } 常数a=减少([1,2,4,6,4,3,1,2,5,1,3,4,5,7],addToSet,set) 控制台日志(a) //在这个节点中,返回

尝试编写一个reduce函数来过滤掉任何重复项。我知道还有其他方法可以解决这个问题,但我正在尝试使用递归函数

功能添加设置(a、b){
a、 添加(b);
返回a;
}
设集合=新集合;
函数reduce([head,…last],fn,init){
if(head==未定义)返回init;
返回fn(fn(初始,头部),减少(最后,fn,初始))
}
常数a=减少([1,2,4,6,4,3,1,2,5,1,3,4,5,7],addToSet,set)
控制台日志(a)

//在这个节点中,返回//Set{1,2,4,6,3,5,7,[Circular]}
为了弄清楚这里发生了什么,我们可以在递归函数中放置一个控制台日志,并使用一个小集合运行它,如下所示:

function addToSet(a, b) {
  a.add(b);
  return a;
}

let set = new Set;

function reduce([head, ...last], fn, init) {
  console.log("head", head)
  console.log("last", last)
  console.log("init", init)
  if (head === undefined) return init;
  return fn(fn(init, head), reduce(last, fn, init))
}
const a = reduce([2, 4, 4], addToSet, set)

console.log(a)
我们得到这个输出(记住,最后一行是从最后的初始调用返回的)

如您所见,最后一次在空数组上调用递归函数并返回init,它被添加到集合的末尾。你可能想通过修改你的基本情况来抓住这一点。我将把它作为一个练习留给你去弄清楚,但是如果你需要更多的帮助,你可以随时回复

还有一个想法:

考虑递归就像是说函数的一次运行将负责一个操作/计算/步骤/不管您想怎么想。问问自己这一步是什么

例:

如果我是一个函数调用,可能我只想回答以下问题:“我是否将当前
头添加到
init
?”

有很多方法可以做到这一点,但可能有一种方法可以说(在伪代码中):


这并不说明未定义的值实际上是数组中的一个值,但希望它能说明这一概念。

考虑这一点的好方法是只查看
addToSet
的返回值。它每次都返回传入的集合。现在查看of
reduce
的返回值。它返回刚刚建立的
fn
的结果,并始终返回集合

因此,您将
reduce
的结果传递到
fn
的第二个参数,在某个点上,您将把集合传递到第二个参数
fn
,这将把集合添加到集合中,并给您一个循环引用

这:

最终成为:

 return fn(init, init)
这不难解决,因为没有真正的理由将函数调用传递两次。您的基本案例最终将返回集合,因此您只需调用
fn
一次,然后返回
reduce
的结果

功能添加设置(a、b){
a、 添加(b);
}
设集合=新集合;
函数reduce([head,…last],fn,init){
if(head==未定义)返回init
fn(初始,头部)
返回减少(最后,fn,初始)
}
常数a=减少([1,2,4,6,4,3,1,2,5,1,3,4,5,7],addToSet,set)
console.log([…a])//因为这里不打印集合

非常感谢,这完全有道理!
 return fn(fn(init, head), reduce(last, fn, init))
 return fn(init, init)