I have a hot observable Observable<Resource> resources that represents consumable resources and I want to queue up consumers Action1<Resource> for these resources. A Resource can be used by at most 1 consumer. It should not be used at all once a new value is pushed from resources. If my consumers were also wrapped in a hot observable then the marble-diagram of what I'm after would be
--A--B--C--D--E--
----1----2--34---
----A----C--D-E--
----1----2--3-4--
I've managed a naive implementation using a PublishSubject and zip but this only works if each resource is consumed before a new resource is published (i.e. instead of the required sequence [A1, C2, D3, E4] this implementation will actually produce [A1, B2, C3, D4]).
This is my first attempt at using rx and I've had a play around with both delay and join but can't quite seem to get what I'm after. I've also read that ideally Subjects should be avoided, but I can't see how else I would implement this.
public class ResourceQueue<Resource> {
private final PublishSubject<Action1<Resource>> consumers = PublishSubject.create();
public ResourceQueue(Observable<Resource> resources) {
resources.zipWith(this.consumers, new Func2<Resource, Action1<Resource>, Object>() {
#Override
public Object call(Resource resource, Action1<Resource> consumer) {
consumer.execute(resource);
return null;
}
}).publish().connect();
}
public void queue(final Action1<Resource> consumer) {
consumers.onNext(consumer);
}
}
Is there a way to achieve what I'm after? Is there a more 'rx-y' approach to the solution?
EDIT: changed withLatesFrom suggestion with combineLatest.
The only solution I can think of is to use combineLatest to get all the possible combinations, and manually exclude the ones that you do not need:
final ExecutorService executorService = Executors.newCachedThreadPool();
final Observable<String> resources = Observable.create(s -> {
Runnable r = new Runnable() {
#Override
public void run() {
final List<Integer> sleepTimes = Arrays.asList(200, 200, 200, 200, 200);
for (int i = 0; i < sleepTimes.size(); i++) {
try {
Thread.sleep(sleepTimes.get(i));
} catch (Exception e) {
e.printStackTrace();
}
String valueOf = String.valueOf((char) (i + 97));
System.out.println("new resource " + valueOf);
s.onNext(valueOf);
}
s.onCompleted();
}
};
executorService.submit(r);
});
final Observable<Integer> consumers = Observable.create(s -> {
Runnable r = new Runnable() {
#Override
public void run() {
final List<Integer> sleepTimes = Arrays.asList(300, 400, 200, 0);
for (int i = 0; i < sleepTimes.size(); i++) {
try {
Thread.sleep(sleepTimes.get(i));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("new consumer " + (i + 1));
s.onNext(i + 1);
}
s.onCompleted();
};
};
executorService.submit(r);
});
final LatestValues latestValues = new LatestValues();
final Observable<String> combineLatest = Observable.combineLatest(consumers, resources, (c, r) -> {
if (latestValues.alreadyProcessedAnyOf(c, r)) {
return "";
}
System.out.println("consumer " + c + " will consume resource " + r);
latestValues.updateWithValues(c, r);
return c + "_" + r;
});
combineLatest.subscribe();
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.SECONDS);
The class holding the latest consumers and resources.
static class LatestValues {
Integer latestConsumer = Integer.MAX_VALUE;
String latestResource = "";
public boolean alreadyProcessedAnyOf(Integer c, String r) {
return latestConsumer.equals(c) || latestResource.equals(r);
}
public void updateWithValues(Integer c, String r) {
latestConsumer = c;
latestResource = r;
}
}
Related
I've been doing some research to find a suitable algorithm for suggesting friends. I came across DFS, but I've never implemented it in Dart before. Could someone please help me t translate it into Dart? Below is the java code:
public class SuggestFriendsDFS<T> {
private HashMap<T, ArrayList<T>> adj = new HashMap<>(); //graph
private List<Set<T>> groups = new ArrayList<>();
public void addFriendship(T src, T dest) {
adj.putIfAbsent(src, new ArrayList<T>());
adj.get(src).add(dest);
adj.putIfAbsent(dest, new ArrayList<T>());
adj.get(dest).add(src);
}
//V is total number of people, E is number of connections
private void findGroups() {
Map<T, Boolean> visited = new HashMap<>();
for (T t: adj.keySet())
visited.put(t, false);
for (T t:adj.keySet()) {
if (!visited.get(t)) {
Set<T> group = new HashSet<>();
dfs(t, visited, group);
groups.add(group);
}
}
}
//DFS + memoization
private void dfs(T v, Map<T, Boolean> visited, Set<T> group ) {
visited.put(v,true);
group.add(v);
for (T x : adj.get(v)) {
if (!visited.get(x))
dfs(x, visited, group);
}
}
public Set<T> getSuggestedFriends (T a) {
if (groups.isEmpty())
findGroups();
Set<T> res = new HashSet<>();
for (Set<T> t : groups) {
if (t.contains(a)) {
res = t;
break;
}
}
if (res.size() > 0)
res.remove(a);
return res;
}
}
I'm aware it's too much to ask, but any help will be much appreciated as I tried to translate it and ended up getting loads of errors. Thanks in advance!(: For reference, this is where I found the explanation for the java code.
I tried https://sma.github.io/stuff/java2dartweb/java2dartweb.html that does automatic Java to Dart conversion but it doesn't work well as soon as the code is a bit complex.
See the full conversion below, you can try it in Dartpad
import 'dart:collection';
class SuggestFriendsDFS<T> {
final HashMap<T, List<T>> _adj = HashMap(); //graph
final List<Set<T>> groups = [];
//Time O(1), Space O(1)
void addFriendship(T src, T dest) {
_adj.putIfAbsent(src, () => <T>[]);
_adj[src]!.add(dest);
_adj.putIfAbsent(dest, () => <T>[]);
_adj[dest]!.add(src);
}
//DFS wrapper, Time O(V+E), Space O(V)
//V is total number of people, E is number of connections
void findGroups() {
Map<T, bool> visited = HashMap();
for (T t in _adj.keys) {
visited[t] = false;
}
for (T t in _adj.keys) {
if (visited[t] == false) {
Set<T> group = HashSet();
_dfs(t, visited, group);
groups.add(group);
}
}
}
//DFS + memoization, Time O(V+E), Space O(V)
void _dfs(T v, Map<T, bool> visited, Set<T> group) {
visited[v] = true;
group.add(v);
for (T x in _adj[v] ?? []) {
if ((visited[x] ?? true) == false) _dfs(x, visited, group);
}
}
//Time O(V+E), Space O(V)
Set<T> getSuggestedFriends(T a) {
if (groups.isEmpty) findGroups();
var result = groups.firstWhere((element) => element.contains(a),
orElse: () => <T>{});
if (result.isNotEmpty) result.remove(a);
return result;
}
}
void main() {
SuggestFriendsDFS<String> g = SuggestFriendsDFS();
g.addFriendship("Ashley", "Christopher");
g.addFriendship("Ashley", "Emily");
g.addFriendship("Ashley", "Joshua");
g.addFriendship("Bart", "Lisa");
g.addFriendship("Bart", "Matthew");
g.addFriendship("Christopher", "Andrew");
g.addFriendship("Emily", "Joshua");
g.addFriendship("Jacob", "Christopher");
g.addFriendship("Jessica", "Ashley");
g.addFriendship("JorEl", "Zod");
g.addFriendship("KalEl", "JorEl");
g.addFriendship("Kyle", "Lex");
g.addFriendship("Kyle", "Zod");
g.addFriendship("Lisa", "Marge");
g.addFriendship("Matthew", "Lisa");
g.addFriendship("Michael", "Christopher");
g.addFriendship("Michael", "Joshua");
g.addFriendship("Michael", "Jessica");
g.addFriendship("Samantha", "Matthew");
g.addFriendship("Samantha", "Tyler");
g.addFriendship("Sarah", "Andrew");
g.addFriendship("Sarah", "Christopher");
g.addFriendship("Sarah", "Emily");
g.addFriendship("Tyler", "Kyle");
g.addFriendship("Stuart", "Jacob");
g.findGroups();
print(g.groups);
String name = "Andrew";
print("Suggestion friends of " +
name +
": " +
g.getSuggestedFriends(name).toString());
}
In the code below I would like the subscriber to control when the Flowable emits an event by holding a reference to the Subscription inside subscribe() and requesting the number of elements I want to be produced.
What I am experiencing is that observeOn()'s buffer with size 2 is hiding my call to subscription.request(3) as the producer is producing 2 elements at a time instead of 3.
public class FlowableExamples {
public static void main(String[] args) throws InterruptedException {
long start = new Date().getTime();
Flowable<Integer> flowable = Flowable
.generate(() -> 0, (Integer state, Emitter<Integer> emitter) -> {
int newValue = state + 1;
log("Producing: " + newValue);
emitter.onNext(newValue);
return newValue;
})
.take(30);
flowable
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation(), false, 2)
.subscribe(new Subscriber<Integer>() {
Subscription subscription;
#Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
subscription.request(5);
}
#Override
public void onNext(Integer integer) {
log("\t\treceived: " + integer);
if (integer >= 5) {
sleep(500);
log("Requesting 3 should produce 3, but actually produced 2");
subscription.request(3);
sleep(1000);
}
}
#Override
public void onError(Throwable throwable) {}
#Override
public void onComplete() {
log("Subscription Completed!!!!!!!!");
}
});
sleep(40_000);
System.out.println("Exit main after: " + (new Date().getTime() - start) + " ms");
}
private static void log(String msg) {
System.out.println(Thread.currentThread().getName() + ": " + msg);
}
private static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {}
}
}
How could I accomplish this?
Project Reactor has this factory method for creating a push/pull Producer<T>.
http://projectreactor.io/docs/core/release/reference/#_hybrid_push_pull_model
Is there any such thing in RxJava-2?
If not, what would be the recommended way (without actually implemementing reactive specs interfaces from scratch) to create such beast that can handle the push/pull model?
EDIT: as requested I am giving an example of the API I am trying to use...
private static class API
{
CompletableFuture<Void> getT(Consumer<Object> consumer) {}
}
private static class Callback implements Consumer<Object>
{
private API api;
public Callback(API api) { this api = api; }
#Override
public void accept(Object o)
{
//do stuff with o
//...
//request for another o
api.getT(this);
}
}
public void example()
{
API api = new API();
api.getT(new Callback(api)).join();
}
So it's call back based, which will get one item and from within you can request for another one. the completable future flags no more items.
Here is an example of a custom Flowable that turns this particular API into an RxJava source. Note however that in general, the API peculiarities in general may not be possible to capture with a single reactive bridge design:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.*;
import java.util.function.*;
import org.reactivestreams.*;
import io.reactivex.Flowable;
import io.reactivex.internal.subscriptions.EmptySubscription;
import io.reactivex.internal.util.BackpressureHelper;
public final class SomeAsyncApiBridge<T> extends Flowable<T> {
final Function<? super Consumer<? super T>,
? extends CompletableFuture<Void>> apiInvoker;
final AtomicBoolean once;
public SomeAsyncApiBridge(Function<? super Consumer<? super T>,
? extends CompletableFuture<Void>> apiInvoker) {
this.apiInvoker = apiInvoker;
this.once = new AtomicBoolean();
}
#Override
protected void subscribeActual(Subscriber<? super T> s) {
if (once.compareAndSet(false, true)) {
SomeAsyncApiBridgeSubscription<T> parent =
new SomeAsyncApiBridgeSubscription<>(s, apiInvoker);
s.onSubscribe(parent);
parent.moveNext();
} else {
EmptySubscription.error(new IllegalStateException(
"Only one Subscriber allowed"), s);
}
}
static final class SomeAsyncApiBridgeSubscription<T>
extends AtomicInteger
implements Subscription, Consumer<T>, BiConsumer<Void, Throwable> {
/** */
private static final long serialVersionUID = 1270592169808316333L;
final Subscriber<? super T> downstream;
final Function<? super Consumer<? super T>,
? extends CompletableFuture<Void>> apiInvoker;
final AtomicInteger wip;
final AtomicLong requested;
final AtomicReference<CompletableFuture<Void>> task;
static final CompletableFuture<Void> TASK_CANCELLED =
CompletableFuture.completedFuture(null);
volatile T item;
volatile boolean done;
Throwable error;
volatile boolean cancelled;
long emitted;
SomeAsyncApiBridgeSubscription(
Subscriber<? super T> downstream,
Function<? super Consumer<? super T>,
? extends CompletableFuture<Void>> apiInvoker) {
this.downstream = downstream;
this.apiInvoker = apiInvoker;
this.requested = new AtomicLong();
this.wip = new AtomicInteger();
this.task = new AtomicReference<>();
}
#Override
public void request(long n) {
BackpressureHelper.add(requested, n);
drain();
}
#Override
public void cancel() {
cancelled = true;
CompletableFuture<Void> curr = task.getAndSet(TASK_CANCELLED);
if (curr != null && curr != TASK_CANCELLED) {
curr.cancel(true);
}
if (getAndIncrement() == 0) {
item = null;
}
}
void moveNext() {
if (wip.getAndIncrement() == 0) {
do {
CompletableFuture<Void> curr = task.get();
if (curr == TASK_CANCELLED) {
return;
}
CompletableFuture<Void> f = apiInvoker.apply(this);
if (task.compareAndSet(curr, f)) {
f.whenComplete(this);
} else {
curr = task.get();
if (curr == TASK_CANCELLED) {
f.cancel(true);
return;
}
}
} while (wip.decrementAndGet() != 0);
}
}
#Override
public void accept(Void t, Throwable u) {
if (u != null) {
error = u;
task.lazySet(TASK_CANCELLED);
}
done = true;
drain();
}
#Override
public void accept(T t) {
item = t;
drain();
}
void drain() {
if (getAndIncrement() != 0) {
return;
}
int missed = 1;
long e = emitted;
for (;;) {
for (;;) {
if (cancelled) {
item = null;
return;
}
boolean d = done;
T v = item;
boolean empty = v == null;
if (d && empty) {
Throwable ex = error;
if (ex == null) {
downstream.onComplete();
} else {
downstream.onError(ex);
}
return;
}
if (empty || e == requested.get()) {
break;
}
item = null;
downstream.onNext(v);
e++;
moveNext();
}
emitted = e;
missed = addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
}
}
Test and example source:
import java.util.concurrent.*;
import java.util.function.Consumer;
import org.junit.Test;
public class SomeAsyncApiBridgeTest {
static final class AsyncRange {
final int max;
int index;
public AsyncRange(int start, int count) {
this.index = start;
this.max = start + count;
}
public CompletableFuture<Void> next(Consumer<? super Integer> consumer) {
int i = index;
if (i == max) {
return CompletableFuture.completedFuture(null);
}
index = i + 1;
CompletableFuture<Void> cf = CompletableFuture
.runAsync(() -> consumer.accept(i));
CompletableFuture<Void> cancel = new CompletableFuture<Void>() {
#Override
public boolean cancel(boolean mayInterruptIfRunning) {
cf.cancel(mayInterruptIfRunning);
return super.cancel(mayInterruptIfRunning);
}
};
return cancel;
}
}
#Test
public void simple() {
AsyncRange r = new AsyncRange(1, 10);
new SomeAsyncApiBridge<Integer>(
consumer -> r.next(consumer)
)
.test()
.awaitDone(500, TimeUnit.SECONDS)
.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
}
}
This is something that looks that is working using Reactor's Flux.create(). I changed the API a bit.
public class FlowableGenerate4
{
private static class API
{
private ExecutorService es = Executors.newFixedThreadPool(1);
private CompletableFuture<Void> done = new CompletableFuture<>();
private AtomicInteger stopCounter = new AtomicInteger(10);
public boolean isDone()
{
return done.isDone();
}
public CompletableFuture<Void> getT(Consumer<Object> consumer)
{
es.submit(() -> {
try {
Thread.sleep(100);
} catch (Exception e) {
}
if (stopCounter.decrementAndGet() < 0)
done.complete(null);
else
consumer.accept(new Object());
});
return done;
}
}
private static class Callback implements Consumer<Object>
{
private API api;
private FluxSink<Object> sink;
public Callback(API api, FluxSink<Object> sink)
{
this.api = api;
this.sink = sink;
}
#Override
public void accept(Object o)
{
sink.next(o);
if (sink.requestedFromDownstream() > 0 && !api.isDone())
api.getT(this);
else
sink.currentContext().<AtomicBoolean>get("inProgress")
.set(false);
}
}
private Publisher<Object> reactorPublisher()
{
API api = new API();
return
Flux.create(sink -> {
sink.onRequest(n -> {
//if it's in progress already, do nothing
//I understand that onRequest() can be called asynchronously
//regardless if the previous call demand has been satisfied or not
if (!sink.currentContext().<AtomicBoolean>get("inProgress")
.compareAndSet(false, true))
return;
//else kick off calls to API
api.getT(new Callback(api, sink))
.whenComplete((o, t) -> {
if (t != null)
sink.error(t);
else
sink.complete();
});
});
}).subscriberContext(
Context.empty().put("inProgress", new AtomicBoolean(false)));
}
#Test
public void test()
{
Flowable.fromPublisher(reactorPublisher())
.skip(5)
.take(10)
.blockingSubscribe(
i -> System.out.println("onNext()"),
Throwable::printStackTrace,
() -> System.out.println("onComplete()")
);
}
}
Scenario: I have a stream of data I am reading from the database. What I would like to do is read a chunk of data, process it and stream it using rx-java 2. But while I am processing and streaming it I would like to load the next chunk of data on a separate thread (pre-pull the next chunk).
I have tried:
Flowable.generate(...)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.map(...)
.subscribe(...)
Unfortunately this causes the generate method to continually run on an io thread. I just want one pre-pull. I have tried using buffer, but that really just ends up creating lists of chunks.
So basically while I am streaming the current chunk on a separate thread I want to read the next chunk and have it ready.
Not sure if this is possible. I need to use generate because there is no concept of when the data will end.
I have tried using subscribe(new FlowableSubscriber(){...}) using Subscription::request but that did not seem to work.
There are no standard operators in RxJava that would have this type of request-response pattern. You'd need a custom observeOn that requests before it sends the current item to its downstream.
import java.util.concurrent.atomic.*;
import org.junit.Test;
import org.reactivestreams.*;
import io.reactivex.*;
import io.reactivex.Scheduler.Worker;
import io.reactivex.internal.util.BackpressureHelper;
import io.reactivex.schedulers.Schedulers;
public class LockstepObserveOnTest {
#Test
public void test() {
Flowable.generate(() -> 0, (s, e) -> {
System.out.println("Generating " + s);
Thread.sleep(500);
e.onNext(s);
return s + 1;
})
.subscribeOn(Schedulers.io())
.compose(new LockstepObserveOn<>(Schedulers.computation()))
.map(v -> {
Thread.sleep(250);
System.out.println("Processing " + v);
Thread.sleep(250);
return v;
})
.take(50)
.blockingSubscribe();
}
static final class LockstepObserveOn<T> extends Flowable<T>
implements FlowableTransformer<T, T> {
final Flowable<T> source;
final Scheduler scheduler;
LockstepObserveOn(Scheduler scheduler) {
this(null, scheduler);
}
LockstepObserveOn(Flowable<T> source, Scheduler scheduler) {
this.source = source;
this.scheduler = scheduler;
}
#Override
protected void subscribeActual(Subscriber<? super T> subscriber) {
source.subscribe(new LockstepObserveOnSubscriber<>(
subscriber, scheduler.createWorker()));
}
#Override
public Publisher<T> apply(Flowable<T> upstream) {
return new LockstepObserveOn<>(upstream, scheduler);
}
static final class LockstepObserveOnSubscriber<T>
implements FlowableSubscriber<T>, Subscription, Runnable {
final Subscriber<? super T> actual;
final Worker worker;
final AtomicReference<T> item;
final AtomicLong requested;
final AtomicInteger wip;
Subscription upstream;
volatile boolean cancelled;
volatile boolean done;
Throwable error;
long emitted;
LockstepObserveOnSubscriber(Subscriber<? super T> actual, Worker worker) {
this.actual = actual;
this.worker = worker;
this.item = new AtomicReference<>();
this.requested = new AtomicLong();
this.wip = new AtomicInteger();
}
#Override
public void onSubscribe(Subscription s) {
upstream = s;
actual.onSubscribe(this);
s.request(1);
}
#Override
public void onNext(T t) {
item.lazySet(t);
schedule();
}
#Override
public void onError(Throwable t) {
error = t;
done = true;
schedule();
}
#Override
public void onComplete() {
done = true;
schedule();
}
#Override
public void request(long n) {
BackpressureHelper.add(requested, n);
schedule();
}
#Override
public void cancel() {
cancelled = true;
upstream.cancel();
worker.dispose();
if (wip.getAndIncrement() == 0) {
item.lazySet(null);
}
}
void schedule() {
if (wip.getAndIncrement() == 0) {
worker.schedule(this);
}
}
#Override
public void run() {
int missed = 1;
long e = emitted;
for (;;) {
long r = requested.get();
while (e != r) {
if (cancelled) {
item.lazySet(null);
return;
}
boolean d = done;
T v = item.get();
boolean empty = v == null;
if (d && empty) {
Throwable ex = error;
if (ex == null) {
actual.onComplete();
} else {
actual.onError(ex);
}
worker.dispose();
return;
}
if (empty) {
break;
}
item.lazySet(null);
upstream.request(1);
actual.onNext(v);
e++;
}
if (e == r) {
if (cancelled) {
item.lazySet(null);
return;
}
if (done && item.get() == null) {
Throwable ex = error;
if (ex == null) {
actual.onComplete();
} else {
actual.onError(ex);
}
worker.dispose();
return;
}
}
emitted = e;
missed = wip.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
}
}
}
I have a Hot stream of events coming of following type:
Event
{
string name;
int state ; // its 1 or 2 ie active or unactive
}
there is a function which provides parent name of given name - string GetParent(string name)
I need to buffer event per parent for 2 minutes, if during this 2 minute , i recv any event for child with state =2 for a given parent , this buffer should cancel and should output 0 otherwise i get the count of the events recvd .
I know I have to use GroupBy to partition, and then buffer and then count but i am unable to think of a way by which i create Buffer which is unique per parent, i though of using Distinct but this doesnt solve the problem, for i only dont want to create buffer till the parent is active (as once the parent's buffer gets cancelled or 2 minutes is over, the parent buffer can be created again)
So I understand I need to create a custom buffer which checks the condition for creating buffer, but how do i do this via reactive extensions.
Any help will be greatly appreciated.
regards
Thanks Brandon for your help. This is the main program I am using for testing. Its not working.As I am new to reactive extension problem can be in the way i am testing
namespace TestReactive
{
class Program
{
static int abc = 1;
static void Main(string[] args)
{
Subject<AEvent> memberAdded = new Subject<AEvent>();
//ISubject<AEvent, AEvent> syncedSubject = new ISubject<AEvent, AEvent>();
var timer = new Timer { Interval = 5 };
timer.Enabled = true;
timer.Elapsed += (sender, e) => MyElapsedMethod(sender, e, memberAdded);
var bc = memberAdded.Subscribe();
var cdc = memberAdded.GroupBy(e => e.parent)
.SelectMany(parentGroup =>
{
var children = parentGroup.Publish().RefCount();
var inactiveChild = children.SkipWhile(c => c.state != 2).Take(1).Select(c => 0);
var timer1 = Observable.Timer(TimeSpan.FromSeconds(1));
var activeCount = children.TakeUntil(timer1).Count();
return Observable.Amb(activeCount, inactiveChild)
.Select(count => new { ParentName = parentGroup.Key, Count = count });
});
Observable.ForEachAsync(cdc, x => WriteMe("Dum Dum " + x.ParentName+x.Count));
// group.Dump("Dum");
Console.ReadKey();
}
static void WriteMe(string sb)
{
Console.WriteLine(sb);
}
static void MyElapsedMethod(object sender, ElapsedEventArgs e, Subject<AEvent> s)
{
AEvent ab = HelperMethods.GetAlarm();
Console.WriteLine(abc + " p =" + ab.parent + ", c = " + ab.name + " ,s = " + ab.state);
s.OnNext(ab);
}
}
}
public static AEvent GetAlarm()
{
if (gp> 4)
gp = 1;
if (p > 4)
p = 1;
if (c > 4)
c = 1;
AEvent a = new AEvent();
a.parent = "P" + gp + p;
a.name = "C" + gp + p + c;
if (containedKeys.ContainsKey(a.name))
{
a.state = containedKeys[a.name];
if (a.state == 1)
containedKeys[a.name] = 2;
else
containedKeys[a.name] = 1;
}
else
{
containedKeys.TryAdd(a.name, 1);
}
gp++; p++; c++;
return a;
}
So this method , generates a event for Parent at each tick. It generates event for parent P11,P22,P33,P44 with State =1 and then followed by events for Parent P11,P22,P33,P44 with State =2
I am using Observable.ForEach to print the result, I see its being called 4 times and after that its nothing, its like cancellation of group is not happening
Assuming that a two minute buffer for each group should open as soon as the first event for that group is seen, and close after two minutes or a zero state is seen, then I think the following works:
public static IObservable<EventCount> EventCountByParent(
this IObservable<Event> source, IScheduler scheduler)
{
return Observable.Create<EventCount>(observer => source.GroupByUntil(
evt => GetParent(evt.Name),
evt => evt,
group =>
#group.Where(evt => evt.State == 2)
.Merge(Observable.Timer(
TimeSpan.FromMinutes(2), scheduler).Select(_ => Event.Null)))
.SelectMany(
go =>
go.Aggregate(0, (acc, evt) => (evt.State == 2 ? 0 : acc + 1))
.Select(count => new EventCount(go.Key, count))).Subscribe(observer));
}
With EventCount (implementing equality overrides for testing) as:
public class EventCount
{
private readonly string _name;
private readonly int _count;
public EventCount(string name, int count)
{
_name = name;
_count = count;
}
public string Name { get { return _name; } }
public int Count { get { return _count; } }
public override string ToString()
{
return string.Format("Name: {0}, Count: {1}", _name, _count);
}
protected bool Equals(EventCount other)
{
return string.Equals(_name, other._name) && _count == other._count;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((EventCount) obj);
}
public override int GetHashCode()
{
unchecked
{
return ((_name != null ? _name.GetHashCode() : 0)*397) ^ _count;
}
}
}
And Event as:
public class Event
{
public static Event Null = new Event(string.Empty, 0);
private readonly string _name;
private readonly int _state;
public Event(string name, int state)
{
_name = name;
_state = state;
}
public string Name { get { return _name; } }
public int State { get { return _state; } }
}
I did a quick (i.e. not exhaustive) test with Rx-Testing:
public class EventCountByParentTests : ReactiveTest
{
private readonly TestScheduler _testScheduler;
public EventCountByParentTests()
{
_testScheduler = new TestScheduler();
}
[Fact]
public void IsCorrect()
{
var source = _testScheduler.CreateHotObservable(
OnNext(TimeSpan.FromSeconds(10).Ticks, new Event("A", 1)),
OnNext(TimeSpan.FromSeconds(20).Ticks, new Event("B", 1)),
OnNext(TimeSpan.FromSeconds(30).Ticks, new Event("A", 1)),
OnNext(TimeSpan.FromSeconds(40).Ticks, new Event("B", 1)),
OnNext(TimeSpan.FromSeconds(50).Ticks, new Event("A", 1)),
OnNext(TimeSpan.FromSeconds(60).Ticks, new Event("B", 2)),
OnNext(TimeSpan.FromSeconds(70).Ticks, new Event("A", 1)),
OnNext(TimeSpan.FromSeconds(140).Ticks, new Event("A", 1)),
OnNext(TimeSpan.FromSeconds(150).Ticks, new Event("A", 1)));
var results = _testScheduler.CreateObserver<EventCount>();
var sut = source.EventCountByParent(_testScheduler).Subscribe(results);
_testScheduler.Start();
results.Messages.AssertEqual(
OnNext(TimeSpan.FromSeconds(60).Ticks, new EventCount("B", 0)),
OnNext(TimeSpan.FromSeconds(130).Ticks, new EventCount("A", 4)),
OnNext(TimeSpan.FromSeconds(260).Ticks, new EventCount("A", 2)));
}
}
something like....
source.GroupBy(e => GetParent(e.name))
.SelectMany(parentGroup =>
{
var children = parentGroup.Publish().RefCount();
var inactiveChild = children.SkipWhile(c => c.state != 2).Take(1).Select(c => 0);
var timer = Observable.Timer(TimeSpan.FromMinutes(2));
var activeCount = children.TakeUntil(timer).Count();
return Observable.Amb(activeCount, inactiveChild)
.Select(count => new { ParentName = parentGroup.Key, Count = count };
});
This will give you a sequence of { ParentName, Count } objects.