145 lines
4.5 KiB
Python
145 lines
4.5 KiB
Python
from collections import OrderedDict
|
|
from typing import Any, Callable, Optional, Tuple, TypeVar
|
|
|
|
from reactivex import Observable, abc
|
|
from reactivex.disposable import CompositeDisposable, SingleAssignmentDisposable
|
|
from reactivex.internal import noop
|
|
from reactivex.operators import take
|
|
|
|
_T1 = TypeVar("_T1")
|
|
_T2 = TypeVar("_T2")
|
|
|
|
|
|
def join_(
|
|
right: Observable[_T2],
|
|
left_duration_mapper: Callable[[Any], Observable[Any]],
|
|
right_duration_mapper: Callable[[Any], Observable[Any]],
|
|
) -> Callable[[Observable[_T1]], Observable[Tuple[_T1, _T2]]]:
|
|
def join(source: Observable[_T1]) -> Observable[Tuple[_T1, _T2]]:
|
|
"""Correlates the elements of two sequences based on
|
|
overlapping durations.
|
|
|
|
Args:
|
|
source: Source observable.
|
|
|
|
Return:
|
|
An observable sequence that contains elements
|
|
combined into a tuple from source elements that have an overlapping
|
|
duration.
|
|
"""
|
|
|
|
left = source
|
|
|
|
def subscribe(
|
|
observer: abc.ObserverBase[Tuple[_T1, _T2]],
|
|
scheduler: Optional[abc.SchedulerBase] = None,
|
|
) -> abc.DisposableBase:
|
|
group = CompositeDisposable()
|
|
left_done = False
|
|
left_map: OrderedDict[int, _T1] = OrderedDict()
|
|
left_id = 0
|
|
right_done = False
|
|
right_map: OrderedDict[int, _T2] = OrderedDict()
|
|
right_id = 0
|
|
|
|
def on_next_left(value: _T1):
|
|
nonlocal left_id
|
|
duration = None
|
|
current_id = left_id
|
|
left_id += 1
|
|
md = SingleAssignmentDisposable()
|
|
|
|
left_map[current_id] = value
|
|
group.add(md)
|
|
|
|
def expire():
|
|
if current_id in left_map:
|
|
del left_map[current_id]
|
|
if not len(left_map) and left_done:
|
|
observer.on_completed()
|
|
|
|
group.remove(md)
|
|
|
|
try:
|
|
duration = left_duration_mapper(value)
|
|
except Exception as exception:
|
|
observer.on_error(exception)
|
|
return
|
|
|
|
md.disposable = duration.pipe(take(1)).subscribe(
|
|
noop, observer.on_error, lambda: expire(), scheduler=scheduler
|
|
)
|
|
|
|
for val in right_map.values():
|
|
result = (value, val)
|
|
observer.on_next(result)
|
|
|
|
def on_completed_left() -> None:
|
|
nonlocal left_done
|
|
left_done = True
|
|
if right_done or not len(left_map):
|
|
observer.on_completed()
|
|
|
|
group.add(
|
|
left.subscribe(
|
|
on_next_left,
|
|
observer.on_error,
|
|
on_completed_left,
|
|
scheduler=scheduler,
|
|
)
|
|
)
|
|
|
|
def on_next_right(value: _T2):
|
|
nonlocal right_id
|
|
duration = None
|
|
current_id = right_id
|
|
right_id += 1
|
|
md = SingleAssignmentDisposable()
|
|
right_map[current_id] = value
|
|
group.add(md)
|
|
|
|
def expire():
|
|
if current_id in right_map:
|
|
del right_map[current_id]
|
|
if not len(right_map) and right_done:
|
|
observer.on_completed()
|
|
|
|
group.remove(md)
|
|
|
|
try:
|
|
duration = right_duration_mapper(value)
|
|
except Exception as exception:
|
|
observer.on_error(exception)
|
|
return
|
|
|
|
md.disposable = duration.pipe(take(1)).subscribe(
|
|
noop, observer.on_error, lambda: expire(), scheduler=scheduler
|
|
)
|
|
|
|
for val in left_map.values():
|
|
result = (val, value)
|
|
observer.on_next(result)
|
|
|
|
def on_completed_right():
|
|
nonlocal right_done
|
|
right_done = True
|
|
if left_done or not len(right_map):
|
|
observer.on_completed()
|
|
|
|
group.add(
|
|
right.subscribe(
|
|
on_next_right,
|
|
observer.on_error,
|
|
on_completed_right,
|
|
scheduler=scheduler,
|
|
)
|
|
)
|
|
return group
|
|
|
|
return Observable(subscribe)
|
|
|
|
return join
|
|
|
|
|
|
__all__ = ["join_"]
|