1

In Javascript it is pretty easy to switch values: https://stackoverflow.com/a/4011851/1626443

I know that Observables are usually not meant to be used in that way but is it really that difficult to swap two elements using their index:

// assumption that index < newIndex
var values = [...];
var values$ = Rx.Observable.from(values);
var swapValues$ = values$
    .take(index - 1)
    .concat(
      values$
        .take(newIndex - 1)
        .skip(index + 1)
        .startWith(values[newIndex]), 
      values$
        .skip(newIndex + 1)
        .startWith(values[index])
    )

Am I missing an easier way?

Spurious
  • 1,903
  • 5
  • 27
  • 53
  • The values may arrive at different times, so to change the order of arrival there is no other way than cache the first value and emit it only after the second one has arrived. – artur grzesiak Sep 22 '17 at 11:27

3 Answers3

2

You have no choice but to create a state machine to delay emitting the earlier (lefthand) value. Rx is designed in many ways to avoid state with its immutable Observables, which is why swapping is so fundamentally awkward. By the same token, Subject, the mutable counterpart, is right for this job.

Minimally, there is one state variable: the lefthand element in the swap. You provide the predicate to identify the left- and righthand values. In your specific example, we need a counter to make the index comparison, which I'll anonymize in reduce:

(function() {
  let lefthand = null;
  const subj = new Rx.Subject();
  const d = values$.reduce((counter, value) => {
    if(counter === index) lefthand = value;
    else {
      subj.onNext(value);
      if(counter === newIndex) subj.onNext(lefthand);
    }
    return counter + 1;
  }, 0)
                   .catch(subj.onError)
                   .finally(subj.onComplete);
  return subj.asObservable();
})();

On a side note, I wanted to make sure that your example is just a toy program, since it seems silly to rely on the values array but not swap the values on the array first. Without that, you'll have to use four Observable chains: one for [0, index), one between (index, newIndex], one to emit index, and finally one for (newIndex, <end>]. Although using Subject seems no less verbose, it better expresses your intent in my opinion.

concat
  • 3,107
  • 16
  • 30
1

Your solution works only on immediate observable. In the general case of observables, where items are emitted over time, concatenating the observables won't work.

Let's say that the source observable is

var values$ = Observable.interval(1000); //.. 0 .. 1 .. 2 .. 3 .. 4 .. 5 ..

and, let's say we want to swap the items in indices 2 and 4, where the expected result is .. 0 .. 1 .. 4 .. 3 .. 2 .. 5 ..

var index = 2;
var newIndex = 4;

There is no choice but buffering all the items between index and newIndex (both included). Then, we can swap these values and emit them back as separated items.

To do that, we need a special handling for the series between index and newIndex. Here is a possible solution with the window operator

var windowFrames$ = values$.filter((_,i) => i === index-1 || i === newIndex); // 'filter' predicate second parameter is the index of the emitted items

2 indices as window signals: index - 1 and newIndex.

var swapValues$ = values$
    .window(windowFrames$)
    .switchMap((frame$, i) =>
        i === 1 ? // the frame index between index-1 and newIndex
            frame$.toArray().flatMap(x => Observable.from(swapFirstWithLast(x))) :
            frame$);

We divide the source observable values$ into three frames, each frame starts an observable of values, and completes when the next frame starts (switchMap behavior). For the second frame (where i === 1), we buffer the sequence into an array, swapping the first and the last values of this frame, then, emitting all back as separated values (using Observable.from).

A working example - here

ZahiC
  • 13,567
  • 3
  • 25
  • 27
0

It's pretty rare that you would just swap two values in an array without being part of some larger operation, for example, a sort will swap values in an array.

However, if you really wanted to, you could do something like...

function swap<T>(input: Observable<T[]>, indexOne: number, indexTwo: number): Observable<T[]> {
  return input.map(arr => {
    arr[indexOne] = arr.splice(indexTwo, 1, arr[indexOne])[0];
    return arr;
  });
}

My guess however, is that whatever larger operation you are doing, there is some better way to handle it without having to do individual swaps. For example, if you were going to do a sort, you wouldn't implement it as a series of swaps on observables. You would do it as:

function sort<T>(input: Observable<T[]>): Observable<T[]> {
  return input.map(arr => {
    arr.sort();
    return arr;
  });
}

The swaps still happen, but they happen inside the map function where you are dealing with arrays and not observables.

Pace
  • 41,875
  • 13
  • 113
  • 156