我观察到一个反应性流Observable<Event>
,当前它直接发出事件。我希望根据BEGIN / END事件将这些事件流在内部小组的支持下进行分组。
输入流
我有一系列类似的事件:
Event(type = Data, groupId = 1)
Event(type = BeginGroup, groupId = 2) // outer group begins
Event(type = Data, groupId = 2)
Event(type = BeginGroup, groupId = 3) // inner group begins
Event(type = Data, groupId = 3)
Event(type = EndGroup, groupId = 3) // inner group ends
Event(type = EndGroup, groupId = 2) // outer group ends
Event(type = Data, groupId = 4)
Event(type = Data, groupId = 5)
编辑-其他前提条件:
我在示例数据中添加了ID,但是通常我不需要ID。该流将注意满足以下条件:
BeginGroup
事件都会在某个时间之后跟随相应的EndGroup
事件期望的输出流
因此,我确保每个事件都属于其上的组,或者如果它不是真实组的一部分,则具有唯一的ID。我想将上述9个事件流归为以下4个事件流:
Event(type = Data, groupId = 1)
GroupEvent(groupId = 2, data = <LIST of Events and/or sub groups>) with following data:
data = [
Event(type = BeginGroup, groupId = 2)
Event(type = Data, groupId = 2)
GroupEvent(groupId = 3, data = <LIST of Events and/or sub groups>) with following data:
data = [
Event(type = BeginGroup, groupId = 3)
Event(type = Data, groupId = 3)
Event(type = EndGroup, groupId = 3)
]
Event(type = EndGroup, groupId = 2)
]
Event(type = Data, groupId = 4)
Event(type = Data, groupId = 5)
我想要的是-逻辑
我想在发生BeginGroup
类型的事件后立即开始分组,直到发生正确的EndGroup
事件为止,并将这两个事件之间的所有事件(包括最终嵌套的组事件)分组。开始/结束组事件之外的元素仅作为单个事件传递。
这是我到目前为止尝试过的
我在问这个问题之前尝试了一些东西,但是我来自Java,甚至在那儿我很少使用window / buffer运算符,并且对它们的使用经验很少。我看到C#中有一个类似GroupByUntil
的运算符,所以我尝试使用它,但是在我的示例中它从未发出任何东西。
var eventObservable: Observable<Event> = ...
// 1) make the observable hot so it can be resued inside the groupbyuntil operator
eventObservable = observable.Publish().RefCount();
var res = eventObservable
.GroupByUntil(
e => e.GroupId, // selector for groups => the group id can be used here
grp => eventObservable.Where(e => e.GroupId != grp.Key) // stop a group as soon as the group id changes
)
.SelectMany(data => data.ToList()) // flatten the observable
.Select(data => {
// Convert the list of Events to GroupEvent if it contains more than 1 event
var list = data.ToList();
if (list.Count == 1)
return list[0];
return new GroupEvent(list);
})
这种方法行不通,它根本不会发出任何东西(尽管eventObservable
确实正确地发出了它的物品)。此外,它缺少嵌套组的支持(从理论上讲是否可行)。
有人可以向我解释如何解决我的问题吗?
答案 0 :(得分:2)
首先进行代码转储,然后进行解释。
我写的数据类:
public enum EventType
{
Data,
BeginGroup,
EndGroup,
Group
}
public class Event<T>
{
public Event(int id, EventType type, T data)
{
this.Id = id;
this.Type = type;
this.Data = data;
}
public int Id { get; set; }
public EventType Type { get; set; }
public T Data { get; set;}
}
public class GroupEvent<T> : Event<T> {
public GroupEvent(int id, IEnumerable<Event<T>> events)
: base(id, EventType.Group, default(T))
{
this.ChildData = events;
}
public IEnumerable<Event<T>> ChildData { get; set; }
}
这是逻辑扩展方法(使用Nuget包System.Collections.Immutable
):
public static class X
{
public static IObservable<Event<T>> GroupEvents<T>(this IObservable<Event<T>> source)
{
return source
.Scan((groupId: 1, stack: ImmutableStack<ImmutableList<Event<T>>>.Empty, output: (Event<T>)null), (state, inEvent) =>
{
if(inEvent.Type == EventType.Data)
{
if (state.stack.IsEmpty)
return (state.groupId + 1, state.stack, new Event<T>(state.groupId, EventType.Data, inEvent.Data));
else
{
var newEvent = new Event<T>(state.stack.Peek()[0].Id, EventType.Data, inEvent.Data);
var newList = state.stack.Peek().Add(newEvent);
var newStack = state.stack.Pop().Push(newList);
return (state.groupId, newStack, null);
}
}
if(inEvent.Type == EventType.BeginGroup)
{
var newEvent = new Event<T>(state.groupId, EventType.BeginGroup, inEvent.Data);
return (state.groupId + 1, state.stack.Push(ImmutableList<Event<T>>.Empty.Add(newEvent)), null);
}
if (inEvent.Type == EventType.EndGroup)
{
var newEvent = new Event<T>(state.stack.Peek()[0].Id, EventType.EndGroup, inEvent.Data);
var newList = state.stack.Peek().Add(newEvent);
var newStack = state.stack.Pop();
var toEmit = new GroupEvent<T>(newList[0].Id, newList);
if(newStack.IsEmpty)
return (state.groupId, newStack, toEmit);
else
{
var parentList = newStack.Peek().Add(toEmit);
newStack = newStack.Pop().Push(parentList);
return (state.groupId, newStack, null);
}
}
throw new NotImplementedException();
})
.Where(t => t.output != null)
.Select(t => t.output);
}
}
此处为跑步者代码:
var s = new Subject<Event<int>>();
var o = s.GroupEvents();
s.OnNext(new Event<int>(-1, EventType.Data, 1));
s.OnNext(new Event<int>(-1, EventType.BeginGroup, 2));
s.OnNext(new Event<int>(-1, EventType.Data, 3));
s.OnNext(new Event<int>(-1, EventType.BeginGroup, 4));
s.OnNext(new Event<int>(-1, EventType.Data, 5));
s.OnNext(new Event<int>(-1, EventType.EndGroup, 6));
s.OnNext(new Event<int>(-1, EventType.EndGroup, 7));
s.OnNext(new Event<int>(-1, EventType.Data, 8));
s.OnNext(new Event<int>(-1, EventType.Data, 9));
结果看起来像您对问题的预期。
我采用了状态机方法,这通常意味着使用.Scan
方法。我们这里的状态是运行中的groupId
计数,以及一堆消息列表。堆栈的顶部代表我们当前要向其添加消息的组。由于Scan
不允许您在状态和输出之间进行区分,因此,第三个状态值是输出变量。
我正在使用不可变的集合,因为它们在Rx上表现最好。如果使用可变集合,则性能可以提高,但是您必须注意多重订阅的含义。
至于使用内置运算符(GroupBy
,Buffer
,Window
,Join
等),我认为这些都不起作用以及所需的树状递归结构。如果您的结构较平坦,则Window
可能有用,但是需要一些工作。