Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegant way to get index of filter or first with RX Java

I'm just practicing RX Java and wanted to get the position in an array for items which match a filter. I can't see any obvious way to do it. I was looking at maybe zipping a range and iterable observable or something but it was quickly getting way more verbose and complicated than for loops.

like image 537
Nathan Schwermann Avatar asked May 09 '15 18:05

Nathan Schwermann


3 Answers

There is not one correct way to do this. Samuel's answer can be one. It also depends the bigger picture on what is you array and how you need to handle the results in the subscriber.

Here is a (java7) example where the Observer emits a ArrayList<String>:

strings.map(new Func1<ArrayList<String>, ArrayList<Integer>>() {
            @Override
            public ArrayList<Integer> call(ArrayList<String> strings) {
                ArrayList<Integer> list = new ArrayList<>();
                for(String string: strings){
                    if(statement){
                        list.add(strings.indexOf(string));
                    }
                }
                return list;
            }
        }).subscribe(new Action1<ArrayList<Integer>>() {
            @Override
            public void call(ArrayList<Integer> integers) {
                doSomethingWithThePositions(integers);
            }
        });

And here is an example where the Observer emits a String per time from your ArrayList:

final ArrayList<String> strings1 = new ArrayList<>();
Observable.from(strings1)
        .filter(new Func1<String, Boolean>() {
            @Override
            public Boolean call(String s) {
                return !s.isEmpty();
            }
        })
        .subscribe(new Action1<String>() {
            @Override
            public void call(String s) {
                int i = strings1.indexOf(s);
                doSomethingWithThePosition(i);
            }
        });
like image 93
Diolor Avatar answered Nov 06 '22 21:11

Diolor


There used to be mapWithIndex and zipWithIndex operators in RxJava, but they were removed, see here why.

So you have to write some library boilerplate once:

class Indexed<T> {
    final int index;
    final T value;
    public Indexed(T value, int index) {
        this.index = index;
        this.value = value;
    }
    @Override
    public String toString() {
        return index + ") " + value;
    }
}

Iterable<Integer> naturals = IntStream.iterate(0, i -> i + 1)::iterator;

But then, you can get it reasonably concise:

Observable<String> obs = Observable.just("zero", "one", "two", "three");
obs.zipWith(naturals, (s, i) -> new Indexed<String>(s, i))
   .filter(e -> e.value.length() > 4)
   .subscribe(System.out::println);
like image 19
Samuel Gruetter Avatar answered Nov 06 '22 23:11

Samuel Gruetter


I like Samuel's answer. I've written a Transformer implementation of his stuff which is Java 6/7 compliant and uses a Long index (I have long streams to deal with!).

You call it like this:

Observable.just("a", "b").compose(MapWithIndex.<String>instance());

Here's the class (source and unit tests here):

package com.github.davidmoten.util;

import java.util.Iterator;

import rx.Observable;
import rx.Observable.Transformer;
import rx.functions.Func2;

import com.github.davidmoten.util.MapWithIndex.Indexed;

public final class MapWithIndex<T> implements Transformer<T, Indexed<T>> {

    private static class Holder {
        static final MapWithIndex<?> INSTANCE = new MapWithIndex<Object>();
    }

    @SuppressWarnings("unchecked")
    public static <T> MapWithIndex<T> instance() {
        return (MapWithIndex<T>) Holder.INSTANCE;
    }

    @Override
    public Observable<Indexed<T>> call(Observable<T> source) {

        return source.zipWith(NaturalNumbers.instance(), new Func2<T, Long, Indexed<T>>() {

            @Override
            public Indexed<T> call(T t, Long n) {
                return new Indexed<T>(t, n);
            }
        });
    }

    public static class Indexed<T> {
        private final long index;
        private final T value;

        public Indexed(T value, long index) {
            this.index = index;
            this.value = value;
        }

        @Override
        public String toString() {
            return index + "->" + value;
        }

        public long index() {
            return index;
        }

        public T value() {
            return value;
        }

    }

    private static final class NaturalNumbers implements Iterable<Long> {

        private static class Holder {
            static final NaturalNumbers INSTANCE = new NaturalNumbers();
        }

        static NaturalNumbers instance() {
            return Holder.INSTANCE;
        }

        @Override
        public Iterator<Long> iterator() {
            return new Iterator<Long>() {

                private long n = 0;

                @Override
                public boolean hasNext() {
                    return true;
                }

                @Override
                public Long next() {
                    return n++;
                }

                @Override
                public void remove() {
                    throw new RuntimeException("not supported");
                }
            };
        }

    }

}
like image 3
Dave Moten Avatar answered Nov 06 '22 21:11

Dave Moten