如何计算RxJava中的移动平均线

时间:2013-12-28 06:29:41

标签: reactive-programming rx-java

在金融领域,我们通常需要从时间序列数据流中计算移动窗口聚合值,以移动平均值为例,说我们有以下数据流(T是时间戳,V是实际vlaue):

[T0,V0],[T1,V1],[T2,V2],[T3,V3],[T4,V4],[T5,V5],[T6,V6],[T7,V7],[T8,V8],[T9,V9],[T10,1V0],......

从我们获得的流中计算出移动平均线3:

avg([T0,V0],[T1,V1],[T2,V2]),
avg([T1,V1],[T2,V2],[T3,V3]),
avg([T2,V2],[T3,V3],[T4,V4]),
avg([T3,V3],[T4,V4],[T5,V5]),
avg([T4,V4],[T5,V5],[T6,V6]),...

要计算移动平均线,我们似乎可以通过以下方式来实现:

  1. 从原始流
  2. 构建一个Observable
  3. 通过将值汇总到组
  4. ,从原始流构建Observable
  5. 使用聚合运算符计算步骤2中Observable的最终结果。
  6. 步骤1和3实现起来很简单,但是,对于第2步,似乎当前的RxJava没有内置运算符来生成移动窗口组,窗口/ groupBy运算符似乎不适合这种情况,而我没有找到一种简单的方法来组成现有运营商的解决方案,任何人都可以建议如何以“优雅”的方式在RxJava中做到这一点?

2 个答案:

答案 0 :(得分:6)

RxJava版本:0.15.1

import java.util.List;                                                          
import rx.Observable;                                                           
import rx.util.functions.Action1;                                               

class Bar {                                                                     

    public static void main(String args[]) {                                    

        Integer arr[] = {1, 2, 3, 4, 5, 6}; // N = 6                            
        Observable<Integer> oi = Observable.from(arr);                          

        // 1.- bundle 3, skip 1                                                 
        oi.buffer(3, 1)                                                         
        /**                                                                     
         * 2.- take only the first X bundles                                    
         * When bundle 3, X = N - 2 => 4                                        
         * When bundle 4, X = N - 3 => 3                                        
         * When bundle a, X = N - (a-1)                                         
         */                                                                     
          .take(4)                                                              
        // 3.- calculate average                                                
          .subscribe(new Action1<List<Integer>>() {                             
            @Override                                                           
            public void call(List<Integer> lst) {                               
                int sum = 0;                                                    
                for(int i = 0; i < lst.size(); i++) {                           
                    sum += lst.get(i);                                          
                }                                                               

                System.out.println("MA(3) " + lst +                             
                                   " => " + sum / lst.size());                  
            }                                                                   
        });                                                                     

    }                                                                           

}  

示例输出:

  

MA(3)[1,2,3] =&gt; 2

     

MA(3)[2,3,4] =&gt; 3

     

MA(3)[3,4,5] =&gt; 4

     

MA(3)[4,5,6] =&gt; 5

答案 1 :(得分:2)

我会这样做:

public static Observable<Double> movingAverage(Observable<Double> o, int N) {
    return o.window(N, 1).flatMap(
        new Func1<Observable<Double>, Observable<Double>>() {
            public Observable<Double> call(Observable<Double> window) {
                return Observable.averageDoubles(window);
            }
        }
    );
}
  • 我使用window(发出Observables,它只消耗一定量的内存)而不是buffer(它会发出Lists,它会消耗每个项目的内存)。
  • 这是一个如何使用组合子运算符而不是编写自己的循环的示例,这是您在使用Observable时应该始终考虑的事情。

更新:如果您要过滤掉流末尾的窗口,这些窗口的元素少于n,您可以这样做:

def movingAverage(o: Observable[Double], n: Int): Observable[Double] = {
  class State(val sum: Double, val n: Int)
  o.window(n, 1).flatMap(win => 
    win.foldLeft(new State(0.0, 0))((s, e) => new State(s.sum + e, s.n + 1))
       .filter(s => s.n == n)
       .map(s => s.sum/s.n))
}

(我之所以选择Scala是因为编写时间较短,但在Java中,您也可以这样做,只需注意Scala的foldLeft在Java中称为reduce