Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: loop over array elements and access previous and next elements

Tags:

swift

In Swift, I want to loop over an array and compare each element to the previous and/or next. For each comparison I will either produce a new element or nothing. Is there "functional" way of doing this?

An example could be that I have an array of Int and want to find all "local minimums.

I could do it sequencially like this

let a = [ 1,2,2,3,5,4,2,5,7,9,5,3,8,10 ]
var i = 1
var r: [Int] = []

while i < a.count - 1 {
    if a[i] < a[i+1] && a[i] < a[i-1] {
        r.append(i)
    }
    i += 1
}

print(r)
// [6, 11]

I wonder if there is a more simple or direct way to do it.

like image 361
Nicolai Henriksen Avatar asked Mar 25 '18 13:03

Nicolai Henriksen


2 Answers

Generally, one can use dropFirst() and zip() to traverse over adjacent array elements in parallel. Here is a simple example which produces the array of increments between the array elements:

let a = [ 1, 2, 2, 3, 5, 4, 2, 5, 7, 9, 5, 3, 8, 10 ]

let diffs = zip(a.dropFirst(), a).map(-)
print(diffs)
// [1, 0, 1, 2, -1, -2, 3, 2, 2, -4, -2, 5, 2]

To compute the indices of local minima we can iterate over a, a.dropFirst() and a.dropFirst(2) in parallel. enumerated() is used to keep track of the array offsets, and flatMap() (renamed to compactMap() in Swift 4.1) is used to pick only those indices which correspond to a local minimum:

let a = [ 1, 2, 2, 3, 5, 4, 2, 5, 7, 9, 5, 3, 8, 10 ]

let localMins = zip(a.enumerated().dropFirst(), zip(a, a.dropFirst(2))).flatMap {
    $0.element < $1.0 && $0.element < $1.1 ? $0.offset : nil
}
print(localMins) // [6, 11]
like image 89
Martin R Avatar answered Sep 20 '22 15:09

Martin R


I was looking for a variation of the original Q that I hope might help someone else. I needed to map every item in the array while considering the previous and next values:

extension Sequence {
    var withPreviousAndNext: [(Element?, Element, Element?)] {
        let optionalSelf = self.map(Optional.some)
        let next = optionalSelf.dropFirst() + [nil]
        let prev = [nil] + optionalSelf.dropLast()
        return zip(self, zip(prev, next)).map {
            ($1.0, $0, $1.1)
        }
    }
}

And the not so pretty way to use that with original Q:

let a = [ 1,2,2,3,5,4,2,5,7,9,5,3,8,10 ]
let indices = a.enumerated().withPreviousAndNext.compactMap { values -> Int? in
    let (prev, cur, next) = values
    return (cur.1 < (prev?.1 ?? Int.min) && cur.1 < (next?.1 ?? Int.min)) ? cur.0 : nil
}
indices // [6,11]
like image 30
ccwasden Avatar answered Sep 19 '22 15:09

ccwasden