我想知道在Rx中解决以下问题的规范方法是什么:假设我有两个observable,mouse_down
和mouse_up
,其元素代表鼠标按钮按下。在一个非常简单的场景中,如果我想检测长按,我可以通过以下方式执行此操作(在本例中使用RxPy,但在任何Rx实现中概念上都相同):
mouse_long_press = mouse_down.delay(1000).take_until(mouse_up).repeat()
但是,当我们需要将mouse_down
可观察量的某些信息提升到mouse_up
可观察量时,就会出现问题。例如,考虑是否可观察元素存储有关按下哪个鼠标按钮的信息。显然,我们只想将mouse_down
与相应按钮的mouse_up
配对。我想出的一个解决方案是:
mouse_long_press = mouse_down.select_many(lambda x:
rx.Observable.just(x).delay(1000)\
.take_until(mouse_up.where(lambda y: x.button == y.button))
)
如果有更直接的解决方案,我很乐意听到它 - 但据我所知,这有效。但是,如果我们还想检测鼠标在mouse_down
和mouse_up
之间的移动距离,事情会变得更复杂。为此,我们需要引入一个新的可观察mouse_move
,其中包含有关鼠标位置的信息。
mouse_long_press = mouse_down.select_many(lambda x:
mouse_move.select(lambda z: distance(x, z) > 100).delay(1000)\
.take_until(mouse_up.where(lambda y: x.button == y.button))
)
然而,这几乎是我被卡住的地方。每当按下按钮的时间超过1秒时,我会得到一堆布尔值。但是,我只想在所有这些都是假的情况下检测到长按,这听起来像all operator的完美情况。感觉只有一小步缺失,但到目前为止我还没有弄清楚如何让它发挥作用。也许我也是以倒退的方式做事。期待任何建议。
答案 0 :(得分:1)
好的,我想我找到了一个可能的答案。 RxPy有一个take_with_time
运算符,可以用于此目的。并不像我希望的那样直截了当(不确定take_with_time
是否可以在其他Rx实现中使用。)
mouse_long_press = mouse_down.select_many(lambda x:
mouse_moves.take_with_time(1000).all(lambda z: distance(x, z) < 100)\
.take_until(mouse_up.where(lambda y: x.button == y.button))
)
如果有人提出更好的建议,我现在暂时打开这个问题。
答案 1 :(得分:0)
我将通过创建带有长度信息的鼠标印刷机流,并过滤长于1s的印刷机来解决该问题。
首先,我们假设您只有一个鼠标按钮。合并mouse_up
和mouse_down
流,并使用time_interval()
运算符在它们之间分配时间间隔。您将获得自上一次事件以来的间隔流,以及事件本身。假设您的鼠标上移和鼠标下移交替进行,则意味着您现在的事件是:
(下降+上次上升以来的时间),(上升+上次下降以来的时间),(下降+上次上升以来的时间)...
现在,只需过滤x.value.type == "up" and x.interval > datetime.timedelta(seconds=1)
(您也可以使用pairwise()
对此进行验证,它总是为您提供当前+上一个事件,因此您可以检查前一个事件是否已关闭而当前事件是否已发生。)
第二,使用window()
运算符添加鼠标移动信息。
(这部分未经测试,我将关闭有关其行为方式的文档,但该文档还不是很清楚。YMMV。)
这个想法是,您可以从一个可观察对象收集事件序列,并根据另一个可观察对象将它们分成几组。从文档:
window_openings
可观察值将是合并的上/下流或间隔流,以较方便的一个为准。然后,您可以flat_map()
(或select_many
,这是同一件事)的结果并以您喜欢的任何方式计算出距离。
同样,您应该最终获得上下事件之间的距离流。然后,您可以将此zip()
与间隔流一起group_by()
进行过滤,这时您可以过滤上移事件,并获得时间和距离,直到前一个下移。
第三,如果要获取多个鼠标按钮的事件怎么办?
只需使用Event = collections.NamedTuple("Event", "event interval distance")
def sum_distance(move_stream):
# put your distance calculation here; something like:
return move_stream.pairwise().reduce(lambda acc, (a, b): acc + distance(a, b), 0)
def mouse_press(updown_stream):
# shared stream for less duplication
shared = updown_stream.share()
intervals = shared.time_interval() # element is: (interval=timedelta, value=original event)
distances = mouse_move.window(shared).flat_map(sum_distance)
zipped = intervals.zip(distances, lambda i, d: \
Event(i.value, i.interval, d) )
mouse_long_press = (
# merge the mouse streams
rx.Observable.merge(mouse_up, mouse_down)
# separate into streams for each button
.group_by(lambda x: x.button)
# create per-button event streams per above and merge results back
.flat_map(mouse_press)
# filter by event type and length
.filter(lambda ev: ev.event.type == "up" and ev.interval >= datetime.timedelta(seconds=1)
)
运算符将其拆分为每个按钮的流,然后按上述步骤进行即可。
下面的完整代码:
{{1}}