Connecting observables in RX.net - system.reactive

I'm trying to make a little pipeline with RX.net, but I'm having trouble figuring out how to do it without nesting the code for each process in the pipe, making the code very messy.
Here's basically what I want to do
inputString -> toUpperCase -> reverseString -> printOutput
Here's the code I got working, but if I want to add more stuff to the pipeline it's not going to be pretty.
var inputObservable = Observable.Return("hello world");
inputObservable.Subscribe(inputString =>
{
var toUpperCaseObservable = Observable.Return(inputString.ToUpper());
toUpperCaseObservable.Subscribe(toUpperCaseInput =>
{
var reverseStringObservable = Observable.Return(new String(toUpperCaseInput.Reverse().ToArray()));
reverseStringObservable.Subscribe(reverseStringInput =>
{
var writeOutputObservable = Observable.Return(reverseStringInput);
writeOutputObservable.Subscribe(input =>
{
Console.WriteLine(input);
Console.ReadLine();
});
});
});
});

You really don't want to create an Rx "pipeline" in this way. Nested subscribes like this are not really an Rx pipeline.
Here's how you would do what you did in a basic Rx query/subscribe approach:
var query =
from x in Observable.Return("hello world")
let y = x.ToUpper()
select new String(y.Reverse().ToArray());
query.Subscribe(z =>
{
Console.WriteLine(z);
Console.ReadLine();
});
Much simpler, but it may be that, for illustrative purposes, your operations may be expensive so then this might be a better approach:
var query =
from x in Observable.Return("hello world")
from y in Observable.Start(() => x.ToUpper())
from z in Observable.Start(() => new String(y.Reverse().ToArray()))
select z;
query.Subscribe(z =>
{
Console.WriteLine(z);
Console.ReadLine();
});
The general rule is that you should not subscribe until the end of your pipeline, at a point when you are ready to perform an action on the result of your pipeline. If you need to perform a computation then that should remain part of the query pipeline.

