103 lines
3.5 KiB
Swift
103 lines
3.5 KiB
Swift
|
|
// Store values with indices to handle duplicates uniquely
|
|
private struct Entry<T: BinaryFloatingPoint>: Comparable {
|
|
let index: Int
|
|
let value: T
|
|
static func < (lhs: Entry<T>, rhs: Entry<T>) -> Bool {
|
|
lhs.value == rhs.value ? lhs.index < rhs.index : lhs.value < rhs.value
|
|
}
|
|
}
|
|
|
|
extension Sequence {
|
|
/// Applies a centered median filter to the sequence.
|
|
/// - Parameters:
|
|
/// - windowSize: The number of samples in the median filter window (should be odd for symmetric centering).
|
|
/// - transform: Closure to transform each element into a numeric value.
|
|
/// - Returns: An array of filtered elements (same type as input).
|
|
func medianFiltered<T: BinaryFloatingPoint>(windowSize: Int, transform: (Element) -> T) -> [Element] {
|
|
precondition(windowSize > 0, "Window size must be greater than zero")
|
|
let input = Array(self)
|
|
guard !input.isEmpty else { return [] }
|
|
|
|
var result: [Element] = []
|
|
result.reserveCapacity(input.count)
|
|
|
|
let halfWindow = windowSize / 2
|
|
|
|
for i in 0..<input.count {
|
|
let start = Swift.max(0, i - halfWindow)
|
|
let end = Swift.min(input.count - 1, i + halfWindow)
|
|
var window: [Entry<T>] = []
|
|
|
|
for j in start...end {
|
|
window.append(Entry(index: j, value: transform(input[j])))
|
|
}
|
|
|
|
window.sort()
|
|
|
|
// Median position
|
|
let medianIndex = window.count / 2
|
|
let medianValue = window[medianIndex].value
|
|
|
|
// Choose the element closest to the median
|
|
let closest = input[start...end]
|
|
.min(by: { abs(Double(transform($0) - medianValue)) < abs(Double(transform($1) - medianValue)) })!
|
|
|
|
result.append(closest)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
/// Default version when Element itself is BinaryFloatingPoint
|
|
func medianFiltered(windowSize: Int) -> [Element] where Element: BinaryFloatingPoint {
|
|
return self.medianFiltered(windowSize: windowSize, transform: { $0 })
|
|
}
|
|
|
|
/// Iterate over adjacent pairs of elements in the sequence, applying a transform closure.
|
|
/// - Parameter transform: A closure that takes two consecutive elements and returns a value of type T.
|
|
/// - Returns: An array of transformed values.
|
|
func adjacentPairs<T>(_ transform: (Element, Element) -> T) -> [T] {
|
|
var result: [T] = []
|
|
var iterator = self.makeIterator()
|
|
guard var prev = iterator.next() else { return [] }
|
|
while let current = iterator.next() {
|
|
result.append(transform(prev, current))
|
|
prev = current
|
|
}
|
|
return result
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
extension Array where Element: Comparable {
|
|
/// Binary search returning the index where `predicate` fails (insertion point).
|
|
func binarySearch(predicate: (Element) -> Bool) -> Int {
|
|
var low = 0
|
|
var high = count
|
|
while low < high {
|
|
let mid = (low + high) / 2
|
|
if predicate(self[mid]) {
|
|
low = mid + 1
|
|
} else {
|
|
high = mid
|
|
}
|
|
}
|
|
return low
|
|
}
|
|
|
|
/// Binary search exact element index if present.
|
|
func binarySearchExact(_ element: Element) -> Int? {
|
|
var low = 0
|
|
var high = count - 1
|
|
while low <= high {
|
|
let mid = (low + high) / 2
|
|
if self[mid] == element { return mid }
|
|
else if self[mid] < element { low = mid + 1 }
|
|
else { high = mid - 1 }
|
|
}
|
|
return nil
|
|
}
|
|
}
|