使用ReactiveCocoa创建移动平均值(以及其他FIR滤波器)



我还没有开始了解ReactiveCocoa和函数式反应式编程概念,所以这可能是一个愚蠢的问题。

ReactiveCocoa似乎是自然设计的,可以对实时数据流、触摸事件或加速度计传感器输入等做出反应。

是否可以在ReactiveCocoa中以一种简单、反应的方式应用有限脉冲响应滤波器?或者,如果没有,做这件事最不难看的方式是什么?如何实现简单的移动平均线?

理想情况下,寻找Swift 2+RA4解决方案,但也感兴趣的是,在目标C和RA2/RA3中,这是否可行。

您实际需要的是某种周期缓冲区,它将缓冲一段时间的值,只有当缓冲区达到容量时才开始发送(下面的代码受到takeLast运算符的启发)

extension SignalType {
    func periodBuffer(period:Int) -> Signal<[Value], Error> {
        return Signal { observer in
            var buffer: [Value] = []
            buffer.reserveCapacity(period)
            return self.observe { event in
                switch event {
                case let .Next(value):
                    // To avoid exceeding the reserved capacity of the buffer, we remove then add.
                    // Remove elements until we have room to add one more.
                    while (buffer.count + 1) > period {
                        buffer.removeAtIndex(0)
                    }
                    buffer.append(value)
                    if buffer.count == period {
                        observer.sendNext(buffer)
                    }
                case let .Failed(error):
                    observer.sendFailed(error)
                case .Completed:
                    observer.sendCompleted()
                case .Interrupted:
                    observer.sendInterrupted()
                }
            }
        }
    }
}

基于此,您可以将其映射到任何您想要的算法

let pipe = Signal<Int,NoError>.pipe()
pipe.0
    .periodBuffer(3)
    .map { Double($0.reduce(0, combine: +))/Double($0.count) } // simple moving average
    .observeNext { print($0) }
pipe.1.sendNext(10) // does nothing
pipe.1.sendNext(11) // does nothing
pipe.1.sendNext(15) // prints 12
pipe.1.sendNext(7) // prints 11
pipe.1.sendNext(9) // prints 10.3333
pipe.1.sendNext(6) // prints 7.3333

您可能正在寻找scan信号运算符。受Andy Jacobs答案的启发,我想出了这样的东西(一个简单的移动平均实现):

  let (signal, observer) = Signal<Int,NoError>.pipe()
  let maxSamples = 3
  let movingAverage = signal.scan( [Int]() ) { (previousSamples, nextValue)  in 
    let samples : [Int] =  previousSamples.count < maxSamples ? previousSamples : Array(previousSamples.dropFirst())
    return samples + [nextValue]
  }
  .filter { $0.count >= maxSamples }
  .map { $0.average }
  movingAverage.observeNext { (next) -> () in
    print("Next: (next)")
  }
  observer.sendNext(1)
  observer.sendNext(2)
  observer.sendNext(3)
  observer.sendNext(4)
  observer.sendNext(42)

注意:我必须将average方法移到协议扩展中,否则编译器会抱怨表达式太复杂。我从这个答案中使用了一个很好的解决方案:

extension Array where Element: IntegerType {
    var total: Element {
        guard !isEmpty else { return 0 }
        return reduce(0){$0 + $1}
    }
    var average: Double {
        guard let total = total as? Int where !isEmpty else { return 0 }
        return Double(total)/Double(count)
    }
}

相关内容

  • 没有找到相关文章

最新更新