1

I have a class that has two functions. Add, which adds a function to an array, and Execute which executes the array of function against its argument. My code is:

class LazyEvaluation {
  constructor(){
    this.functionQueue = []
  }

  add(fn){
    this.functionQueue.push(fn)
    return this
  }

  evaluate(target){
    for (let i = 0; i < this.functionQueue.length; i++){
      let newArray = target.map(this.functionQueue[i])
      return newArray
    }
  }
}

As it stands, this code works when i only have one function in the array. My problem is as soon as there is more than one function in the array the execute function creates a new array for each function.

For example, when the functionQueue array has the following two functions:

(function timesTwo(a){ return a * 2 })
(function addOne(a) { return a + 1 })

And the execute function is given [1, 2, 3]

The output I need is [3, 5, 7] however I am getting two separate outputs of [2, 4, 6] and [2, 3, 4]

How do I ensure that the execute function doesn't create a new array for each function in the functionQueue?

LewMoore
  • 69
  • 1
  • 10

2 Answers2

2

You're returning too early in your for loop. You actually have to reduce your functions passing along the values from target:

class LazyEvaluation {
  constructor(){
    this.functionQueue = []
  }

  add(fn){
    this.functionQueue.push(fn)
    return this
  }

  evaluate(target){
    return target.map(x => 
      this.functionQueue.reduce((y, f) => f(y), x)
    );
  }
}

const sums = new LazyEvaluation();
sums.add(x => x * 2);
sums.add(x => x + 1);

console.log(
  sums.evaluate([1,2,3])
);
user3297291
  • 22,592
  • 4
  • 29
  • 45
  • That won't produce the desired output. – T.J. Crowder Jun 06 '18 at 09:29
  • @T.J.Crowder Ah, read a bit too fast there and jumped on the first "mistake" I saw in the code. Fixed it now! Thanks for the heads up – user3297291 Jun 06 '18 at 09:32
  • That's still not the output the OP said they want. – T.J. Crowder Jun 06 '18 at 09:33
  • Because I chose different example data... It's not about reproducing the exact example outcome, it's about figuring out the logic, right? I expect the OP to read my code snippet and see it meets the requirement, not to *only* check the outcome of the console.log. Do you agree that's a valid approach? – user3297291 Jun 06 '18 at 09:34
  • Thanks, this worked! - just for the sake of understanding, would it be possible to explain why this worked? So, the results from the map of target is passed in to the reduce? – LewMoore Jun 06 '18 at 09:35
  • *"Because I chose different example data"* No, I ignored the fourth value. The first three were wrong, too. Looking at your edit, though, the reason was the order of the functions you added to the queue. Latest version works. – T.J. Crowder Jun 06 '18 at 09:36
  • Fun fact to the OP, if you want to switch between `compose` and `pipe` behavior, you can use `reduceRight`. @T.J. Crowder: I appreciate your first comment when I made a legit mistake, for the second one I'd expect you to look further than just "it doesn't log `[3, 5, 7]`". Thanks! – user3297291 Jun 06 '18 at 09:37
1

For example, when the functionQueue array has the following two functions:

(function timesTwo(a){ return a * 2 })
(function addOne(a) { return a + 1 })

And the execute function is given [1, 2, 3]

The output I need is [3, 5, 7] however I am getting two separate outputs of [2, 4, 6] and [2, 3, 4]

You need to feed the result of the first function into the second, etc.:

evaluate(target){
  return target.map(value => {
      for (const f of this.functionQueue) {
          value = f(value);
      }
      return value;
  });
}

Live Example:

class LazyEvaluation {
  constructor(){
    this.functionQueue = []
  }

  add(fn){
    this.functionQueue.push(fn)
    return this
  }

  evaluate(target){
    return target.map(value => {
        for (const f of this.functionQueue) {
            value = f(value);
        }
        return value;
    });
  }
}

const l = new LazyEvaluation();
l.add(function timesTwo(a){ return a * 2 });
l.add(function addOne(a) { return a + 1 });
console.log(l.evaluate([1, 2, 3]));
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Is `const f of this.functionQueue` valid? `const` shouldn't be a variable that never going to be assigned again? – Michael Jun 06 '18 at 09:36
  • @Michael - Yes, it's valid -- did you notice the runnable example? :-) The semantics of `const` and `let` "variables" declared in loops are different from the behavior of `var` variables in loops: A *different* variable/constant is created for each loop iteration. `for-of` and `for-in` never try to modify the value of that variable, so you can use a constant. `for` does modify it, so you have to use `let` in that case (even though, again, it's a different variable each time). – T.J. Crowder Jun 06 '18 at 09:40
  • @Michael - The new behavior is hugely useful in addressing the common [closures in loops problem](http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example). – T.J. Crowder Jun 06 '18 at 09:41
  • I noticed the runnable example, and even run it in my chrome console to be sure :) I know about `let f of this.functionQueue` but `const j...` sounds illogical for me. – Michael Jun 06 '18 at 11:45
  • 1
    @Michael - :-) It's logical if you dig deep enough, it's really a different variable/constant each time. `for-of` is syntactic sugar for acquiring an iterator, calling its `next` method to get a result object, and then assigning a variable/constant to the result's `value` property; then repeating the `next` call the next time. Here's a long-winded example: http://jsfiddle.net/x098rj7L/ – T.J. Crowder Jun 06 '18 at 12:55