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

前端 未结 7 2457
庸人自扰
庸人自扰 2021-02-20 14:19

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 \"funct

相关标签:
7条回答
  • 2021-02-20 14:48

    I think Martin R's answer is smart, though I try answering using the other way.

    let a = [ 1,2,2,3,5,4,2,5,7,9,5,3,8,10 ]
    
    func withPrevAndNext<T, U>(`default`: U, _ f: @escaping (T, T, T) -> (U)) -> (T) -> (U) {
        var previous: T?
        var current: T?
    
        return { next in
            defer { (previous, current) = (current, next) }
            guard let prev = previous, let curt = current else { return `default` }
            return f(prev, curt, next)
        }
    }
    
    let r = a.enumerated().compactMap(withPrevAndNext(default: .none) { prev, curt, next -> Int? in
        curt.1 < prev.1 && curt.1 < next.1 ? curt.0 : .none
    })
    
    print(r)
    // [6, 11]
    
    0 讨论(0)
  • 2021-02-20 14:53

    Using flatMap

    let a = [ 1,2,2,3,5,4,2,5,7,9,5,3,8,10 ]
    let r = a.enumerated().flatMap { (_ offset: Int, _ element: Int) -> Int? in
        guard offset > 0 else { return nil }
        if element < a[offset-1] && element < a[offset+1] {
            return offset
        }
        return nil
    }
    
    0 讨论(0)
  • 2021-02-20 14:59

    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]
    
    0 讨论(0)
  • 2021-02-20 15:00

    You could also iterate over indices and compare like this,

    for i in a.indices.dropFirst().dropLast()
    {
        if a[i] < a[a.index(after: i)],
                a[i] < a[a.index(before: i)] {
            r.append(i)
        }
    }
    print(r)
    // [6, 11]
    

    Or, something like this,

    let result = a.indices.dropLast().dropFirst().filter { i in
        return a[i] < a[a.index(after: i)] &&
                a[i] < a[a.index(before: i)]
    }
    print(r)
    // [6, 11]
    

    Or, short,

    let result = a.indices.dropLast()
                          .dropFirst()
                          .filter { a[$0] < a[$0 + 1] &&
                                    a[$0] < a[$0 - 1] }
     print(result)
    
    0 讨论(0)
  • 2021-02-20 15:02

    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]
    
    0 讨论(0)
  • 2021-02-20 15:06

    You can replace the while loop and i with a for loop and stride.

    let a = [ 1,2,2,3,5,4,2,5,7,9,5,3,8,10 ]
    var r: [Int] = []
    
    for i in stride(from: 1, to: a.count - 1, by: 1) {
        if a[i] < a[i+1] && a[i] < a[i-1] {
            r.append(i)
        }
    }
    
    print(r)
    // [6, 11]
    

    You can get real fancy with a filter but this isn't nearly as readable as the above code:

    let a = [ 1,2,2,3,5,4,2,5,7,9,5,3,8,10 ]
    let r = a.enumerated().dropFirst().dropLast().filter { $0.1 < a[$0.0 + 1] && $0.1 < a[$0.0 - 1] }.map { $0.0 }
    print(r)
    // [6, 11]
    
    0 讨论(0)
提交回复
热议问题