m59
m59

Reputation: 43785

Unexpected index values when chaining filter functions

The following code produces the result I desire:

// output 1,2,3,4,5,etc
var input$ = Rx.Observable.interval(500).map((v, idx) => idx+1);

var inputEveryOtherOdd$ = input$
  // filter even numbers
  .filter(v => v % 2)
  .map(x => x) // workaround to fix the index (read below)
  // filter every other remaining number (take every other odd number)
  .filter((v, idx) => {
    console.log(`Value: ${v}, Index: ${idx}`)
    return !(idx % 2);
  })
  .subscribe(function(v) {
    output.textContent+= v;
  })
;

The log produces:

Value: 1, Index: 0
Value: 3, Index: 1
Value: 5, Index: 2
Value: 7, Index: 3
Value: 9, Index: 4

Each item that passes the first filter has the next index so that the index is incremented by one for each item (0,1,2,3,4,5,etc).

What I can't understand is if I remove map, the second filter receives different idx values for the same items:

Value: 1, Index: 0
Value: 3, Index: 2
Value: 5, Index: 4
Value: 7, Index: 6
Value: 9, Index: 8

It seems that the values filtered in the first filter are still being considered in the second filter. I can't make any sense of it. The filter function doesn't run for the values, so how can the index be incrementing for items that don't exist? Why does map make any difference?

Click here for live demo.

I expect chaining two filters together would produce the same result as synchronous filtering and the idx value would be 0,1,2,3,4,5,etc in each filter.

var result = [1,2,3,4,5,6,7,8,9]
  .filter(v => v % 2)
  .filter((v, idx) => !(idx % 2));

I was originally using startWith in place of map. It seems like just putting something in between the filters makes the idx value what I expect.

Upvotes: 0

Views: 89

Answers (2)

paulpdaniels
paulpdaniels

Reputation: 18663

The problem is ironically a "feature" of the current version (v4) of RxJS.

The filter operator contains an optimization to detect if it is being used in a "chained" manner, that is, if you have multiple filters in a row. If it is then it will not create a second FilterObservable but rather will wrap the new filter operation in as part of the existing Observable.

See source code here and here.

As a result, no matter how many filters you chain together you will always receive the same index for all of them.

When you use an operator in between the two filters then it cannot perform the optimization and so the filters will not be merged and you will see different indices.

Upvotes: 1

foxdonut
foxdonut

Reputation: 7947

The indexidx is the index of the element in the original list, independently of what has been filtered out.

Upvotes: 0

Related Questions