-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Add Single.zip() for Iterable of Singles #3539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
*/ | ||
package rx; | ||
|
||
import java.util.Collection; | ||
import java.util.concurrent.Callable; | ||
import java.util.concurrent.Future; | ||
import java.util.concurrent.TimeUnit; | ||
|
@@ -32,6 +33,7 @@ | |
import rx.functions.Func7; | ||
import rx.functions.Func8; | ||
import rx.functions.Func9; | ||
import rx.functions.FuncN; | ||
import rx.annotations.Beta; | ||
import rx.internal.operators.*; | ||
import rx.internal.producers.SingleDelayedProducer; | ||
|
@@ -1196,6 +1198,30 @@ public final static <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> Single<R> zip(Single | |
return just(new Observable<?>[] { asObservable(o1), asObservable(o2), asObservable(o3), asObservable(o4), asObservable(o5), asObservable(o6), asObservable(o7), asObservable(o8), asObservable(o9) }).lift(new OperatorZip<R>(zipFunction)); | ||
} | ||
|
||
/** | ||
* Returns a Single that emits the result of specified combiner function applied to combination of | ||
* items emitted, in sequence, by an Iterable of other Singles. | ||
* <p> | ||
* {@code zip} applies this function in strict sequence. | ||
* <p> | ||
* <img width="640" height="380" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png" alt=""> | ||
* <dl> | ||
* <dt><b>Scheduler:</b></dt> | ||
* <dd>{@code zip} does not operate by default on a particular {@link Scheduler}.</dd> | ||
* </dl> | ||
* | ||
* @param singles | ||
* an Iterable of source Singles | ||
* @param zipFunction | ||
* a function that, when applied to an item emitted by each of the source Singles, results in | ||
* an item that will be emitted by the resulting Single | ||
* @return a Single that emits the zipped results | ||
* @see <a href="http://reactivex.io/documentation/operators/zip.html">ReactiveX operators documentation: Zip</a> | ||
*/ | ||
public static <R> Single<R> zip(Iterable<? extends Single<?>> singles, FuncN<? extends R> zipFunction) { | ||
return SingleOperatorZip.zip(iterableToArray(singles), zipFunction); | ||
} | ||
|
||
/** | ||
* Returns an Observable that emits the item emitted by the source Single, then the item emitted by the | ||
* specified Single. | ||
|
@@ -1264,7 +1290,7 @@ public final <R> Observable<R> flatMapObservable(Func1<? super T, ? extends Obse | |
* <dt><b>Scheduler:</b></dt> | ||
* <dd>{@code map} does not operate by default on a particular {@link Scheduler}.</dd> | ||
* </dl> | ||
* | ||
* | ||
* @param func | ||
* a function to apply to the item emitted by the Single | ||
* @return a Single that emits the item from the source Single, transformed by the specified function | ||
|
@@ -2031,4 +2057,46 @@ public final Single<T> doOnUnsubscribe(final Action0 action) { | |
public final Single<T> doAfterTerminate(Action0 action) { | ||
return lift(new OperatorDoAfterTerminate<T>(action)); | ||
} | ||
|
||
/** | ||
* FOR INTERNAL USE ONLY. | ||
* <p> | ||
* Converts {@link Iterable} of {@link Single} to array of {@link Single}. | ||
* | ||
* @param singlesIterable | ||
* non null iterable of {@link Single}. | ||
* @return array of {@link Single} with same length as passed iterable. | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
static <T> Single<? extends T>[] iterableToArray(final Iterable<? extends Single<? extends T>> singlesIterable) { | ||
final Single<? extends T>[] singlesArray; | ||
int count; | ||
|
||
if (singlesIterable instanceof Collection) { | ||
Collection<? extends Single<? extends T>> list = (Collection<? extends Single<? extends T>>) singlesIterable; | ||
count = list.size(); | ||
singlesArray = list.toArray(new Single[count]); | ||
} else { | ||
Single<? extends T>[] tempArray = new Single[8]; // Magic number used just to reduce number of allocations. | ||
count = 0; | ||
for (Single<? extends T> s : singlesIterable) { | ||
if (count == tempArray.length) { | ||
Single<? extends T>[] sb = new Single[count + (count >> 2)]; | ||
System.arraycopy(tempArray, 0, sb, 0, count); | ||
tempArray = sb; | ||
} | ||
tempArray[count] = s; | ||
count++; | ||
} | ||
|
||
if (tempArray.length == count) { | ||
singlesArray = tempArray; | ||
} else { | ||
singlesArray = new Single[count]; | ||
System.arraycopy(tempArray, 0, singlesArray, 0, count); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This copy can be avoided with my inlined version. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope! It's a bugfix. Otherwise, you can have an array with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is why mine run up to count and not array length. If you factor it out, you can't return both the array and the count. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, got it, so it was my bug when I split your code into two parts, sorry. I can return If you afraid about performance — we can add more general check |
||
} | ||
} | ||
|
||
return singlesArray; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package rx.internal.operators; | ||
|
||
import rx.Single; | ||
import rx.SingleSubscriber; | ||
import rx.exceptions.Exceptions; | ||
import rx.functions.FuncN; | ||
import rx.plugins.RxJavaPlugins; | ||
import rx.subscriptions.CompositeSubscription; | ||
|
||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
public class SingleOperatorZip { | ||
|
||
public static <T, R> Single<R> zip(final Single<? extends T>[] singles, final FuncN<? extends R> zipper) { | ||
return Single.create(new Single.OnSubscribe<R>() { | ||
@Override | ||
public void call(final SingleSubscriber<? super R> subscriber) { | ||
final AtomicInteger wip = new AtomicInteger(singles.length); | ||
final AtomicBoolean once = new AtomicBoolean(); | ||
final Object[] values = new Object[singles.length]; | ||
|
||
CompositeSubscription compositeSubscription = new CompositeSubscription(); | ||
subscriber.add(compositeSubscription); | ||
|
||
for (int i = 0; i < singles.length; i++) { | ||
if (compositeSubscription.isUnsubscribed() || once.get()) { | ||
break; | ||
} | ||
|
||
final int j = i; | ||
SingleSubscriber<T> singleSubscriber = new SingleSubscriber<T>() { | ||
@Override | ||
public void onSuccess(T value) { | ||
values[j] = value; | ||
if (wip.decrementAndGet() == 0) { | ||
R r; | ||
|
||
try { | ||
r = zipper.call(values); | ||
} catch (Throwable e) { | ||
Exceptions.throwIfFatal(e); | ||
onError(e); | ||
return; | ||
} | ||
|
||
subscriber.onSuccess(r); | ||
} | ||
} | ||
|
||
@Override | ||
public void onError(Throwable error) { | ||
if (once.compareAndSet(false, true)) { | ||
subscriber.onError(error); | ||
} else { | ||
RxJavaPlugins.getInstance().getErrorHandler().handleError(error); | ||
} | ||
} | ||
}; | ||
|
||
compositeSubscription.add(singleSubscriber); | ||
|
||
if (compositeSubscription.isUnsubscribed() || once.get()) { | ||
break; | ||
} | ||
|
||
singles[i].subscribe(singleSubscriber); | ||
} | ||
} | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Iterables and Observables are mainly for deferred data streaming. This will iterate at assembly time instead on Subscription time, losing the duality aspects. For example, an Observable source turned into an Iterable via toBlocking().getIterable() is no longer lazy when this zip is applied.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is compatible with the behavior of
Observable.zip
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep currently Observable serializes all values in the iterable into an array before zipping them so you can't append after lifting. IMO this is probably intentional since appending to some iterables while iterating results in ConcurrentModificationExceptions. Today the
Observable<Observable<T>>
overload can receive new observables to zip but the zip function won't be called until the outer observable callsonCompleted()
. This is slightly later than the Iterable overload.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, @akarnokd, @stealthcode let's use current implementation from this PR to be consistent with
Observable.zip(Iterable<Observable>)
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay.