如何消除累加器中的条件?

时间:2014-01-14 21:11:56

标签: c# linq system.reactive

鉴于此代码:

private static IObservable<Stock> ToStock(this IObservable<decimal> prices, string symbol)
{
    return prices.Scan(
        default(Stock),
        (previous, price) => previous == default(Stock)
            ? new Stock(symbol, price)
            : previous.Change(price));
}

// The signature for Stock.Change() looks like this. Stock is an immutable class.
// public Stock Change(decimal newCurrentPrice)

我想消除每次调用累加器时发生的检查previous == default(Stock)。我所拥有的是第一个项目与其他项目不同的行为。我不确定如何简单地使用LINQ for Rx来表达。

EDIT。这是Stock的代码,这可能有助于解释为什么我不能给它一个价格的哨兵值。

public class Stock
{
    private readonly decimal _current;
    private readonly decimal _dayHigh;
    private readonly decimal _dayLow;
    private readonly decimal _dayOpen;
    private readonly decimal _lastChange;
    private readonly string _symbol;

    public Stock(string symbol, decimal price)
    {
        if (symbol == null) throw new ArgumentNullException("symbol");
        if (price <= 0) throw new ArgumentOutOfRangeException("price", "Price must be greater than zero.");

        _symbol = symbol;
        _current = _dayOpen = _dayLow = _dayHigh = price;
    }

    private Stock(Stock original, decimal newCurrent)
    {
        if (original == null) throw new ArgumentNullException("original");

        _symbol = original.Symbol;
        _current = newCurrent;
        _dayOpen = original.DayOpen;
        _dayHigh = Math.Max(newCurrent, original.DayHigh);
        _dayLow = Math.Min(newCurrent, original.DayLow);
        _lastChange = newCurrent - original.Current;
    }

    public string Symbol { get { return _symbol; } }
    public decimal Current { get { return _current; } }
    public decimal LastChange { get { return _lastChange; } }
    public decimal DayOpen { get { return _dayOpen; } }
    public decimal DayLow { get { return _dayLow; } }
    public decimal DayHigh { get { return _dayHigh; } }
    public decimal DayChange { get { return Current - DayOpen; } }
    public double DayChangeRatio { get { return (double) Math.Round(DayChange/Current, 4); } }

    public Stock Change(decimal newCurrent)
    {
        return newCurrent == Current
            ? this
            : new Stock(this, newCurrent);
    }
}

4 个答案:

答案 0 :(得分:1)

我提出了这个解决方案:

private static IObservable<Stock> ToStock2(this IObservable<decimal> prices, string symbol)
{
    Func<Stock, decimal, Stock> accumulator = (_, firstPrice) =>
    {
        accumulator = (previous, price) => previous.Change(price);
        return new Stock(symbol, firstPrice);
    };

    return prices.Scan(default(Stock), (previous, price) => accumulator(previous, price));
}

它使用自变异Func变量来改变第一次调用时的行为,但快速测试(以50万的价格运行)表明它比原始方法慢了2-3%,并且代码不太清楚。似乎.NET在为每个项目执行相等比较器时效率更高,而对每个项目调用第二个Func。我不确定是否还有优化它,以便它比原始表现更好,以证明清晰度降低。

答案 1 :(得分:1)

你可以这样做:

public static partial class ObservableExtensions
{
    public static IObservable<Stock> ToStock(this IObservable<decimal> prices, string symbol)
    {                                                
        return Observable.Create<Stock>(o =>
        {
            Stock lastStock;            
            Action<decimal> action = null;
            action = price => {
                lastStock = new Stock(symbol, price);
                action = newPrice =>
                    {
                        lastStock = lastStock.Change(newPrice);
                        o.OnNext(lastStock);
                    };
                o.OnNext(lastStock);
            };

            return prices.Subscribe(p => action(p), o.OnError, o.OnCompleted);
        });    
    }   
}

与吉姆的回答相比,我不确定我的回答是否更好;这是一个类似的想法,但它避免调用扫描,这可能会避免一些跳跃。

我的flakey性能测试表明,这种情况并不比原版差 - 但也没有更好。我以100,000,000的价格运行了几次,他们相互之间的比例在1%之内,每次赢得大约一半的时间。没有统计学上的显着差异。

我会用一小撮盐来做这件事,因为这是在我的家用电脑上而不是在实验室环境中,不会运行很长时间并且上帝知道安装了哪些其他服务。

但是......通过重写私有构造函数以不冗余地进行Math.Max/Min计算,绕过属性并直接访问字段,我确实获得了显着提高3%的改进 - 我确信这是要探索的更多里程,例如删除Change和使用公共字段:

private Stock(Stock original, decimal newCurrent)
{
    if (original == null) throw new ArgumentNullException("original");

    _symbol = original._symbol;
    _current = newCurrent;
    _dayOpen = original._dayOpen;        
    if(newCurrent > original._dayHigh)
    {
        _dayHigh = newCurrent;
        _dayLow = original._dayLow;            
    }
    else
    {
        _dayHigh = original._dayHigh;
        _dayLow = newCurrent;          
    }
    _lastChange = newCurrent - original._current;
}

一般表现 - 价格很高,采用这种方法会产生相当大的GC压力。过去,我在使用数组实现的环形缓冲区中使用Stock实例池来减少垃圾收集,从而取得了成功。

答案 2 :(得分:0)

return prices.Skip(1)
             .Scan(new Stock(symbol, prices.First()),
               (previous, price) => previous.Change(price));

这是否可以解决您的副作用问题?

答案 3 :(得分:0)

我更愿意介绍某种多态性。您可以出于种子目的介绍股票的特例:

public class Stock {

  // same implementation as yours but make the Change method virtual

  public static Stock Seed(string symbol) {
    return new StockSeed(symbol);
  }

  class StockSeed : Stock {
    public StockSeed(string symbol) {
      _symbol = symbol;
    }

    public override Stock Change(decimal newCurrent) {
      return new Stock(Symbol, newCurrent)
    }
  }
}

然后您可以将反应代码简化为:

static IObservable<Stock> ToStock(this IObservable<decimal> prices, string symbol)
{
    return prices.Scan(Stock.Seed(symbol), (prev, price) => prev.Change(price));
}