Add route files, show overview
This commit is contained in:
102
CHDataManagement/Workouts/Sequence+Median.swift
Normal file
102
CHDataManagement/Workouts/Sequence+Median.swift
Normal file
@@ -0,0 +1,102 @@
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user