如何在 Swift 的数组中找到多个峰谷元素?

How can I find multiple peak and valley elements in an array in Swift?

我正在构建潮汐指南应用程序。我有一个数组,其中包含从 00:00 开始的每小时潮汐水位,持续 7 天。数组中有 168 个元素。 (24 小时 x 7 天)。

作为人类,您可以按顺序查看每个 Double 并识别数字何时开始增加或减少,从而找到波峰和波谷。

根据潮汐的运作方式,我需要将完整的 7 天时间表放在一个数组中。我计划在找到波峰和波谷并检查它们在数组中的索引后将其分类到每一天。如果他们的索引在 0 到 23 之间,那么我们知道它是第 1 天,依此类推。

let arr = [2.9, 2.8, 3.0, 3.5, 4.0, 4.4, 4.6, 4.4, 3.9, 3.1, 2.3, 1.4, 0.6, 0.3, 0.5, 1.1, 1.9, 2.8, 3.7, 4.3, 4.6, 4.5, 4.2, 3.8, 3.4, 3.1, 3.1, 3.3, 3.7, 4.1, 4.4, 4.4, 4.2, 3.5, 2.7, 1.9, 1.0, 0.4, 0.3, 0.6, 1.2, 2.1, 3.0, 3.9, 4.4, 4.7, 4.6, 4.3, 3.9, 3.5, 3.3, 3.3, 3.5, 3.8, 4.1, 4.3, 4.2, 3.8, 3.2, 2.4, 1.6, 0.9, 0.4, 0.4, 0.9, 1.4, 2.3, 3.2, 4.0, 4.5, 4.7, 4.6, 4.3, 3.9, 3.6, 3.4, 3.4, 3.5, 3.7, 3.9, 4.0, 3.9, 3.5, 2.9, 2.2, 1.5, 0.9, 0.6, 0.6, 1.1, 1.7, 2.5, 3.4, 4.1, 4.5, 4.6, 4.5, 4.3, 3.9, 3.6, 3.4, 3.3, 3.4, 3.6, 3.7, 3.7, 3.6, 3.2, 2.7, 2.1, 1.5, 1.0, 0.8, 1.0, 1.4, 2.0, 2.7, 3.5, 4.1, 4.5, 4.6, 4.5, 4.2, 3.9, 3.6, 3.3, 3.2, 3.3, 3.3, 3.4, 3.4, 3.3, 3.0, 2.6, 2.1, 1.7, 1.3, 1.2, 1.3, 1.7, 2.3, 2.9, 3.6, 4.1, 4.4, 4.5, 4.4, 4.1, 3.8, 3.4, 3.2, 3.0, 3.0, 3.1, 3.2, 3.2, 3.1, 2.9, 2.6, 2.3, 1.9, 1.6, 1.5, 1.6, 2.0, 2.5, 3.1, 3.6]

在这个数组中,您可以看到第一个谷是 2.8,索引 1。然后数字开始增加,直到索引 6 处的 4.6,然后开始减少。这是数据的可视化表示:

我还有两个空数组:

var highTides = [Int]()
var lowTides = [Int]()

这些数组需要包含原始数组中峰谷的索引。 以下是数组的外观:

var highTides = [6, 20, 30, 31, 45, 55, 70, 80, 95, 104, 105, 120, 129, 130, 145, 154, 155]
//Two indexes that are beside each other (eg 30 and 31) which have the same peak value of 4.4 must both be included in the `highTides` array

在 "arr" 开始时数字开始下降,但这并不意味着索引 [0] 是峰值。 "arr" 的结尾也是如此。 "arr"的结尾开始上升,但这并不意味着索引[167]是一个峰值。

在您提供的任何帮助下,请包括一种忽略索引 0 和 167 中的峰值或谷值的方法。(也称为 arr.first 或 arr.last)

我已尽力解释我需要做什么,如果您有任何其他问题,请告诉我。谢谢:)

@NewDev 的回答没有考虑这样的图表区域:

[... 3.2, 3.3, 3.3, 3.4, ...]

(注意它是如何开始上升、变平然后再次上升的。)