This will also work
var inputObservable = Observable.Return("hello world")
.Select(s=>s.ToUpper())
.Select(upperCased=>new String(upperCased.Reverse().ToArray());
inputObservable.Subscribe(str =>
{
Console.WriteLine(str);
Console.ReadLine();
});

Related

How to mock two same mongo collection with different results?

I have to mock the two aggregate pipeline methods which point to the same collection.
sample Code below
private void GetMethod()
{
var result1 = databaseContext.GetCollection<SampleCollection>("SampleCollection")
.Aggregate<SampleModel>(aggregateQuery, new AggregateOptions { AllowDiskUse = true }).ToList();
var result2 = databaseContext.GetCollection<SampleCollection>("SampleCollection")
.Aggregate<SampleModel>(aggregateQuery, new AggregateOptions { AllowDiskUse = true }).ToList();
}
Below is the code I try to mock the Collection.
List<SampleModel> models = new List<SampleModel>
{
new SampleModel
{
Quarter = 20141,
Type = "AA",
PaidAmount = 100
},
new SampleModel
{
Quarter = 20141,
Type = "BB",
PaidAmount = 200
}
};
var mockMongoCollection = new Mock<IMongoCollection<SampleCollection>>();
var mockCursor = new Mock<IAsyncCursor<SampleModel>>();
mockCursor.Setup(x => x.Current).Returns(models);
mockCursor.SetupSequence(x => x.MoveNext(It.IsAny<CancellationToken>())).Returns(true).Returns(false);
mockMongoCollection.SetupSequence(x => x.Aggregate(It.IsAny<PipelineDefinition<SampleCollection, SampleModel>>(), It.IsAny<AggregateOptions>(), It.IsAny<CancellationToken>()))
.Returns(mockCursor.Object);
this.mockDbContext.Setup(x => x.GetCollection<SampleCollection>(It.IsAny<string>()))
.Returns(mockMongoCollection.Object);
How to mock and set up the different results for each aggregate? . The mock code I tried only working for the first aggregate query.
The key here is to uniquely identify the two invocations for both the pipelines.
If you are certain which pipeline invocation in your test would execute first, you can make use of the SetupSequence to control the return values. You have already used in your MoveNext method mock setup. It wouldn't matter what the pipeline definitions looks like for both.
If that is not the case, then you would need to write conditions to uniquely identify the invocations.
For example:
mockMongoCollection.SetupSequence(c =>
c.Aggregate(
It.Is<PipelineDefinition<SampleCollection, SampleModel>>(x => x.Stages.Count() == 2),
It.IsAny<AggregateOptions>(),
It.IsAny<CancellationToken>()))
.Returns(mockCursor.Object);
The above example simply demonstrates to identify based on how many stages are defined in a pipeline. Say if there were 2 stages in the first pipeline and the second pipeline had 3 stages, then you can use this to setup your mock.
You can also make use of other properties that you can use to express the condition to determine the invocation correctly and setup correct return value.
Got it worked after initialize the individual cursor for each result.
var mockCursorfirstAggregate = new Mock<IAsyncCursor<SampleModel>>();
mockCursorfirstAggregate.Setup(_ => _.Current).Returns(expectedFirstAggregateResult);
mockCursorfirstAggregate
.SetupSequence(_ => _.MoveNext(It.IsAny<CancellationToken>()))
.Returns(true)
.Returns(false);
var mockCursorsecondAggregate = new Mock<IAsyncCursor<SampleModel>>();
mockCursorsecondAggregate.Setup(_ => _.Current).Returns(expectedSecondAggregateResult);
mockCursorsecondAggregate
.SetupSequence(_ => _.MoveNext(It.IsAny<CancellationToken>()))
.Returns(true)
.Returns(false);
this.mockDbContext.SetupSequence(x => x.GetCollection<SampleCollection>(It.IsAny<string>()).Aggregate(It.IsAny<PipelineDefinition<SampleCollection, SampleModel>>(), It.IsAny<AggregateOptions>(), It.IsAny<CancellationToken>()))
.Returns(mockCursorfirstAggregate.Object)
.Returns(mockCursorsecondAggregate.Object);

Efficiently combine many IObservable<bool> streams with boolean operators

I'm looking to combine many IObservable<bool> streams such that when the latest value for all of them is true, a true is emitted, and otherwise a false is emitted.
CombinedLast would allow me to build something like this for two streams easily, but a) I'm not sure the API easily allows thousands of streams to be combined and b) I'm not sure how efficient it would be even if it could.
All is kinda similar to what I want except I'm assuming that works over a single sequence and once false cannot dynamically changes back to true.
Also I need the values to be "distinct until changed", although the DistintUntilChanged operator may not be efficient for this?
I'm hoping for an O(1) algorithm.
A good approach for combining the latest is to start with a IObservable<IObservable<T>> and turn it in to a IObservable<T[]>. This becomes a very dynamic way to combine as many values you need.
Here's an extension method to do this:
public static IObservable<T[]> CombineLatest<T>(this IObservable<IObservable<T>> sources)
{
return
sources.Publish(ss =>
Observable.Create<T[]>(o =>
{
var composite = new CompositeDisposable();
var list = new List<T>();
composite.Add(
ss.Subscribe(source =>
{
var index = list.Count;
list.Add(default(T));
composite.Add(source.Subscribe(x => list[index] = x));
}));
composite.Add(ss.Merge().Select(x => list.ToArray()).Subscribe(o));
return composite;
}));
}
This nicely creates and tracks all subscriptions and uses a closure to define the index that each subscription needs to use to update its value in the list that is used for output.
If you use it like this:
var sources = new Subject<IObservable<bool>>();
var output = sources.CombineLatest();
output.Subscribe(x => Console.WriteLine(x));
var s1 = new Subject<bool>();
sources.OnNext(s1);
s1.OnNext(true);
var s2 = new Subject<bool>();
sources.OnNext(s2);
s2.OnNext(false);
var s3 = new Subject<bool>();
sources.OnNext(s3);
s3.OnNext(true);
s2.OnNext(true);
s1.OnNext(false);
Then you get this output:
If you change the definition of output to var output = sources.CombineLatest().Select(xs => xs.Aggregate((x, y) => x & y)); then you get the output that I think you're after:
True
False
False
True
False
I don't know how to do this in a classically functional way and still achieve O(1). This used mutable state, and is O(1) for observing each message, but O(n) for memory:
public IObservable<bool> CombineBooleans(this IObservable<bool>[] source)
{
return source.Select((o, i) => o.Select(b => (value: b, index: i)))
.Merge()
.Scan((array: new bool[source.Length], countFalse: source.Length), (state, item) =>
{
var countFalse = state.countFalse;
if (state.array[item.index] == item.value)
return (state.array, countFalse); //nothing to change, emit same state
else if (state.array[item.index]) //previous/current state is true, becoming false
{
countFalse++;
state.array[item.index] = false;
}
else //previous/current state is false, becoming true
{
countFalse--;
state.array[item.index] = true;
}
return (state.array, countFalse);
})
.Scan((countFalse: source.Length, oldCountFalse: source.Length), (state, item) => (countFalse: item.countFalse, oldCountFalse: state.countFalse))
.SelectMany(state =>
state.countFalse == 1 && state.oldCountFalse == 0
? Observable.Return(false)
: state.countFalse == 0 && state.oldCountFalse == 1
? Observable.Return(true)
: Observable.Empty<bool>()
)
.Publish()
.RefCount();
}
EDIT: Added .Publish().Refcount() to eliminate multiple-subscriber bugs.

