var a = Observable.Range(0, 10);
var b = Observable.Range(5, 10);
var zip = a.Zip(b, (x, y) => x + \"-\" + y);
zip.Subscribe(Conso
I honestly can't think of a solution based on existing operators that works for hot sources of unknown order (that is, xs before ys
vs ys before xs
). Your solution seems fine (hey, if it works), but I'd make a few changes if it were my code:
MutableDisposable
and CompositeDisposable
OnError
for exceptions thrown from the selector (making it more consistant with other operators)The code below has been tested with your dual-Range input, the same inputs flipped, as well as with Empty
:
public static IObservable MergeJoin(
IObservable left, IObservable right, Func selector)
{
return Observable.CreateWithDisposable(o =>
{
Queue a = new Queue();
Queue b = new Queue();
object gate = new object();
bool leftComplete = false;
bool rightComplete = false;
MutableDisposable leftSubscription = new MutableDisposable();
MutableDisposable rightSubscription = new MutableDisposable();
Action tryDequeue = () =>
{
lock (gate)
{
while (a.Count != 0 && b.Count != 0)
{
if (a.Peek() == b.Peek())
{
string value = null;
try
{
value = selector(a.Dequeue(), b.Dequeue());
}
catch (Exception ex)
{
o.OnError(ex);
return;
}
o.OnNext(value);
}
else if (a.Peek() < b.Peek())
{
a.Dequeue();
}
else
{
b.Dequeue();
}
}
}
};
leftSubscription.Disposable = left.Subscribe(x =>
{
lock (gate)
{
if (a.Count == 0 || a.Peek() < x)
a.Enqueue(x);
tryDequeue();
if (rightComplete && b.Count == 0)
{
o.OnCompleted();
}
}
}, () =>
{
leftComplete = true;
if (a.Count == 0 || rightComplete)
{
o.OnCompleted();
}
});
rightSubscription.Disposable = right.Subscribe(x =>
{
lock (gate)
{
if (b.Count == 0 || b.Peek() < x)
b.Enqueue(x);
tryDequeue();
if (rightComplete && b.Count == 0)
{
o.OnCompleted();
}
}
}, () =>
{
rightComplete = true;
if (b.Count == 0 || leftComplete)
{
o.OnCompleted();
}
});
return new CompositeDisposable(leftSubscription, rightSubscription);
});
}