I'm building a tide guide app. I have an array which contains the tide level each hour starting at 00:00 for 7 days. There is 168 elements in the array. (24 hours x 7 days).
As a human, you can look at each Double in order and recognize when the numbers start to increase or decrease, allowing you to find the peaks and valleys.
Based on how the tides work, I need the full 7 day schedule to be in one array initially. I plan to sort it into each day after finding the peaks and valleys and checking their index in the array. If their index is between 0 and 23, then we know its Day 1, and so forth.
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]
In this array, you can see the first valley is 2.8 a index 1. Then the numbers begin to increase until 4.6 at index 6 then they start to decrease. Here's a visual representation of the data:
I also have two empty arrays:
var highTides = [Int]()
var lowTides = [Int]()
Those arrays will need to contain the indexes of the peaks and valleys from the original array. Here is how the arrays need to look:
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
At the start of "arr" the numbers begin to descend, but that does not mean that index[0] is a peak. Same goes with the ending of "arr". The ending of "arr" begins to ascend, but that does nt mean that index[167] is a peak.
With any help you provide, please include a way to disregard peak or valley values from index 0 and 167. (Otherwise known as arr.first or arr.last)
I tried my best to explain what I need to do so if you have any other questions, let me know. Thank you:)
@NewDev's answer does not take into account areas of the graph like this:
[... 3.2, 3.3, 3.3, 3.4, ...]
(Notice how it starts ascending, flattens out, then ascends again.)
If you only check the value before and after the current value in your iteration, then in the above example the first 3.3
will be seen as a peak (because 3.3 >= 3.2 && 3.3 >= 3.3
), even though it's not a peak. Similarly, the second 3.3
will be seen as a valley (because 3.3 <= 3.3 && 3.3 <= 3.4
), even though it's not a valley.
Here's my solution, which solves this problem by keeping track of the last peak and valley starting indices, and treats peaks and valleys as ranges instead of single indices:
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)
}
}
Usage:
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($0) }
let lowTides = valleys.flatMap { Array($0) }
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]