我正在使用Reactive Extensions开发应用程序并遇到以下问题:
说我有两个观察者P和Q,我想建立第三个观察者R,如果P的两个值没有Q,则R输出0.如果在P之后出现Q,则R输出结果传递这些值的方法,如:
P0 Q0 -> R0 = f(P0,Q0)
P1 -> R1 = 0
P2 Q1 -> R2 = f(P2,Q1)
P3 -> R3 = 0
P4 -> R4 = 0
P5 Q2 -> R5 = f(P5,Q2)
(...)
并且值按以下顺序进入观察者:
P0 Q0 P1 P2 Q1 P3 P4 P5 Q2
感谢您的帮助。
答案 0 :(得分:1)
我想我有一个解决方案。
如果我认为您已定义以下内容:
IObservable<int> ps = ...;
IObservable<int> qs = ...;
Func<int, int, int> f = ...;
首先,我创建一个函数字典来计算最终值:
var fs = new Dictionary<string, Func<int, int, int?>>()
{
{ "pp", (x, y) => 0 },
{ "pq", (x, y) => f(x, y) },
{ "qp", (x, y) => null },
{ "qq", (x, y) => null },
};
“p”与“p”的每个组合“q”就在那里。
然后你可以像这样创建一个合并的observable:
var pqs =
(from p in ps select new { k = "p", v = p })
.Merge(from q in qs select new { k = "q", v = q });
我现在知道哪个序列产生了哪个值。
接下来,我发布组合列表,因为我不知道源可观察源是热还是冷 - 所以发布它们会使它们变热 - 然后我将已发布的observable分别跳过一个零和零。然后我知道每对值和它们来自的原始可观察量。然后很容易应用字典函数(过滤掉任何空值)。
这是:
var rs =
from kvv in pqs.Publish(_pqs =>
_pqs.Skip(1).Zip(_pqs, (pq1, pq0) => new
{
k = pq0.k + pq1.k,
v1 = pq1.v,
v0 = pq0.v
}))
let r = fs[kvv.k](kvv.v0, kvv.v1)
where r.HasValue
select r.Value;
这对你有用吗?
答案 1 :(得分:1)
一般的想法很简单:你合并P和Q,使用BufferWithCount(2)获取值对,然后根据你的逻辑处理对:
P.Merge(Q).BufferWithCount(2).Select(values =>
{
var first = values[0];
var second = values[1];
if (first is P && second is P ||
first is Q && second is Q)
{
return 0;
}
if (first is P)
{
return selector(first, second);
}
else // suppose Q, P is a valid sequence as well.
{
return selector(second, first);
}
});
现在困难的部分是合并P和Q,如果它们是不同的类型,然后在Select中区分它们。如果它们属于同一类型,你可以使用像Enigmativity提出的简单方法,即
var pqs =
(from p in ps select new { k = "p", v = p })
.Merge(from q in qs select new { k = "q", v = q });
现在困难的部分是,如果它们属于不同类型,要合并它们,我们需要一些常见的包装类型,例如,来自Haskell的Data.Either:
public abstract class Either<TLeft, TRight>
{
private Either()
{
}
public static Either<TLeft, TRight> Create(TLeft value)
{
return new Left(value);
}
public static Either<TLeft, TRight> Create(TRight value)
{
return new Right(value);
}
public abstract TResult Match<TResult>(
Func<TLeft, TResult> onLeft,
Func<TRight, TResult> onRight);
public sealed class Left : Either<TLeft, TRight>
{
public Left(TLeft value)
{
this.Value = value;
}
public TLeft Value
{
get;
private set;
}
public override TResult Match<TResult>(
Func<TLeft, TResult> onLeft,
Func<TRight, TResult> onRight)
{
return onLeft(this.Value);
}
}
public sealed class Right : Either<TLeft, TRight>
{
public Right(TRight value)
{
this.Value = value;
}
public TRight Value
{
get;
private set;
}
public override TResult Match<TResult>(
Func<TLeft, TResult> onLeft,
Func<TRight, TResult> onRight)
{
return onRight(this.Value);
}
}
}
有趣的是,System.Reactive.dll中已经有类似的Either类,不幸的是它是内部的,所以我们需要自己的实现。现在我们可以将P和Q都放入Either并继续解决(我已经将它推广了一下,所以你可以返回任何结果而不是int):
public static IObservable<TResult> SmartZip<TLeft, TRight, TResult>(
IObservable<TLeft> leftSource,
IObservable<TRight> rightSource,
Func<TLeft, TRight, TResult> selector)
{
return Observable
.Merge(
leftSource.Select(Either<TLeft, TRight>.Create),
rightSource.Select(Either<TLeft, TRight>.Create))
.BufferWithCount(2)
.Select(values =>
{
// this case was not covered in your question,
// but I've added it for the sake of completeness.
if (values.Count < 2)
{
return default(TResult);
}
var first = values[0];
var second = values[1];
// pattern-matching in C# is really ugly.
return first.Match(
left => second.Match(
_ => default(TResult),
right => selector(left, right)),
right => second.Match(
left => selector(left, right),
_ => default(TResult)));
});
}
这是一个针对所有这些可怕的丑陋东西的小型演示。
private static void Main(string[] args)
{
var psource = Observable
.Generate(1, i => i < 100, i => i, i => i + 1)
.Zip(Observable.Interval(TimeSpan.FromMilliseconds(10.0)), (i, _) => i);
var qsource = Observable
.Generate(1, i => i < 100, i => (double)i * i, i => i + 1)
.Zip(Observable.Interval(TimeSpan.FromMilliseconds(30.0)), (i, _) => i);
var result = SmartZip(
psource,
qsource,
(p, q) => q / p).ToEnumerable();
foreach (var item in result)
{
Console.WriteLine(item);
}
}
答案 2 :(得分:0)
如果我已正确理解你的问题,那么下面是一个可以处理这种情况的通用函数:
public static IObservable<T> MyCombiner<T>(IObservable<T> P, IObservable<T> Q, T defaultValue,Func<T,T,T> fun)
{
var c = P.Select(p => new { Type = 'P', Value = p })
.Merge(Q.Select(p => new { Type = 'Q', Value = p }));
return c.Zip(c.Skip(1), (a, b) =>
{
if (a.Type == 'P' && b.Type == 'P')
return new { Ok = true, Value = defaultValue };
if (a.Type == 'P' && b.Type == 'Q')
return new { Ok = true, Value = fun(a.Value, b.Value) };
else
return new { Ok = false, Value = default(T) };
}).Where(b => b.Ok).Select(b => b.Value);
}
答案 3 :(得分:0)
假设我们有两种方法
通过这种方法,问题几乎解决了。
IObservable<TP> P = // observer P
IObservable<TQ> Q = // observer Q
var PP = P.Without((prev, next) => 0, Q);
var PQ = P.Before(Q, (p,q) => f(p,q)); // apply the function
var ResultSecuence = PP.Merge(PQ);
以下是两种方法
public static class Observer
{
/// <summary>
/// Merges two observable sequences into one observable sequence by using the selector function
/// whenever the first observable produces an element rigth before the second one.
/// </summary>
/// <param name="first"> First observable source.</param>
/// <param name="second">Second observable source.</param>
/// <param name="resultSelector">Function to invoke whenever the first observable produces an element rigth before the second one.</param>
/// <returns>
/// An observable sequence containing the result of combining elements of both sources
/// using the specified result selector function.
/// </returns>
public static IObservable<TResult> Before<TLeft, TRight, TResult>(this IObservable<TLeft> first, IObservable<TRight> second, Func<TLeft, TRight, TResult> resultSelector)
{
var result = new Subject<TResult>();
bool firstCame = false;
TLeft lastLeft = default(TLeft);
first.Subscribe(item =>
{
firstCame = true;
lastLeft = item;
});
second.Subscribe(item =>
{
if (firstCame)
result.OnNext(resultSelector(lastLeft, item));
firstCame = false;
});
return result;
}
/// <summary>
/// Merges an observable sequence into one observable sequence by using the selector function
/// every time two items came from <paramref name="first"/> without any item of any observable
/// in <paramref name="second"/>
/// </summary>
/// <param name="first"> Observable source to merge.</param>
/// <param name="second"> Observable list to ignore.</param>
/// <param name="resultSelector">Function to invoke whenever the first observable produces two elements without any of the observables in the secuence produces any element</param>
/// <returns>
/// An observable sequence containing the result of combining elements
/// using the specified result selector function.
/// </returns>
public static IObservable<TResult> Without<TLeft, TResult>(this IObservable<TLeft> first, Func<TLeft, TLeft, TResult> resultSelector,params IObservable<object>[] second)
{
var result = new Subject<TResult>();
bool firstCame = false;
TLeft lastLeft = default(TLeft);
first.Subscribe(item =>
{
if (firstCame)
result.OnNext(resultSelector(lastLeft, item));
firstCame = true;
lastLeft = item;
});
foreach (var observable in second)
observable.Subscribe(item => firstCame = false);
return result;
}
}