How to filter one observable with another observable?

With https://github.com/neuecc/UniRx,
I have two observables A and B.
I want A to be filtered by B. Sample seems like what I want but the negative of it.
IObservable<long> A = Observable.EveryUpdate();
IObservable<Collider2D> B = this.OnTriggerEnter2DAsObservable()
.Where( x => x.gameObject.tag == "Wall");
I want some kind of Pseudo code like that:
A.filterBy(B)
.Subscribe(x => Debug.Log(x)); //executed only when B is not streaming
(Update1)
Here is actual code. I am trying to cancel out input stream with colliding stream.
var isCollidingWithWall = this.OnTriggerEnter2DAsObservable()
.Where(collider => collider.gameObject.tag == "Wall");
Func<long, float> displaceCalculate = (_) => this.Speed * Time.deltaTime;
var moveLeft = Observable.EveryUpdate()
.Where(_ => Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow));
var moveRight = Observable.EveryUpdate()
.Where(_ => Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow));
var movement1 = moveLeft
.Select(displaceCalculate)
.Select(f => -f);
var movement2 = moveRight
.Select(displaceCalculate);
movement2
.Merge(movement1)
.Subscribe(f =>
{
this.transform.position = new Vector2(this.transform.position.x + f, this.transform.position.y);
});
I think I might be going in wrong direction.
It is difficult to only combine operators.
The two streams are not synchronized.
When the OnNext message comes from stream B, how long shut off stream A?
Next stream B message? or Next stream A?
If you want to stop it only one frame, how about this?
void Start()
{
var isCollisionEntered = false;
this.OnCollisionEnter2DAsObservable()
.Where(x => x.gameObject.tag == "Wall")
.Subscribe(_ => isCollisionEntered = true);
this.LateUpdateAsObservable()
.Where(_ => isCollisionEntered)
.Subscribe(_ => isCollisionEntered = false);
this.UpdateAsObservable()
.Where(_ => !isCollisionEntered)
.Subscribe(_ => Debug.Log("Do here"));
}
And, I don't recommend Observable.EveryUpdate .It is necessary to manage lifetime.
I recommend using this.UpdateAsObservable (UniRx.Triggers) instead.
It automatically publishes OnCompleted message on the gameobject destroyed.
I just came up with another way.
var streamB = this.OnTriggerEnter2DAsObservable().AsUnitObservable();
this.UpdateAsObservable()
.TakeUntil(streamB)
.RepeatUntilDestroy(this)
.Subscribe(_ =>
{
Debug.Log(Time.frameCount);
});
Can you provide a little more context about the actual game behavior you are trying to implement?
My guess would be that there is some other approach to what you are trying to do, without having to rely on EveryUpdate (e.g. by using OnTriggerStay and/or OnTriggerExit).
Just giving a guess to what you mean by "negative" of the sample operator: you might want to have a look at pausable. You'd have to generate the proper boolean values though, and how to do that really depends on what game behavior you are actually trying to implement here.

