Elegant way to get index of filter or first with RX Java
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"); } }; } } }
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);
}
});
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);