反向拉链收藏

时间:2018-10-02 21:05:01

标签: swift collections functional-programming zip

我正在寻找一个收藏夹的“反向拉链”。本质上意义:

let a = [1, 2, 3, 4, 5, 6, 7]
let (left, right) = a.reverseZip()

print(left)  // [1, 3, 5, 7] 
print(right) // [2, 4, 6]

我有一个似乎可行的实现(请参阅下面的答案),但是我有两个问题希望我们可以讨论:

  1. 这种操作的正确名称是什么?这与zip并非完全相反(我敢肯定,这种模式在另一种功能语言的其他地方也存在)
  2. 此方法是否有更优化的算法?

3 个答案:

答案 0 :(得分:2)

这是一个想法的种子。而不是返回数组,返回序列会更有效。

这是一个Array扩展名,它返回两个LazyMapSequence<StrideTo<Int>, Element>。好处是实际上不会生成序列。因为它们是lazy,所以按需提供它们。这很像reversed()Array上的工作方式。

extension Array {

    func reverseZip() -> (LazyMapSequence<StrideTo<Int>, Element>, LazyMapSequence<StrideTo<Int>, Element>) {

        let left = stride(from: 0, to: self.count, by: 2).lazy.map { self[$0] }
        let right = stride(from: 1, to: self.count, by: 2).lazy.map { self[$0] }

        return (left, right)
    }
}

示例:

let a = [1, 2, 3, 4, 5, 6, 7]
let (left, right) = a.reverseZip()

// iterate left
for i in left {
    print(i)
}
1
3
5
7
// turn them into arrays to print them
print(Array(left))
[1, 3, 5, 7]
print(Array(right))
[2, 4, 6]

我确信可以将其推广到比Array更通用的地方。留给读者练习。


如果要两个数组

如果您确实需要两个数组,则只需返回map的结果(取出lazy):

extension Array {

    func reverseZip() -> ([Element], [Element]) {

        let left = stride(from: 0, to: self.count, by: 2).map { self[$0] }
        let right = stride(from: 1, to: self.count, by: 2).map { self[$0] }

        return (left, right)
    }
}

答案 1 :(得分:1)

extension Collection {

    func reverseZip() -> ([Element], [Element]) {

        var left  = [Element]()
        var right = [Element]()

        let capacity = Double(self.count) / 2.0
        left .reserveCapacity(Int(ceil(capacity)))
        right.reserveCapacity(self.count / 2)

        for element in self {

            if left.count > right.count {
                right.append(element)
            } else {
                left.append(element)
            }
        }
        return (left, right)
    }
}

更新(18/10/18) 谢谢大家的帮助。似乎很多人都陷入了Stride,这绝对是一个好方法。我拒绝此方法有两个原因:

  1. 我希望将此功能保留为Collection的扩展名,而不是Array
  2. 严格来说,给定startIndex的{​​{1}}不一定为0,索引的进程不一定每次都增加1。

Martin R使用自定义迭代器的方法绝对看起来是正确的方法,因此我将其与WWDC 2018's implementation of "EveryOther"一起使用,以创建一个标准的顺序迭代器,该迭代器仅在每个while循环块中遍历两个元素,直到结束了。

Collection

毫无疑问,在一定程度上可以进一步改进并使用vacawama的extension Collection { func deinterleave() -> ([Element], [Element]) { let smallHalf = count / 2 let bigHalf = count - smallHalf var left = [Element]() var right = [Element]() left .reserveCapacity(bigHalf) right.reserveCapacity(smallHalf) let start = self.startIndex let end = self.endIndex var iter = start while iter != end { left.append(self[iter]) iter = index(after: iter) if iter == end { break } right.append(self[iter]) iter = index(after: iter) } return (left, right) } } let a = [1,2,3,4,5,6,7] print(a.deinterleave()) // ([1, 3, 5, 7], [2, 4, 6]) 实现,但是就目前而言,这已经满足了我的需要。

答案 2 :(得分:1)

基于vacawama的想法,即返回惰性集合而不是数组, 这是对具有任意跨度的任意集合的可能概括:

extension Collection {

    func makeStrideIterator(stride: Int) -> AnyIterator<Element> {
        precondition(stride > 0, "The stride must be positive")
        var it = makeIterator()
        var first = true
        return AnyIterator<Element> {
            if first {
                first = false
            } else {
                // Skip `stride - 1` elements:
                for _ in 1..<stride {
                    guard let _ = it.next() else { return nil }
                }
            }
            return it.next()
        }
    }
}

该方法返回一个AnyIterator(既是迭代器又是序列) 位置的收集元素

0, stride, 2*stride, ...

解压缩数组(或任何其他集合)可以通过

完成。
let a = [1, 2, 3, 4, 5]
let left = a.makeStrideIterator(stride: 2)
let right = a.dropFirst().makeStrideIterator(stride: 2)

for e in left { print(e) }
// 1, 3, 5
for e in right { print(e) }
// 2, 4

以下是从字符串中提取第三个字符的示例:

for c in "ABCDEFG".makeStrideIterator(stride: 3) {
    print(c)
}
// A D G

然后将解压缩为两个数组的特殊情况实现为

extension Collection {
    func unzip() -> ([Element], [Element]) {
        return (Array(makeStrideIterator(stride: 2)),
                Array(dropFirst().makeStrideIterator(stride: 2)))
    }
}

示例:

print([1, 2, 3, 4, 5].unzip())
// ([1, 3, 5], [2, 4])