Ruby Tuesday: Slicing arrays

A friend writes, “So I want earch_slice, something like [1,2,3,4,5,6] / 2 == [[1,2],[3,4],[5,6]]. How do I get that in Swift?”

I recommended using a lazy sequence, consuming n indices at a  time.

extension Array {
    func sliced(by n: Int) -> AnySequence<ArraySlice<Element>> {
        assert(n > 0, "Slice must be positive integer")
        var idx = self.startIndex
        return AnySequence {
            return AnyGenerator {
                if idx >= self.endIndex { return nil }
                let nextEnd = idx.advancedBy(n, limit: self.endIndex)
                defer { idx = nextEnd }
                return self[idx ..< nextEnd]
            }
        }
    }
}

From there you can use for-in to produce [1, 2], [3, 4], [5, 6], etc:

let testArray = [1, 2, 3, 4, 5, 6]
for x in testArray.sliced(by: 2) { print(x) }

He responded, “Sure you can like [1,2,3,4,5,6].each_slice(2) and get sets of [1,2] but you have to push them somewhere, don’t you? Like tmp = []; [1,2,3,4,5,6].each_slice(2) {|x| tmp.push(x)}?

With Swift, you don’t really have to push a sequence anywhere. It’s more a promise of data than the data itself. Just hold onto it and use it when you need it.

If you want to force things and collect results into an array, you can initialize an array with a sequence:

let result = Array(testArray.sliced(by: count))

But what you get from this is actually an array of array slices, which is what my generator produces. To build an array of arrays instead, a little functional application converts each slice into an array:

let result = Array(testArray.sliced(by: count).map(Array.init))

According to ruby-doc.org, each_slice(n) returns groups of n items at a time with the final group containing the remainder. I based my odd cases on this behavior:

// Test odd slicing
print("test", Array(Array<String>().sliced(by: 1)))
for count in 1 ... 7 {
    let y = Array(testArray.sliced(by: count).map(Array.init))
    print(count, y)
}

 

2 Comments

  • extension CollectionType where SubSequence == Self {
        mutating func popFirst(n: Int) -> SubSequence {
            defer { self = dropFirst(n) }
            return prefix(n)
        }
    }
    
    extension CollectionType where SubSequence: CollectionType, SubSequence.SubSequence == SubSequence {
        func sliced(by n: Int) -> AnySequence {
            var rest = self[indices]
            return AnySequence(AnyGenerator { rest.isEmpty ? nil : rest.popFirst(n) })
        }
    }
  • extension CollectionType where Index: Strideable {
        func sliced(by n: Index.Stride) -> AnySequence {
            let starts = startIndex.stride(to: endIndex, by: n)
            let ends = [starts, starts].joinWithSeparator([endIndex])
            return AnySequence(
                zip(starts, ends.dropFirst()).lazy.map { self[$0..<$1] }
            )
        }
    }