Создайте скользящее среднее (и другие FIR-фильтры), используя ReactiveCocoa
Я все еще начинаю с ReactiveCocoa и концепций функционального реактивного программирования, так что, возможно, это глупый вопрос.
ReactiveCocoa, естественно, предназначен для реагирования на потоки данных в реальном времени, сенсорные события или ввод датчика акселерометра и т. Д.
Можно ли применять фильтры с конечной импульсной характеристикой в ReactiveCocoa простым, реактивным способом? Или, если нет, что было бы наименее уродливым хакерским способом сделать это? Как можно реализовать что-то вроде простого скользящего среднего?
Идеально ищет решение Swift 2 + RA4, но также интересуется, возможно ли это вообще в Objective C и RA2/RA3.
2 ответа
Что вам действительно нужно, так это некоторый буфер периодов, который будет хранить период значений в буфере и начинать отправку только после того, как буфер достигнет своей емкости (код ниже вдохновлен оператором 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
Сигнальный оператор - это то, что вы ищете. Вдохновленный ответом Энди Джейкобса, я придумал что-то вроде этого (простая реализация скользящего среднего):
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)
}
}