Nas
Nas

Reputation: 1121

Lazily map, filter, reduce, etc. in JavaScript

Array#map, Array#filter create a new array and hence effectively iterating over the array (or creating new array).

Whereas in rust, python, java, c#, etc. such expression chain will iterate only once.

In most cases this is irrelevant and we do not have to care about that. However in some cases the performance hit could be a deal breaker to leverage the function api of the Array class.

How do you mitigate this? So you have any preference on a library enabling lazy evaluation for functional expression?

Upvotes: 6

Views: 2658

Answers (5)

trincot
trincot

Reputation: 350137

As of ECMAScript 2025 you can use iterator helpers.

Example:

const res =  Array(100000000)
             .keys()                  // 0, 1, 2, 3, 4, 5, 6, 7, ...
             .map(x => 3*x)           // 0, 3, 6, 9, 12, 15, 18, 21, ...
             .filter(x => x % 5 == 1) // 6, 21, 36, 51, 66, ...
             .take(4)                 // 6, 21, 36, 51
             .reduce((a, b) => a + b) // Sum: 114
             
console.log(res);

This has the lazy behaviour: even though it looks like it, there is at no time an array with 100000000 elements. The initial array is sparse, and just has a length property and no entries. It is also the only array in this expression. The keys method returns an iterator, which only gets consumed indirectly and lazily by the chain of iterator functions. It is the final reduce call (which is also an iterator method) that initiates the consumption of values from the chained iterators (the "pipe"). As take(4) limits the need for values, only a limited number of values are consumed from the keys() iterator and those that follow it, so that this expression finishes quickly with a result.

Upvotes: 2

ohrlando
ohrlando

Reputation: 136

You can install stream-list lib from npm

https://www.npmjs.com/package/stream-list

let myNumbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 let mylist = new List(myNumbers);
 myList
  .filter((num) => num % 2 == 0)  //only pair
  .map((num) => num * num)  // square
  .map((num) => num/2) // half
  .toList(); // myNumbers was just itered once

Upvotes: 0

F. Gouveia
F. Gouveia

Reputation: 183

I believe what you are looking for is processing the array as a stream. You can do that with highland:

import _ from "highland";
_([1, 2, 3, 4])
    .filter(v => v % 2 === 0)
    .map(v => v * 2)
    .toArray((result: number[]) => {
        // result is the new array
    });

Relevant part from the docs:

Arrays - Streams created from Arrays will emit each value of the Array (...)

Upvotes: 1

Andrew Gillis
Andrew Gillis

Reputation: 3885

If you don't want it to iterate more than once. You can use a loop

const numbers = [1,2,3,4,5,6]
let result = 0;
for(const number of numbers) {
  const square = number * number
  if(square % 2) {
    result += square
  }
}
console.log(result)

Or reduce

const numbers = [1,2,3,4,5,6]
const result = numbers.reduce((acc, number) => {
  const square = number * number
  if(square % 2) {
    return acc + square
  }
  return acc
}, 0)
console.log(result)

Array methods aren't functional so the whole premise is flawed. The fact that they exist on the array object means they aren't open composition the way pure functions are. You can get closer though

const square = (n) => n * n
const oddNumberOrZero = (n) => n % 2 ? n : 0
const add = (a, b) => a + b
const addOddSquare = (a, b) => add(a, oddNumberOrZero(square(b)))
const reduce = (arr, fn, acc) => arr.reduce(fn,acc)
const numbers = [1,2,3,4,5,6]

const result = reduce(numbers, addOddSquare, 0)

console.log(result)

You also seem to be conflating fluent interfaces with functional programming.

Upvotes: 2

Reinis
Reinis

Reputation: 505

Please check the following code lines. In the code, in which v * v is divided 2 means that v is divided.

const numbers = [1,2,3,4,5,6];
const res = numbers.reduce((sum, v)=> sum + (v % 2? v * v: 0), 0);
console.log(res)

Upvotes: -2

Related Questions