0

I am building an algebra calculator and I'm working on a recursive function to filter like terms from a polynomial. The function below works in that it produces the desired array of arrays of like terms. I can verify this by adding a console.log statement to the function. However, for some reason, the function won't return the output. It returns "undefined".

My thinking is that the chain of recursive calls should terminate with the end condition indicated below, and then pass the returned argument[1] array through the stack.

I've read similar questions on here where the person forgets to put a return statement in one or more places. However, in my code, I have a return statement with the end condition and with the recursive function call. It's probably something simple I'm missing.

var filterLikeTerms = function (terms) { //takes an array of terms, optional second argument is an array of arrays of similar terms
  if (!arguments[1]) arguments[1] = []; //Initilizes the second argument if none is given
  if (terms.length == 0) return arguments[1]; //End condition
  arguments[1].push(terms.filter(term => terms[0].toString() === term.toString())); //Adds similar terms to the 2nd argument array
  terms = terms.filter (term => terms[0].toString() !== term.toString()); //shortens the terms array to exclude the like terms filtered above
  return filterLikeTerms(terms, arguments[1]); //recursive function call
}
Avi C
  • 297
  • 1
  • 3
  • 7
  • post what you are calling `filterLikeTerms` with? You're calling `.toString` on a property that has to be passed in as a string. You can't pass in `7x + 1` as a value. It would already be a string. Second note, it seems like the code is trying to be clever. Take a step back, make it simple to walk through and then optimize later. – VtoCorleone Dec 31 '17 at 04:02
  • Maybe I dont understand the question but when I put `console.log(filterLikeTerms(['A', 'B', 'C', 'D']))`, it prints array of arrays and not undefined.. – bigless Dec 31 '17 at 04:10
  • You can't pass parameter `arguments` to the method since it only accept one parameter: `terms`. I think you need to make the method to accept 2 parameters `filterLikeTerms(terms, arguments)`. An example of the process can make your questions more understandable, like the sample inputs and the expected outputs. – Muhammad Kholid B Dec 31 '17 at 04:26
  • 1
    @Eaton non-arrow functions support being called with a variable number of arguments, not necessarily declared as formal parameters, by accessing the [`arguments`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments) object made available within a function. Declaring a parameter named `arguments` is not recommended! – traktor Dec 31 '17 at 05:34

2 Answers2

0

Work smarter, not harder

Try not to hurt your head with unnecessary variables, assignments, or logical conditions – a simple recursive function with simple equality testing

const eq = x => y =>
  x === y

const neq = x => y =>
  x !== y

const filterLikeTerms = ([ x, ...xs ]) =>
  x === undefined
    ? []
    : [ xs.filter (eq (x)) ]
      .concat (filterLikeTerms (xs.filter (neq (x))))

const data =
  ['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'a', 'b']

console.log (filterLikeTerms (data))
// [ [ a, a, a ]
// , [ b, b ]
// , [ c ]
// , [ d ]
// ]

Flexible implementation

If you want to change how items are compared and grouped, changed eq and neq

// for example ...
const eq = x => y =>
  x.toString() === y.toString()

const neq = x => y =>
  x.toString() !== y.toString()

If you want to use the accumulator parameter like you did in your original code, that's fine too – this form can easily be made stack-safe for very large inputs

const filterLikeTerms = ([ x, ...xs ], acc = []) =>
  x === undefined
    ? acc
    : filterLikeTerms ( xs.filter (neq (x))
                      , acc.concat ([ xs.filter (eq (x)) ])
                      )

Work even smarterer

The proper tail call (immediately above) poses us to work with huge data inputs, but our function is very inefficient because runs multiple filters for each item in the list. We can make a dramatic improvement by utilizing a Map – now we only touch each item in the original input once

const filterLikeTerms = ([ x, ...xs ], acc = new Map) =>
  x === undefined
    ? Array.from (acc.values ())
    : acc.has (x)
      ? filterLikeTerms (xs, acc.set (x, [x].concat (acc.get (x))))
      : filterLikeTerms (xs, acc.set (x, [x]))

const data =
  ['a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'a', 'b']

console.log (filterLikeTerms (data))

Don't stop learning, yet

You can make all sorts of neat things on your own. Here's some things I wrote about that relate to this answer:

Mulan
  • 129,518
  • 31
  • 228
  • 259
0

In ES6:

const filterLikeTerms=terms=>[...(new Set(terms))]
Ryan Hanekamp
  • 558
  • 4
  • 6