Я новичок в реактивных расширениях (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
, но не знаю, как именно их использовать в моем сценарии.
Производительность, конечно, должна быть как можно лучше, потому что в конце поток будет бесконечным.
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
подход может быть использован для проверки последующих производимых значений.
Вот результат:
После вдохновения от Шломо, вот мой подход с использованием .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));
Вот несколько способов сделать это:
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);
Я неравнодушен к Скану, но на самом деле, до вас.
Я нашел способ сделать это благодаря ответу @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 с ним.
Это способ, которым я бы кодировал его в 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
Новые вопросы
c#
C # (произносится как «резкий») - это высокоуровневый, статически типизированный язык программирования с несколькими парадигмами, разработанный Microsoft. Код C # обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, включая, среди прочего, .NET Framework, .NET Core и Xamarin. Используйте этот тег для вопросов о коде, написанном на C # или в формальной спецификации C #.