Files
ChWebsiteApp/CHDataManagement/Workouts/Sequence+Median.swift
2025-08-21 20:26:22 +02:00

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
}
}