Iterate over collection two at a time in Swift

前端 未结 7 893
花落未央
花落未央 2020-11-28 16:00

Say I have an array [1, 2, 3, 4, 5]. How can I iterate two at a time?

Iteration 1: (1, 2)
Iteration 2: (3, 4)
Iteration 3: (5, nil)
相关标签:
7条回答
  • 2020-11-28 16:15

    I personally dislike looping through half the list (mainly because of dividing), so here is how I like to do it:

    let array = [1,2,3,4,5];
    var i = 0;
    
    while i < array.count {
        var a = array[i];
        var b : Int? = nil;
        if i + 1 < array.count {
            b = array[i+1];
        }
        print("(\(a), \(b))");
    
        i += 2;
    }
    

    You loop through the array by incrementing by 2.

    If you want to have nil in the element, you need to use optionals.

    0 讨论(0)
  • 2020-11-28 16:17

    You can use a progression loop called stride(to:, by:) to iterate over your elements every n elements:

    let array = Array(1...5)
    
    let pairs = stride(from: 0, to: array.endIndex, by: 2).map {
        (array[$0], $0 < array.index(before: array.endIndex) ? array[$0.advanced(by: 1)] : nil)
    }   // [(.0 1, {some 2}), (.0 3, {some 4}), (.0 5, nil)]
    
    print(pairs)  // "[(1, Optional(2)), (3, Optional(4)), (5, nil)]\n"
    

    To iterate your collection subsequences instead of tuples:

    extension Collection {
        func unfoldSubSequences(limitedTo maxLength: Int) -> UnfoldSequence<SubSequence,Index> {
            sequence(state: startIndex) { start in
                guard start < self.endIndex else { return nil }
                let end = self.index(start, offsetBy: maxLength, limitedBy: self.endIndex) ?? self.endIndex
                defer { start = end }
                return self[start..<end]
            }
        }
    }
    

    let array = Array(1...5)
    for subsequence in array.unfoldSubSequences(limitedTo: 2) {
        print(subsequence)  // [1, 2] [3, 4] [5]
    }
    

    This would work on any kind of collection:

    let string = "12345"
    for substring in string.unfoldSubSequences(limitedTo: 2) {
        print(substring)  // "12" "34" "5"
    }
    
    0 讨论(0)
  • 2020-11-28 16:20

    You can use sequence() and the iterator's next() method to iterate over pairs of consecutive elements. This works for arbitrary sequences, not only arrays:

    let a = "ABCDE"
    
    for pair in sequence(state: a.makeIterator(), next: { it in
        it.next().map { ($0, it.next()) }
    }) {
        print(pair)
    }
    

    Output:

    ("A", Optional("B"))
    ("C", Optional("D"))
    ("E", nil)
    

    The “outer” it.next() yields the elements at even positions, or nil (in which case it.next().map { } evaluates to nil as well, and the sequence terminates). The “inner” it.next() yields the elements at odd positions or nil.

    As an extension method for arbitrary sequences:

    extension Sequence {
        func pairs() -> AnyIterator<(Element, Element?)> {
            return AnyIterator(sequence(state: makeIterator(), next: { it in
                it.next().map { ($0, it.next()) }
            }))
        }
    }
    

    Example:

    let seq = (1...).prefix(5)
    for pair in seq.pairs() { print(pair) }
    

    Note that the pairs are generated lazily, no intermediate array is created. If you want an array with all pairs then

    let pairs = Array([1, 2, 3, 4, 5].pairs())
    print(pairs) // [(1, Optional(2)), (3, Optional(4)), (5, nil)]
    

    does the job.

    0 讨论(0)
  • 2020-11-28 16:35

    This is not identically what was asked, but I use an extension on Sequence that generates an array of arrays chunking the original sequence by any desired size:

    extension Sequence {
        func clump(by clumpsize:Int) -> [[Element]] {
            let slices : [[Element]] = self.reduce(into:[]) {
                memo, cur in
                if memo.count == 0 {
                    return memo.append([cur])
                }
                if memo.last!.count < clumpsize {
                    memo.append(memo.removeLast() + [cur])
                } else {
                    memo.append([cur])
                }
            }
            return slices
        }
    }
    

    So [1, 2, 3, 4, 5].clump(by:2) yields [[1, 2], [3, 4], [5]] and now you can iterate through that if you like.

    0 讨论(0)
  • 2020-11-28 16:35

    Extension to split the array.

    extension Array {
       func chunked(into size: Int) -> [[Element]] {
          return stride(from: 0, to: count, by: size).map {
          Array(self[$0 ..< Swift.min($0 + size, count)]) }
       }
    }
    
    let result = [1...10].chunked(into: 2)
    
    0 讨论(0)
  • 2020-11-28 16:36

    One approach would be to encapsulate the array in a class. The return values for getting pairs of items would be optionals to protect against out-of-range calls.

    Example:

    class Pairs {
    
        let source = [1, 2, 3, 4, 5]  // Or set with init()
        var offset = 0
    
        func nextpair() -> (Int?, Int?) {
            var first: Int? = nil
            var second: Int? = nil
            if offset < source.count {
                first = source[offset]
                offset++
            }
            if offset < source.count {
                second = source[offset]
                offset++
            }
            return (first, second)
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题