简体   繁体   中英

Tail recursive reduce function returns […, [Curcular] ]

Trying to Write a reduce function that would filter out any duplicates. I know there are other ways to solve this, but I'm trying to practice recursive functions.

 function addToSet(a, b) { a.add(b); return a; } let set = new Set; function reduce([head, ...last], fn, init) { if (head === undefined) return init; return fn(fn(init, head), reduce(last, fn, init)) } const a = reduce([1, 2, 4, 6, 4, 3, 1, 2, 5, 1, 3, 4, 5, 7, 7], addToSet, set) console.log(a) // in node this returns // Set { 1, 2, 4, 6, 3, 5, 7, [Circular] } 

I read that circular means that object is self-referencing? but I am not sure I fully understand what that means in the context of a Set. Why am I having this issue, and how would I solve it? Thanks so much for your time guys!

A good way to think about this is to just look at the return value of addToSet . It returns the passed in set…every time. Now look at the return value of of reduce . It returns the result of fn which we just established always returns the set.

So the you pass the result of reduce into the second argument of fn at some point you will be passing the set into the second argument fn which will add the set to the set and give you a circular ref.

This:

 return fn(fn(init, head), reduce(last, fn, init))

eventually becomes:

 return fn(init, init)

It's not hard to solve, because there's no real reason to pass call the function twice. Your base case will return the set in the end, so you can just call fn once and return the result of reduce .

 function addToSet(a, b) { a.add(b); } let set = new Set; function reduce([head, ...last], fn, init) { if (head === undefined) return init fn(init, head) return reduce(last, fn, init) } const a = reduce([1, 2, 4, 6, 4, 3, 1, 2, 5, 1, 3, 4, 5, 7, 7], addToSet, set) console.log([...a]) // spreading because sets don't print here 

To figure out what's going on here, we can place a console log inside of your recursive function and run it with a small set like so:

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)

We get this output (remember that the last line is what returns from the initial call at the end)

在此处输入图片说明

As you can see, you call your recursive function one last time on the empty array and return init there, which gets added to the end of your set. You probably want to catch that by amending your base case. I'll leave that as an exercise for you to figure out, but if you need more help, you can always comment back.

One more thought:

Consider that recursion is like saying that one run of the function is going to be responsible for one action/calculation/step/however you want to think about it. Ask yourself what that one step is.

Ex:

If I am one function call, maybe I only want to be responsible for the question "do I add the current head to init ?"

There are loads of ways to do this, but maybe one way would be to say (in pseudocode):

reduce([head, ...last], fn, init) {
  is_base_case (where head is undefined)?
    return // do nothing -- we don't want undefined to be in the set
  otherwise
    attempt to add head to init
  reduce(...) // call the next reduce fn -- responsible for the next head
  return init // init should either have the original set or the set + head
}

This doesn't account undefined actually being a value in the array, but hopefully it illustrates the concept.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM