How to implement Haskell's splitEvery in Swift?

前端 未结 1 1383
执念已碎
执念已碎 2020-12-07 03:36

PROBLEM

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

相关标签:
1条回答
  • 2020-12-07 04:17

    The values in a range are of ForwardIndexType, so you can only advance() them, or compute the distance(), but the subtraction - is not defined. The advance amount has to be of the corresponding type T.Distance. So this would be a possible implementation:

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

    Example:

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

    Note however that 0 ..< 10 is not a list (or array) of integers. To split an array into subarrays you could define a similar extension:

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

    Example:

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

    A more general approach could be to split all sliceable objects. But Sliceable is protocol and protocols cannot be extended. What you can do instead is to define a function that takes the sliceable object as the first argument:

    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
    }
    

    (Note that this function is completely unrelated to the (extension) methods defined above.)

    Example:

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

    Ranges are not sliceable, but you could define a separate function that takes a range argument:

    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
    }
    

    Example:

    println( splitEvery(0 ..< 10, 3) )
    // Output: [0..<3, 3..<6, 6..<9, 9..<10]
    
    0 讨论(0)
提交回复
热议问题