Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I perform operations in JavaScript just like we do pipeline of operations in Java streams?

In Java 8 using streams when I chain methods one after the another the execution of operations are performed in pipelined manner.

Example:

List<Integer> nums = Arrays.asList(1,2,3,4,5,6);    
nums.stream().map(x->{
    x = x * x;
    System.out.println("map1="+x);
    return x;
}).map(x->{
    x = x * 3;
    System.out.println("map2="+x);
    return x;
}).forEach(x-> System.out.println("forEach="+x));

Output:-

map1=1
map2=3
forEach=3
map1=4
map2=12
forEach=12
map1=9
map2=27
forEach=27
map1=16
map2=48
forEach=48
map1=25
map2=75
forEach=75
map1=36
map2=108
forEach=108

But when I tried similarly in javascript.The result is different.As in javascript first operation gets completed and then second operation is performed.Example:-

var nums = [1,2,3,4,5,6 ];
nums.map(x => {
  x = (x * x);
  console.log('map1='+x);
  return x;})
  .map(x => {
  x = x * 3;
  console.log('map2='+x);
  return x;})
  .forEach(x=> console.log('forEach='+x));

Output:-

 map1=1
 map1=4
 map1=9
 map1=16
 map1=25
 map1=36
 map2=3
 map2=12
 map2=27
 map2=48
 map2=75
 map2=108
 forEach=3
 forEach=12
 forEach=27
 forEach=48
 forEach=75
 forEach=108

Is there any way in JavaScript to make it performs operations in a pipeline manner, and I get output as in the Java program?

This question ask only how to collect like in JavaScript but not how internal working changes for same type of methods.

like image 436
Jaspreet Jolly Avatar asked Feb 07 '19 07:02

Jaspreet Jolly


People also ask

Which method can be used to create a stream from similar type of data?

Create stream from an array:of() and Arrays. stream() are two commonly used methods for creating a sequential stream from a specified array. Both these methods returns a Stream when called with a non-primitive type T.

Which of the following methods are terminal operations applied on streams?

The Stream API gives you two terminal operations to find an element: findFirst() and findAny() . These two methods do not take any argument and return a single element of your stream. To properly handle the case of empty streams, this element is wrapped in an optional object.

When performing operations on a stream it will affect the original stream?

A stream can be composed of multiple functions that create a pipeline that data that flows through. This data cannot be mutated. That is to say the original data structure doesn't change. However the data can be transformed and later stored in another data structure or perhaps consumed by another operation.


4 Answers

Maybe later (or never) you can use the actual experimental pipeline operator |>, which has the following syntax:

expression |> function

Your wanted result could be achieved by taking the functions as separate functions and iterate the stream array for each pipe.

This works only in FF. From version 58: this feature is behind the --enable-pipeline-operator compile flag.

const
    a = x => { x = x * x; console.log("map1=" + x); return x; },
    b = x => { x = x * 3; console.log("map2=" + x); return x; },
    c = x => console.log("forEach=" + x)

var nums = [1, 2, 3, 4, 5, 6];

nums.forEach(v => v |> a |> b |> c);

The same with a pipe as function (function composition enabling piping) with a closure over the wanted functions.

const
    pipe = (...functions) => input => functions.reduce((acc, fn) => fn(acc), input),
    a = x => { x = x * x; console.log("map1=" + x); return x; },
    b = x => { x = x * 3; console.log("map2=" + x); return x; },
    c = x => console.log("forEach=" + x)

var nums = [1, 2, 3, 4, 5, 6],
    pipeline = pipe(a, b, c);

nums.forEach(pipeline);
like image 102
Nina Scholz Avatar answered Oct 11 '22 18:10

Nina Scholz


If you put each function operation into an array, you can iterate over that array with reduce and pass the last calculated value along in the accumulator until the end of the function array is reached:

var nums = [1,2,3,4,5,6 ];
var fns = [
  (x) => {
    x = x * x;
    console.log('map1=' + x);
    return x;
  },
  (x) => {
    x *= 3;
    console.log('map2=' + x);
    return x;
  },
  (x) => {
    console.log(x);
    return x;
  }
];

nums.forEach((num) => {
  fns.reduce((lastResult, fn) => fn(lastResult), num);
  // pass "num" as the initial value for "lastResult",
  // before the first function has been called
});

You can't use nums.map because .map will necessarily iterate through the whole input array before resolving to the mapped output array (after which the mapped output array will then have another .map called on it).

like image 30
CertainPerformance Avatar answered Oct 11 '22 17:10

CertainPerformance


The equivalent to Java's streams are JavaScript's iterators. Iterator objects unfortunately don't have a map method (yet), but you can easily write one yourself (and even install it on the prototype if you want method syntax).

function* map(iterable, f) {
    for (var x of iterable)
        yield f(x);
}

var nums = [1,2,3,4,5,6];
function square(x) {
  x = (x * x);
  console.log('map1='+x);
  return x;
}
function triple(x) {
  x = x * 3;
  console.log('map2='+x);
  return x;
}
for (const x of map(map(nums.values(), square), triple)) {
  console.log('forEach='+x);
}

Also notice that in functional programming, order doesn't matter for pure operations - you shouldn't need to rely on the execution order if you are using map.

like image 27
Bergi Avatar answered Oct 11 '22 18:10

Bergi


Why re-invent from scratch when we have solutions. This functionality is present in lodash/RxJS/stream.js.

Example snippet from lodash:

_.flow(
 _.assign(rows[0]),
 _.omit('blah')
)(foundUser);

// >> {"charData":[],"ok": 1}

However, javascript runs on single thread and so do these libraries. Java streams benefit from multi core systems(in case of parallel). There they can use multiple threads to use all available cores.

like image 34
YetAnotherBot Avatar answered Oct 11 '22 18:10

YetAnotherBot