Я новичок в реактивных расширениях (rx) и пытаюсь сделать следующее в .NET (но должно быть то же самое в JS и других языках.):

У меня есть поток с объектами, содержащими строку и свойство bool. Поток будет бесконечным. У меня есть следующие условия:

  • Первый объект всегда должен быть напечатан.
  • Теперь все входящие объекты следует пропускать до тех пор, пока не прибудет объект со свойством bool, установленным в "true".
  • Когда объект прибывает со свойством bool, установленным в «true», этот объект должен быть пропущен, но следующий объект должен быть напечатан (независимо от свойств).
  • Теперь так и происходит, каждый объект, следующий за объектом со свойством, установленным в true, должен быть напечатан.

Примере:

("one", false)--("two", true)--("three", false)--("four", false)--("five", true)--("six", true)--("seven", true)--("eight", false)--("nine", true)--("ten", false)

Ожидаемый результат:

"one"--"three"--"six"--"seven"--"eight"--"ten"

Помните, что «шесть» и «семь» были напечатаны, потому что они следуют за объектом со свойством, установленным в true, даже если их собственное свойство также установлено в «true».

Простая .NET программа для тестирования:

using System;
using System.Reactive.Linq;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        class Result
        {
            public bool Flag { get; set; }
            public string Text { get; set; }
        }

        static void Main(string[] args)
        {               
            var source =
               Observable.Create<Result>(f =>
               {
                   f.OnNext(new Result() { Text = "one", Flag = false });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "two", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "three", Flag = false });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "four", Flag = false });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "five", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "six", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "seven", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "eight", Flag = false });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "nine", Flag = true });
                   Thread.Sleep(1000);
                   f.OnNext(new Result() { Text = "ten", Flag = false });

                   return () => Console.WriteLine("Observer has unsubscribed");
               });
        }
    }
}

Я пытался использовать расширения .Scan и .Buffer, но не знаю, как именно их использовать в моем сценарии.

Производительность, конечно, должна быть как можно лучше, потому что в конце поток будет бесконечным.

2
Tobias von Falkenhayn 21 Авг 2018 в 11:44

4 ответа

Лучший ответ

Попробуйте такой подход:

var results = new[]
{
    new Result() { Text = "one", Flag = false },
    new Result() { Text = "two", Flag = true },
    new Result() { Text = "three", Flag = false },
    new Result() { Text = "four", Flag = false },
    new Result() { Text = "five", Flag = true },
    new Result() { Text = "six", Flag = true },
    new Result() { Text = "seven", Flag = true },
    new Result() { Text = "eight", Flag = false },
    new Result() { Text = "nine", Flag = true },
    new Result() { Text = "ten", Flag = false },
};

IObservable<Result> source =
    Observable
        .Generate(
            0, x => x < results.Length, x => x + 1,
            x => results[x],
            x => TimeSpan.FromSeconds(1.0));

Вышеуказанное просто создает source более идиоматическим способом, чем ваш Observable.Create<Result> подход.

Теперь вот query:

IObservable<Result> query =
    source
        .StartWith(new Result() { Flag = true })
        .Publish(ss =>
            ss
                .Skip(1)
                .Zip(ss, (s1, s0) =>
                    s0.Flag
                    ? Observable.Return(s1) 
                    : Observable.Empty<Result>())
                .Merge());

Использование .Publish здесь позволяет наблюдаемому источнику иметь только одну подписку, но его можно использовать несколько раз в методе .Publish. Тогда стандартный Skip(1).Zip подход может быть использован для проверки последующих производимых значений.

Вот результат:

output


После вдохновения от Шломо, вот мой подход с использованием .Buffer(2, 1):

IObservable<Result> query2 =
    source
        .StartWith(new Result() { Flag = true })
        .Buffer(2, 1)
        .Where(rs => rs.First().Flag)
        .SelectMany(rs => rs.Skip(1));
3
Enigmativity 21 Авг 2018 в 12:21

Вот несколько способов сделать это:

var result1 = source.Publish(_source => _source
    .Zip(_source.Skip(1), (older, newer) => (older, newer))
    .Where(t => t.older.Flag == true)
    .Select(t => t.newer)
    .Merge(_source.Take(1))
    .Select(r => r.Text)
);

var result2 = source.Publish(_source => _source
    .Buffer(2, 1)
    .Where(l => l[0].Flag == true)
    .Select(l => l[1])
    .Merge(_source.Take(1))
    .Select(l => l.Text)
);

var result3 = source.Publish(_source => _source
    .Window(2, 1)
    .SelectMany(w => w
        .TakeWhile((r, index) => (index == 0 && r.Flag) || index == 1)
        .Skip(1)
    )
    .Merge(_source.Take(1))
    .Select(l => l.Text)
);

var result4 = source
    .Scan((result: new Result {Flag = true, Text = null}, emit: false), (state, r) => (r, state.result.Flag))
    .Where(t => t.emit)
    .Select(t => t.result.Text);

Я неравнодушен к Скану, но на самом деле, до вас.

2
Shlomo 21 Авг 2018 в 11:47

Я нашел способ сделать это благодаря ответу @Picci:

Func<bool, Action<Result>> printItem = print =>
                {
                    return data => {
                        if(print)
                        {
                            Console.WriteLine(data.Text);
                        }
                    };
                };

var printItemFunction = printItem(true);

source.Do(item => printItemFunction(item))
      .Do(item => printItemFunction = printItem(item.Flag))
      .Subscribe();

Тем не менее, я не совсем уверен, что это лучший способ, поскольку мне кажется немного странным не использовать Subscribe (), а побочные эффекты. В конце я не только хочу напечатать значения, но и вызвать Webservice с ним.

0
Tobias von Falkenhayn 21 Авг 2018 в 11:25

Это способ, которым я бы кодировал его в TypeScript

const printItem = (print: boolean) => {
    return (data) => {
        if (print) {
            console.log(data);
        }
    };
}

let printItemFunction = printItem(true);

from(data)
.pipe(
    tap(item => printItemFunction(item.data)),
    tap(item => printItemFunction = printItem(item.printBool))
)
.subscribe()

Основная идея состоит в том, чтобы использовать функцию более высокого уровня printItem, которая возвращает функцию, которая знает, печатать ли и что. Возвращенная функция хранится в переменной printItemFunction.

Для каждого элемента, излучаемого исходной наблюдаемой, первое, что нужно сделать, - выполнить printItemFunction, передав данные, уведомленные исходной наблюдаемой.

Второе - оценить функцию printItem и сохранить результат в переменной printItemFunction, чтобы он был готов к следующему уведомлению.

В начале программы printItemFunction инициализируется true, так что первый элемент всегда печатается.

Я не знаком с C #, чтобы дать вам ответ для .NET

-1
Picci 21 Авг 2018 в 09:28
51944853