Confusion over behavior of Publish().Refcount()

I've got a simple program here that displays the number of letters in various words. It works as expected.
static void Main(string[] args) {
var word = new Subject<string>();
var wordPub = word.Publish().RefCount();
var length = word.Select(i => i.Length);
var report =
wordPub
.GroupJoin(length,
s => wordPub,
s => Observable.Empty<int>(),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.Select(j => new { Word = i.Word, Length = j }));
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Donkey");
word.OnNext("Elephant");
word.OnNext("Zebra");
Console.ReadLine();
}
And the output is:
Apple 5
Banana 6
Cat 3
Donkey 6
Elephant 8
Zebra 5
I used the Publish().RefCount() because "wordpub" is included in "report" twice. Without it, when a word is emitted first one part of the report would get notified by a callback, and then the other part of report would be notified, double the notifications. That is kindof what happens; the output ends up having 11 items rather than 6. At least that is what I think is going on. I think of using Publish().RefCount() in this situation as simultaneously updating both parts of the report.
However if I change the length function to ALSO use the published source like this:
var length = wordPub.Select(i => i.Length);
Then the output is this:
Apple 5
Apple 6
Banana 6
Cat 3
Banana 3
Cat 6
Donkey 6
Elephant 8
Donkey 8
Elephant 5
Zebra 5
Why can't the length function also use the same published source?
This was a great challenge to solve!
So subtle the conditions that this happens.
Apologies in advance for the long explanation, but bear with me!
TL;DR
Subscriptions to the published source are processed in order, but before any other subscription directly to the unpublished source. i.e. you can jump the queue!
With GroupJoin subscription order is important to determine when windows open and close.
My first concern would be that you are publish refcounting a subject.
This should be a no-op.
Subject<T> has no subscription cost.
So when you remove the Publish().RefCount() :
var word = new Subject<string>();
var wordPub = word;//.Publish().RefCount();
var length = word.Select(i => i.Length);
then you get the same issue.
So then I look to the GroupJoin (because my intuition suggests that Publish().Refcount() is a red herring).
For me, eyeballing this alone was too hard to rationalise, so I lean on a simple debugging too I have used dozens of times of the years - a Trace or Log extension method.
public interface ILogger
{
void Log(string input);
}
public class DumpLogger : ILogger
{
public void Log(string input)
{
//LinqPad `Dump()` extension method.
// Could use Console.Write instead.
input.Dump();
}
}
public static class ObservableLoggingExtensions
{
private static int _index = 0;
public static IObservable<T> Log<T>(this IObservable<T> source, ILogger logger, string name)
{
return Observable.Create<T>(o =>
{
var index = Interlocked.Increment(ref _index);
var label = $"{index:0000}{name}";
logger.Log($"{label}.Subscribe()");
var disposed = Disposable.Create(() => logger.Log($"{label}.Dispose()"));
var subscription = source
.Do(
x => logger.Log($"{label}.OnNext({x.ToString()})"),
ex => logger.Log($"{label}.OnError({ex})"),
() => logger.Log($"{label}.OnCompleted()")
)
.Subscribe(o);
return new CompositeDisposable(subscription, disposed);
});
}
}
When I add the logging to your provided code it looks like this:
var logger = new DumpLogger();
var word = new Subject<string>();
var wordPub = word.Publish().RefCount();
var length = word.Select(i => i.Length);
var report =
wordPub.Log(logger, "lhs")
.GroupJoin(word.Select(i => i.Length).Log(logger, "rhs"),
s => wordPub.Log(logger, "lhsDuration"),
s => Observable.Empty<int>().Log(logger, "rhsDuration"),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.Select(j => new { Word = i.Word, Length = j }));
report.Subscribe(i => ($"{i.Word} {i.Length}").Dump("OnNext"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Donkey");
word.OnNext("Elephant");
word.OnNext("Zebra");
This will then output in my log something like the following
Log with Publish().RefCount() used
0001lhs.Subscribe()
0002rhs.Subscribe()
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe()
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe()
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Banana 6
...
However when I remove the usage Publish().RefCount() the new log output is as follows:
Log without only Subject
0001lhs.Subscribe()
0002rhs.Subscribe()
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe()
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe()
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Apple 6
OnNext
Banana 6
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
...
This gives us some insight, however when the issue really becomes clear is when we start annotating our logs with a logical list of subscriptions.
In the original (working) code with the RefCount our annotations might look like this
//word.Subsribers.Add(wordPub)
0001lhs.Subscribe() //wordPub.Subsribers.Add(0001lhs)
0002rhs.Subscribe() //word.Subsribers.Add(0002rhs)
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe() //wordPub.Subsribers.Add(0003lhsDuration)
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe() //wordPub.Subsribers.Add(0005lhsDuration)
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose() //wordPub.Subsribers.Remove(0003lhsDuration)
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Banana 6
So in this example, when word.OnNext("Banana"); is executed the chain of observers is linked in this order
wordPub
0002rhs
However, wordPub has child subscriptions!
So the real subscription list looks like
wordPub
0001lhs
0003lhsDuration
0005lhsDuration
0002rhs
If we annotate the Subject only log we see where the subtlety lies
0001lhs.Subscribe() //word.Subsribers.Add(0001lhs)
0002rhs.Subscribe() //word.Subsribers.Add(0002rhs)
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe() //word.Subsribers.Add(0003lhsDuration)
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe() //word.Subsribers.Add(0005lhsDuration)
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Apple 6
OnNext
Banana 6
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
So in this example, when word.OnNext("Banana"); is executed the chain of observers is linked in this order
1. 0001lhs
2. 0002rhs
3. 0003lhsDuration
4. 0005lhsDuration
As the 0003lhsDuration subscription is activated after the 0002rhs, it wont see the "Banana" value to terminate the window, until after the rhs has been sent the value, thus yielding it in the still open window.
Whew
As #francezu13k50 points out the obvious and simple solution to your problem is to just use word.Select(x => new { Word = x, Length = x.Length });, but as I think you have given us a simplified version of your real problem (appreciated) I understand why this isn't suitable.
However, as I dont know what your real problem space is I am not sure what to suggest to you to provide a solution, except that you have one with your current code, and now you should know why it works the way it does.
RefCount returns an Observable that stays connected to the source as long as there is at least one subscription to the returned Observable. When the last subscription is disposed, RefCount disposes it's connection to the source, and reconnects when a new subscription is being made. It might be the case with your report query that all subscriptions to the 'wordPub' are disposed before the query is fulfilled.
Instead of the complicated GroupJoin query you could simply do :
var report = word.Select(x => new { Word = x, Length = x.Length });
Edit:
Change your report query to this if you want to use the GroupJoin operator :
var report =
wordPub
.GroupJoin(length,
s => wordPub,
s => Observable.Empty<int>(),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.FirstAsync().Select(j => new { Word = i.Word, Length = j }));
Because GroupJoin seems to be very tricky to work with, here is another approach for correlating the inputs and outputs of functions.
static void Main(string[] args) {
var word = new Subject<string>();
var length = new Subject<int>();
var report =
word
.CombineLatest(length, (w, l) => new { Word = w, Length = l })
.Scan((a, b) => new { Word = b.Word, Length = a.Word == b.Word ? b.Length : -1 })
.Where(i => i.Length != -1);
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple"); length.OnNext(5);
word.OnNext("Banana");
word.OnNext("Cat"); length.OnNext(3);
word.OnNext("Donkey");
word.OnNext("Elephant"); length.OnNext(8);
word.OnNext("Zebra"); length.OnNext(5);
Console.ReadLine();
}
This approach works if every input has 0 or more outputs subject to the constraints that (1) outputs only arrive in the same order as the inputs AND (2) each output corresponds to its most recent input. This is like a LeftJoin - each item in the first list (word) is paired with items in the right list (length) that subsequently arrive, up until another item in the first list is emitted.
Trying to use regular Join instead of GroupJoin. I thought the problem was that when a new word was created there was a race condition inside Join between creating a new window and ending the current one. So here I tried to elimate that by pairing every word with a null signifying the end of the window. Doesn't work, just like the first version did not. How is it possible that a new window is created for each word without the previous one being closed first? Completely confused.
static void Main(string[] args) {
var lgr = new DelegateLogger(Console.WriteLine);
var word = new Subject<string>();
var wordDelimited =
word
.Select(i => Observable.Return<string>(null).StartWith(i))
.SelectMany(i => i);
var wordStart = wordDelimited.Where(i => i != null);
var wordEnd = wordDelimited.Where(i => i == null);
var report = Observable
.Join(
wordStart.Log(lgr, "word"), // starts window
wordStart.Select(i => i.Length),
s => wordEnd.Log(lgr, "expireWord"), // ends current window
s => Observable.Empty<int>(),
(l, r) => new { Word = l, Length = r });
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Zebra");
word.OnNext("Elephant");
word.OnNext("Bear");
Console.ReadLine();
}

Merging Observables

Here we have a Observable Sequence... in .NET using Rx.
var aSource = new Subject<int>();
var bSource = new Subject<int>();
var paired = Observable
.Merge(aSource, bSource)
.GroupBy(i => i).SelectMany(g => g.Buffer(2).Take(1));
paired.Subscribe(g => Console.WriteLine("{0}:{1}", g.ElementAt(0), g.ElementAt(1)));
aSource.OnNext(4);
bSource.OnNext(1);
aSource.OnNext(2);
bSource.OnNext(5);
aSource.OnNext(3);
bSource.OnNext(3);
aSource.OnNext(5);
bSource.OnNext(2);
aSource.OnNext(1);
bSource.OnNext(4);
Output:
3:3
5:5
2:2
1:1
4:4
We will get events every time a pair of numbers arrive with the same id.
Perfect! Just what i want.
Groups of two, paired by value.
Next question....
How to get a selectmany/buffer for sequences of values.
So 1,2,3,4,5 arrives at both aSource and bSource via OnNext(). Then fire ConsoleWriteLine() for 1-5. Then when 2,3,4,5,6 arrives, we get another console.writeline(). Any clues anyone?
Immediately, the Rx forum suggests looking at .Window()
http://introtorx.com/Content/v1.0.10621.0/17_SequencesOfCoincidence.html
Which on the surface looks perfect. In my case i need a window of value 4, in this case.
Where in the query sequence does it belong to get this effect?
var paired = Observable.Merge(aSource, bSource).GroupBy(i => i).SelectMany(g => g.Buffer(2).Take(1));
Output
1,2,3,4,5 : 1,2,3,4,5
2,3,4,5,6 : 2,3,4,5,6
Regards,
Daniel
Assuming events arrive randomly at the sources, use my answer to "Reordering events with Reactive Extensions" to get the events in order.
Then use Observable.Buffer to create a sliding buffer:
// get this using the OrderedCollect/Sort in the referenced question
IObservable<int> orderedSource;
// then subscribe to this
orderedSource.Buffer(5, 1);
Here is an extension method that fires when it has n inputs of the same ids.
public static class RxExtension
{
public static IObservable<TSource> MergeBuffer<TSource>(this IObservable<TSource> source, Func<TSource, int> keySelector, Func<IList<TSource>,TSource> mergeFunction, int bufferCount)
{
return Observable.Create<TSource>(o => {
var buffer = new Dictionary<int, IList<TSource>>();
return source.Subscribe<TSource>(i =>
{
var index = keySelector(i);
if (buffer.ContainsKey(index))
{
buffer[index].Add(i);
}
else
{
buffer.Add(index, new List<TSource>(){i});
}
if (buffer.Count==bufferCount)
{
o.OnNext(mergeFunction(buffer[index]));
buffer.Remove(index);
}
});
});
}
}
Calling the extension.
mainInput = Observable.Merge(inputNodes.ToArray()).MergeBuffer<NodeData>(x => x.id, x => MergeData(x), 1);