如果您只检查迭代中当前值前后的值,那么在上面的示例中,第一个 3.3 将被视为峰值(因为 3.3 >= 3.2 && 3.3 >= 3.3),即使这不是高峰。同样,第二个 3.3 将被视为山谷(因为 3.3 <= 3.3 && 3.3 <= 3.4),即使它不是山谷。

这是我的解决方案,它通过跟踪最后的峰值和谷值起始索引解决了这个问题,并将峰值和谷值视为范围而不是单个索引:

extension Array where Element: Comparable {
    var rangesOfPeaksAndValleys: (peaks: [ClosedRange<Int>], valleys: [ClosedRange<Int>]) {
        guard !isEmpty else { return ([], []) }

        var peaks = [ClosedRange<Int>]()
        var valleys = [ClosedRange<Int>]()

        var previousValue = self[0]
        var lastPeakStartingIndex: Int?
        var lastValleyStartingIndex: Int?

        for (index, value) in enumerated() {
            if value > previousValue {
                if let lastValleyStartingIndexUnwrapped = lastValleyStartingIndex {
                    valleys.append(lastValleyStartingIndexUnwrapped...index - 1)
                    lastValleyStartingIndex = nil
                }

                lastPeakStartingIndex = index
            } else if value < previousValue {
                if let lastPeakStartingIndexUnwrapped = lastPeakStartingIndex {
                    peaks.append(lastPeakStartingIndexUnwrapped...index - 1)
                    lastPeakStartingIndex = nil
                }

                lastValleyStartingIndex = index
            }

            previousValue = value
        }

        return (peaks, valleys)
    }
}

用法:

let arr: [Double] = [2.9, 2.8, 3.0, 3.5, 4.0, 4.4, 4.6, 4.4, 3.9, 3.1, 2.3, 1.4, 0.6, 0.3, 0.5, 1.1, 1.9, 2.8, 3.7, 4.3, 4.6, 4.5, 4.2, 3.8, 3.4, 3.1, 3.1, 3.3, 3.7, 4.1, 4.4, 4.4, 4.2, 3.5, 2.7, 1.9, 1.0, 0.4, 0.3, 0.6, 1.2, 2.1, 3.0, 3.9, 4.4, 4.7, 4.6, 4.3, 3.9, 3.5, 3.3, 3.3, 3.5, 3.8, 4.1, 4.3, 4.2, 3.8, 3.2, 2.4, 1.6, 0.9, 0.4, 0.4, 0.9, 1.4, 2.3, 3.2, 4.0, 4.5, 4.7, 4.6, 4.3, 3.9, 3.6, 3.4, 3.4, 3.5, 3.7, 3.9, 4.0, 3.9, 3.5, 2.9, 2.2, 1.5, 0.9, 0.6, 0.6, 1.1, 1.7, 2.5, 3.4, 4.1, 4.5, 4.6, 4.5, 4.3, 3.9, 3.6, 3.4, 3.3, 3.4, 3.6, 3.7, 3.7, 3.6, 3.2, 2.7, 2.1, 1.5, 1.0, 0.8, 1.0, 1.4, 2.0, 2.7, 3.5, 4.1, 4.5, 4.6, 4.5, 4.2, 3.9, 3.6, 3.3, 3.2, 3.3, 3.3, 3.4, 3.4, 3.3, 3.0, 2.6, 2.1, 1.7, 1.3, 1.2, 1.3, 1.7, 2.3, 2.9, 3.6, 4.1, 4.4, 4.5, 4.4, 4.1, 3.8, 3.4, 3.2, 3.0, 3.0, 3.1, 3.2, 3.2, 3.1, 2.9, 2.6, 2.3, 1.9, 1.6, 1.5, 1.6, 2.0, 2.5, 3.1, 3.6]

let (peaks, valleys) = arr.rangesOfPeaksAndValleys

// Convert the above arrays of `ClosedRange`s to arrays of `Int`s:
let highTides = peaks.flatMap { Array([=12=]) }
let lowTides = valleys.flatMap { Array([=12=]) }

print(highTides) // [6, 20, 30, 31, 45, 55, 70, 80, 95, 104, 105, 120, 129, 130, 145, 154, 155]
print(lowTides)  // [1, 13, 25, 26, 38, 50, 51, 62, 63, 75, 76, 87, 88, 101, 112, 126, 137, 151, 152, 162]