如何在Swift中实现Haskell的splitEvery函数?

3

问题

let x = (0..<10).splitEvery( 3 )
XCTAssertEqual( x, [(0...2),(3...5),(6...8),(9)], "implementation broken" )

评论

我在计算范围内元素数量等方面遇到了问题...

extension Range
{
    func splitEvery( nInEach: Int ) -> [Range]
    {
        let n = self.endIndex - self.startIndex // ERROR - cannot invoke '-' with an argument list of type (T,T)
    }
}
1个回答

5

一个范围内的值属于ForwardIndexType,因此您只能通过advance()来提高它们的等级, 或计算distance(),但减法 - 未定义。前进量必须是对应类型T.Distance。因此,这是可能的实现:

extension Range {
    func splitEvery(nInEach: T.Distance) -> [Range] {
        var result = [Range]() // Start with empty array
        var from  = self.startIndex
        while from != self.endIndex {
            // Advance position, but not beyond the end index:
            let to = advance(from, nInEach, self.endIndex)
            result.append(from ..< to)
            // Continue with next interval:
            from = to
        }
        return result
    }
}

例子:

println( (0 ..< 10).splitEvery(3) )
// Output: [0..<3, 3..<6, 6..<9, 9..<10]

请注意,0 ..< 10 不是一个整数列表(或数组)。要将一个数组分割成子数组,您可以定义类似的扩展:
extension Array {
    func splitEvery(nInEach: Int) -> [[T]] {
        var result = [[T]]()
        for from in stride(from: 0, to: self.count, by: nInEach) {
            let to = advance(from, nInEach, self.count)
            result.append(Array(self[from ..< to]))
        }
        return result
    }
}

例子:

println( [1, 1, 2, 3, 5, 8, 13].splitEvery(3) )
// Output: [[1, 1, 2], [3, 5, 8], [13]]

更通用的方法是将所有可切片的对象进行分割。但是Sliceable是一个协议,无法扩展。相反,您可以定义一个函数,将可切片的对象作为第一个参数:

func splitEvery<S : Sliceable>(seq : S, nInEach : S.Index.Distance) -> [S.SubSlice] { 
    var result : [S.SubSlice] = []

    var from  = seq.startIndex
    while from != seq.endIndex {
        let to = advance(from, nInEach, seq.endIndex)
        result.append(seq[from ..< to])
        from = to
    }
    return result
}

请注意,此函数与上面定义的(扩展)方法完全无关。

示例:

println( splitEvery("abcdefg", 2) )
// Output: [ab, cd, ef, g]
println( splitEvery([3.1, 4.1, 5.9, 2.6, 5.3], 2) )
// Output: [[3.1, 4.1], [5.9, 2.6], [5.3]]

范围不支持切片,但是您可以定义一个单独的函数,接受范围参数:

func splitEvery<T>(range : Range<T>, nInEach : T.Distance) -> [Range<T>] { 
    var result : [Range<T>] = []

    var from  = range.startIndex
    while from != range.endIndex {
        let to = advance(from, nInEach, range.endIndex)
        result.append(from ..< to)
        from = to
    }
    return result
}

例子:

println( splitEvery(0 ..< 10, 3) )
// Output: [0..<3, 3..<6, 6..<9, 9..<10]

FANTASTIC - 一种通用实现! - kfmfe04
奇怪的是,在Playground中这个代码完美运行,但我在项目中编译时遇到了问题 - 我已经在这里发布了一个新条目:http://stackoverflow.com/q/26694296/975129 - kfmfe04
@kfmfe04:我稍微扩展了答案。如果您需要更多信息,请告诉我。 - Martin R
这些编辑非常有用 - 感谢您花时间详细说明。 - kfmfe04

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