在Swift中每次以两个为单位迭代集合

17

假设我有一个数组 [1, 2, 3, 4, 5]。如何每次迭代两个元素?

Iteration 1: (1, 2)
Iteration 2: (3, 4)
Iteration 3: (5, nil)
8个回答

31
您可以使用一个名为 stride(to:, by:) 的迭代循环来每隔 n 个元素遍历您的元素:
您可以使用名为 stride(to:, by:) 的循环来按照指定的步长n遍历列表中的元素:
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"

为了迭代您的集合子序列而不是元组:

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]
}

这可以适用于任何类型的集合:

let string = "12345"
for substring in string.unfoldSubSequences(limitedTo: 2) {
    print(substring)  // "12" "34" "5"
}

1
注意:元组的第一个元素不需要是可选的,因为输入数组只包含整数(Int),所以它永远不会为nil,因此您可以将其声明为[(Int, Int?)]。 - Leo Dabus
1
酷!那么对于无法通过整数索引的内容呢? - fumoboy007
@fumoboy007,我不知道你可以拥有一个无法通过整数进行索引的数组。 - Leo Dabus
1
其实算了。任何值得以两个为一组迭代的东西都会有一个可步进的索引。 - fumoboy007
如果我们不涉及泛型,为什么要使用“advanced(by :)”方法? - Paweł Brewczynski
1
@PaulBrewczynski,无论涉及到遗传学与否都没有任何区别。这只是个人偏好(语法)。如果这让你感到困扰,只需将其更改为+1即可。 - Leo Dabus

7
您可以使用sequence()和迭代器的next()方法来迭代连续元素对。这适用于任意序列,而不仅仅是数组:
let a = "ABCDE"

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

输出:

("A", 可选的("B"))
("C", 可选的("D"))
("E", 空)

“外部”it.next()生成偶数位置的元素或nil(在这种情况下,it.next().map {}也会评估为nil,序列终止)。 “内部”it.next()生成奇数位置或nil的元素。

作为任意序列的扩展方法:

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

例子:

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

请注意,这些配对是“惰性地”生成的,不会创建中间数组。如果您想要一个包含所有配对的数组,则可以使用以下方法:
let pairs = Array([1, 2, 3, 4, 5].pairs())
print(pairs) // [(1, Optional(2)), (3, Optional(4)), (5, nil)]

做好了。

1
当然,真正的目标是实现Ruby的each_slice,在这里我们不仅限于成对,而且可以将序列分成任意给定长度的子序列(三元组等)。 - matt
@matt:类似于 https://stackoverflow.com/q/27984914/1187415 的问题吗?我发布了这个答案,因为这里的请求是将“不完整”的块填充为 nil 而不是截断,但是确实非常相似。 - Martin R
太好了,我正希望你有这样的东西! - matt
非常干净!一个问题:在 it.next().map { } 中,由于 it.next() 返回可选项,难道不需要使用可选链吗? - fumoboy007
1
@fumoboy007 不是,这是 Optional.map - Martin R

6

虽然这不完全符合要求,但我使用了一个序列扩展来生成一个数组的数组,通过任何所需的大小对原始序列进行分块:

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
    }
}

所以[1, 2, 3, 4, 5].clump(by:2)会得到[[1, 2], [3, 4], [5]],现在您可以遍历它(如果您喜欢)。

正是我所需要的!谢谢。 - Dennis Calla

6

将数组拆分的扩展。

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)

4
如果数组有偶数个元素,你可以这样写:

代码示例:

for i in 0..<arr.count/2 {
    print(arr[2*i...2*i+1])
}

然而,并非总是如此。另外,nil并不总与数组中元素的类型兼容,比如在你的例子中(nilInt不兼容,只与Int?兼容)。
另一种解决方案是扩展Array并添加一个pair()方法,该方法返回一个元组(元组可以是异构的)。您可以使用pair来遍历数组中的所有对,或者您甚至可以进一步扩展Array结构并添加pairs()以返回元组的数组。请注意,由于元组中的第二个元素是可选的,因此您需要在使用之前将其解包。
extension Array {
    func pair(at i: Index) -> (Element, Element?) {
        return (self[i], i < self.count - 1 ? self[i+1] : nil)
    }

    func pairs() -> [(Element, Element?)] {
        guard !isEmpty else { return [] }
        var result = [(Element, Element?)]()
        for i in 0...arr.count/2 {
            result.append(self.pair(at: 2*i))
        }
        return result
    }
}

let arr = [1, 2, 3, 4, 5]

for i in 0...arr.count/2 {
    print(arr.pair(at: 2*i))
}

for pair in arr.pairs() {
    print(pair)
}

更新 上述两种解决方案都可以通过使用 map 而不是手动循环来简化:

let pairs = (0..<arr.count/2).map { (arr[$0*2], arr[$0*2+1]) }
print(pairs) // prints [(1, 2), (3, 4)]

或者,针对 Array 扩展:

extension Array {
    func pair(at i: Index) -> (Element, Element?) {
        return (self[i], i < self.count - 1 ? self[i+1] : nil)
    }

    func pairs() -> [(Element, Element?)] {
        guard !isEmpty else { return [] }
        return (0..<(arr.count/2 + arr.count%2)).map { pair(at: $0*2) }
    }
}

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

您可以扩展Collection,以便所有集合都可用此pair功能:
extension Collection {
    func pairs() -> [(Element, Element?)] {
        guard !isEmpty else { return [] }
        return (0..<count/2+count%2).map {
            let i1 = index(startIndex, offsetBy: $0*2)
            let i2 = index(after: i1)
            return (self[i1], i2 < endIndex ? self[i2] : nil)
        }
    }
}

为什么不直接使用 let i2 = index(after: i1) 呢? - Leo Dabus
1
是的,那会更简单,@LeoDabus - Cristik

4

我个人不喜欢循环遍历一半的列表(主要是因为要进行除法运算),所以这是我喜欢的做法:

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;
}

通过每次增加2的方式循环遍历数组。

如果您想在元素中使用nil,需要使用可选项。


你应该将a声明为let,因为它的值不会改变。 - Cristik

1

这是我的解决方案,使用了一个reduce和几个guard

extension Array {
  var touplesOfTwo: [(Element,Element?)] {
    self.reduce(into: [(Element,Element?)]()) {
      guard let last = $0.last else { $0.append( ($1,nil) ); return }
      let lastIndex = $0.count - 1
      guard let _ = last.1 else { $0[lastIndex].1 = $1; return }
      $0.append( ($1,nil) )
    }
  }
}
let list = [1,4,3,7,2,9,6,5]
let queues = list.map { $0 }
let touplesList = queues.touplesOfTwo
print("\(touplesList)")
// [(1, Optional(4)), (3, Optional(7)), (2, Optional(9)), (6, Optional(5))]

让队列等于列表映射 { $0 } 是毫无意义的。 - Leo Dabus
顺便提一下,Swift 是一种类型推断语言,reduce(into: []) - Leo Dabus
不需要使用两个守卫语句。guard let last = $0.last, last.1 == nil else { $0.append(($1,nil)) return } $0[$0.index(before: $0.endIndex)].1 = $1 - Leo Dabus

0
一种方法是将数组封装在一个类中。获取项目对的返回值将是可选项,以防止超出范围的调用。
示例:
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)
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接