Учитывая 2 горячих наблюдаемых t1 и t2, как мне GoupJoin, чтобы я получал все события из t2, которые происходят за x секунд до и y секунд после каждого события в t1?

Данный:

T1 ----- A ----- B ----- C

T2 --1--2--3--4--5--6

Если интервал t1 составляет 2 секунды, а интервал t2 - одну секунду, и мы ищем события t2, которые находятся на 1 секунде с каждой стороны от каждого события t1, результатом будет следующий результат.

Результат:

{A, [1,2,3]}

{B, [3,4,5]}

{C, [5,6]}

Ниже приведен реальный пример, в котором нам нужно решение указанной выше проблемы. У нас есть поток электронных писем и еще один поток текстовых сообщений. Нам нужно выдать другой результат потока, в котором есть электронная почта и текстовые сообщения, произошедшие до или после 1 минуты времени отправки электронного письма.

2
Anuj Chauhan 3 Янв 2018 в 13:08

2 ответа

Лучший ответ

Проблема здесь (как упомянул Шломо) в том, что нам нужно открыть окно в t2 ДО того, как произойдет событие t1. К сожалению, это невозможно, потому что как только мы достигаем события в t1, мы уже прошли точку, в которой нам нужно открыть окно в t2.

Вместо этого мы можем сдвинуть t2 вперед во времени с помощью Delay (). Если мы компенсируем его на x (время до), мы можем переформулировать вопрос как "получить события в t2, которые происходят в окне, открывающемся в t1 и закрывающемся в {{X4}" } Для решения этой проблемы мы можем использовать GroupJoin.

var scheduler = new HistoricalScheduler();

var t1 = Observable.Interval(TimeSpan.FromMilliseconds(200), scheduler)
    .Select(l => (char)('A' + l));
var t2 = Observable.Interval(TimeSpan.FromMilliseconds(100), scheduler);

var x = TimeSpan.FromMilliseconds(100);     //before time
var y = TimeSpan.FromMilliseconds(100);     //after time

var delayedT2 = t2.Delay(x, scheduler);

var g = t1.GroupJoin(delayedT2 ,
    _ => Observable.Timer(x + y, scheduler),
    _ => Observable.Empty<Unit>(scheduler),
    (a, b) => new { a, b}
);

scheduler.Start();

Это дает результат:

{ A, [1,2] }
{ B, [3,4] }
{ C, [5,6] }

Этот результат все еще не совсем то, что вы ожидали. Это потому, что в вашем примере события t2 происходят точно в тот же момент, когда события t1. В этом случае событие t1 + y обрабатывается первым и закрывает окно до того, как событие t2 может быть включено. Это означает, что мы фактически получаем (t1-01:00) <= t1 < (t1 + 01:00). Например. Окно для A - 01: 0000 - 02.9999 ... поэтому 3, происходящее в 03:00, не включено.

Это можно сделать включительно, просто добавив одну отметку к нашему времени y

var y = TimeSpan.FromMilliseconds(100).Add(TimeSpan.FromTicks(1)); 
1
1adam12 11 Янв 2018 в 11:54

Ответ на дамп кода (с использованием 100 миллисекунд вместо 1 секунды):

var t1 = Observable.Interval(TimeSpan.FromMilliseconds(200))
    .Select(l => (char)('A' + l))
    .Delay(TimeSpan.FromMilliseconds(200));
var t2 = Observable.Interval(TimeSpan.FromMilliseconds(100))
    .Delay(TimeSpan.FromMilliseconds(100));

var x = TimeSpan.FromMilliseconds(100);     //before time
var y = TimeSpan.FromMilliseconds(100);     //after time

var g = t1.Timestamp().Join(t2.Timestamp(),
    c => Observable.Timer(y),
    i => Observable.Timer(x + y),
    (c, i) => new {GroupItem = c, RightItem = i}
)
    .Where(a =>
        (a.GroupItem.Timestamp > a.RightItem.Timestamp && a.GroupItem.Timestamp - a.RightItem.Timestamp <= x) //group-item came first
        || (a.GroupItem.Timestamp <= a.RightItem.Timestamp && a.RightItem.Timestamp - a.GroupItem.Timestamp <= y) // right-item came first, or exact timestamp match
    )
    .Select(a => new { GroupItem = a.GroupItem.Value, RightItem = a.RightItem.Value })
    .GroupBy(a => a.GroupItem, a => a.RightItem);

Пояснение: Join Все дело в «окнах». Поэтому, когда вы определяете соединение, вы должны думать об окне времени, которое открыто для каждого элемента из наблюдаемого слева и наблюдаемого справа. Однако наше окно здесь трудно понять: мы должны каким-то образом открыть окно для левого наблюдаемого времени X, прежде чем оно произойдет, а затем закрыть его на время Y после того, как оно произойдет.

Вместо того, чтобы делать невозможное, мы оставляем его открытым только на время Y после появления левого элемента, и позволяем определять окна правого элемента по времени X + Y. Однако это оставит нам предметы, которые не следует включать. Поэтому мы используем Where в метках времени, чтобы их отфильтровать.

Наконец, мы выбираем анонимные типы и временные метки и группируем их вместе.

Я не думаю, что GroupJoin - лучший вариант: вы закончите тем, что разберете группу и воссоздадите ее, как это сделал я ...

1
Shlomo 11 Янв 2018 в 14:41