Swift:将数组元素配对的最佳方法是什么?

15

我遇到了一个需要迭代数组成对的问题。最好的方法是什么?或者,作为替代方案,将数组转换为成对的数组,然后正常迭代它们的最佳方式是什么?

这是我找到的最佳方法。它需要output成为一个var,而且它不是很美观。有更好的方法吗?

let input = [1, 2, 3, 4, 5, 6]

var output = [(Int, Int)]()

for i in stride(from: 0, to: input.count - 1, by: 2) {
    output.append((input[i], input[i+1]))
}


print(output) // [(1, 2), (3, 4), (5, 6)]

// let desiredOutput = [(1, 2), (3, 4), (5, 6)]
// print(desiredOutput)

1
如果你的数组元素数量不是偶数怎么办?最后一个元素将会被丢弃。 - Leo Dabus
可以安全地假设元素数量为偶数。如果不是,则可以自行处理(抛出异常、致命错误、忽略单个元素等)。 - Alexander
5个回答

34
您可以将步幅映射而非迭代它,这允许将结果作为常量获取:

您可以 映射 步长而不是迭代它,这样可以将结果作为一个常量获得:

let input = [1, 2, 3, 4, 5, 6]

let output = stride(from: 0, to: input.count - 1, by: 2).map {
    (input[$0], input[$0+1])
}

print(output) // [(1, 2), (3, 4), (5, 6)]

如果您只需要迭代这些键值对且给定的数组很大,那么避免创建一个中间数组并使用惰性映射可能会更有优势:

for (left, right) in stride(from: 0, to: input.count - 1, by: 2)
    .lazy
    .map( { (input[$0], input[$0+1]) } ) {

    print(left, right)

}

1
太好了!在接受之前我会等一会儿,看看还有什么其他的东西出现。 - Alexander
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Martin R
1
@AhmadF:对集合使用 map 方法会返回一个包含所有映射元素的 数组,甚至在开始迭代之前就已经完成了。lazy 关键字则创建了一个“惰性序列”,在这个序列上实现了 map 方法的惰性加载:每个元素都是在迭代期间按需获取和映射的。这样做虽然更耗费计算资源,但可以节省内存。 - Martin R
更新这样的问题在SO上的礼仪是什么?现在有一个官方解决方案来回答这个问题,我想指引人们去使用它而不是手动编写的版本。https://dev59.com/iVkR5IYBdhLWcg3wygDp#68187239 - Alexander
1
@Alexander:这个问题不会改变,对吗?但是当然你可以选择接受你的答案 - Martin R
显示剩余5条评论

9

不确定相邻对与您的问题有什么关系,原本是获取每两个元素[(1, 2), (3, 4), (5, 6)]。顺便问一下,您确定chunks(ofCount:)也会返回相邻对[[1, 2],[2, 3],[3, 4],[4, 5]]吗? - Leo Dabus
1
哦,没错,我搞混了。撤销。 - Alexander

5

我认为这并不比Martin R的更好,但似乎OP需要其他东西...

struct PairIterator<C: IteratorProtocol>: IteratorProtocol {
    private var baseIterator: C
    init(_ iterator: C) {
        baseIterator = iterator
    }

    mutating func next() -> (C.Element, C.Element)? {
        if let left = baseIterator.next(), let right = baseIterator.next() {
            return (left, right)
        }
        return nil
    }
}
extension Sequence {
    var pairs: AnySequence<(Self.Iterator.Element,Self.Iterator.Element)> {
        return AnySequence({PairIterator(self.makeIterator())})
    }
}

input.pairs.forEach{ print($0) }

let output = input.pairs.map{$0}
print(output) //->[(1, 2), (3, 4), (5, 6)]

小建议(肯定是个人偏好问题):使用Array(input.pairs)代替...map{$0},在Self.Iterator.Element中可以省略Self.。- 特别感谢您在下一个方法中使用guard :) - Martin R
1
@MartinR,你能解释一下在next方法中使用guard的问题吗? - Alexander
MartinR的解决方案很好用,我只是想看看其他人在接受他的答案之前还有什么其他的想法。这个解决方案是我考虑的另一个方案。感谢你实现它 :) - Alexander
1
@AlexanderMomchliov:我不是一个总是使用guard进行早期返回的粉丝。这是个人口味问题,不要太认真对待我的评论。这里有一个最近的例子,我认为if / else if / else会更容易阅读:http://stackoverflow.com/questions/40835332/how-does-this-function-calculate. - Martin R
@MartinR 是的,那个问题让我重新考虑了对guard的立场。 - Alexander

4
这是@OOPer的答案的版本,适用于您列表中的元素数为奇数的情况。当然,如果您愿意,可以省略对CustomStringConvertible的一致性要求。但它会为这个例子提供更漂亮的输出。:)
struct Pair<P: CustomStringConvertible>: CustomStringConvertible {
    let left: P
    let right: P?

    var description: String {
        if let right = right {
            return "(\(left.description), \(right.description)"
        }
        return "(\(left.description), nil)"
    }
}

struct PairIterator<C: IteratorProtocol>: IteratorProtocol where C.Element: CustomStringConvertible {
    private var baseIterator: C
    init(_ iterator: C) {
        baseIterator = iterator
    }

    mutating func next() -> Pair<C.Element>? {
        if let left = baseIterator.next() {
            return Pair(left: left, right: baseIterator.next())
        }
        return nil
    }
}
extension Sequence where Element: CustomStringConvertible {
    var pairs: AnySequence<Pair<Self.Element>> {
        return AnySequence({PairIterator(self.makeIterator())})
    }
}

let input: [Int] = [1,2,3,4,5,6,7]
print(input.pairs)
print(Array(input.pairs))


//output:
AnySequence<Pair<Int>>(_box: Swift._SequenceBox<Swift._ClosureBasedSequence<__lldb_expr_27.PairIterator<Swift.IndexingIterator<Swift.Array<Swift.Int>>>>>)
[(1, 2, (3, 4, (5, 6, (7, nil)]

现在我们有了条件一致性,你应该将 CustomStringConvertible 的一致性移动到条件中。 - Alexander

3

您不需要像上面的答案中建议的那样自定义类型,比如PairIterator。获取成对的序列只需一行代码:

let xs = [1, 2, 3]
for pair in zip(xs, xs.dropFirst()) {
    print(pair) // (1, 2) (2, 3)
}

如果你想要重复使用它,可以在扩展中放置一个pairs方法:
extension Sequence {
    func pairs() -> AnySequence<(Element, Element)> {
        AnySequence(zip(self, self.dropFirst()))
    }
}

1
如果你经常这样做,PairIterator可能会很有用,尽管名称不太明确,因为它没有描述配对是如何完成的。一种方法是你展示的那种方法,它对于在栅栏段之间放置栅栏柱非常有用。我在问题中询问的方式是不同的,期望的输出是[(1, 2), (3, 4), (5, 6)],例如从数组中呈现一个2x3项目网格。 - Alexander

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