2

I am trying to split array into pairs. I am able to split into consecutive pair but I want to split into pair which includes previous value as mentioned in Results

Logic to split in consecutive pair, which I tried.

extension Array {
func chunks(_ chunkSize: Int) -> [[Element]] {
    return stride(from: 0, to: self.count, by: chunkSize).map {
        Array(self[$0..<Swift.min($0 + chunkSize, self.count)])
    }
}

Array:

["1", "2", "3", "4", "5", "6", "7", "8", "9"]

Results:

[["1", "2"], ["2", "3"], ["3", "4"], ["4", "5"], ["6", "7"], ["7", "8"], ["8", "9"]]
pkamb
  • 33,281
  • 23
  • 160
  • 191
Santosh Singh
  • 765
  • 11
  • 27
  • What have you tried so far? – pavel Jan 21 '22 at 18:43
  • 2
    Does this answer your question? [Swift: loop over array elements and access previous and next elements](https://stackoverflow.com/questions/49476485/swift-loop-over-array-elements-and-access-previous-and-next-elements) – Tomerikoo Jan 21 '22 at 18:48
  • If the chunk size was 4, would the correct result be `[[1, 2, 3, 4], [4, 5, 6, 7], [7, 8, 9]]`? – Duncan C Jan 21 '22 at 19:11

3 Answers3

3

How about this:

let a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9]

let pairs = Array(zip(a, a.dropFirst())).map {[$0.0, $0.1] }

print(pairs)

That outputs

[[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9]]

Edit:

If you want arbitrary chunk-size, you could write the extension like this:

extension Array {
    func chunks(_ chunkSize: Int, includePartialChunk: Bool = true) -> [[Element]] {
        var indexes = Array<Int>(stride(from: 0, to: count, by: chunkSize - 1))
        if includePartialChunk,
           let last = indexes.last,
           last < count - 1 {
            indexes.append(count-1)
        }
        return zip(indexes, indexes.dropFirst()).map {Array(self[$0.0...$0.1])}
    }
}

Use the parameter includePartialChunk to tell the function if you want to include a "partial chunk" at the end when the array size is not an even multiple of the chunk-size. If true (The default) it returns a last chunk that is smaller than chunkSize but goes to the end of the array. If false, it only returns full-sized chunks, but will skip array elements at the end that don't fit into a full-sized chunk.

(I'll have to study Leo's UnfoldSequence version and see if I can adapt my code to that.)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 1
    A similar approach should work in most languages that support functional programming constructs (Kotlin, Haskell, etc.) – Duncan C Jan 21 '22 at 18:59
  • 1
    Oh, I missed the fact that you wanted to allow variable sized chunks. Is that a requirement for your problem? – Duncan C Jan 21 '22 at 19:09
  • for me, it will work with fixed size. – Santosh Singh Jan 21 '22 at 19:17
  • If you allow a variable chunkSize, it isn't clear what to do if the array count is not an even multiple of chunkSize. Do you stop when you wind up with chunks smaller than chunksize? If so you would not include the elements in the last, partial chunk in the result. Or do you return a final, partial chunk? – Duncan C Jan 21 '22 at 20:15
  • Actually in my case, I need pair sequence when array count is > 2. So, above solution is fine. – Santosh Singh Jan 21 '22 at 20:18
  • Leo, code is *impossible* to read in comments. Would you mind posting your version as a second answer? I'm not familiar with `UnfoldSequence`. – Duncan C Jan 21 '22 at 20:23
  • @DuncanC done [post](https://stackoverflow.com/a/70807337/2303865) – Leo Dabus Jan 21 '22 at 20:31
  • @DuncanC btw initializing a new array before mapping it is pointless `let pairs = zip(a, a.dropFirst()).map { [$0.0, $0.1] }` – Leo Dabus Jan 21 '22 at 21:44
  • @DuncanC btw I have updated the answers to support different lengths as well as to include the tail or not – Leo Dabus Jan 21 '22 at 23:11
3

Not a direct answer to the question but the elements of the sequence should be computed lazily. You should use Swift UnfoldSequence type as follow:

extension Collection {
    var unfoldedNeighbors: UnfoldSequence<SubSequence,Index> {
        sequence(state: startIndex) { start in
            guard start < endIndex else { return nil }
            guard let end = index(start, offsetBy: 2, limitedBy: endIndex) else {
                return nil
            }
            defer { formIndex(after: &start) }
            return self[start..<end]
        }
    }
    var neighborsSubsequences: [SubSequence] {
        .init(unfoldedNeighbors)
    }
    var neighborsArrays: [[Element]] {
        unfoldedNeighbors.map([Element].init)
    }
}

Usage:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for neighbors in numbers.unfoldedNeighbors {
    print(neighbors)
}

If you need control the number of elements of each subsequence and also if it includes the tail or not:

extension Collection {
    func unfoldedNeighbors(limitedTo length: Int, includesTail: Bool = false) -> UnfoldSequence<SubSequence,Index> {
        sequence(state: startIndex) { start in
            guard start < endIndex else { return nil }
            guard let end = index(start, offsetBy: length, limitedBy: endIndex) else {
                if includesTail {
                    defer { formIndex(&start, offsetBy: length-1, limitedBy: endIndex) }
                    return self[start...]
                }
                return nil
            }
            defer { formIndex(&start, offsetBy: length-1, limitedBy: endIndex) }
            return self[start..<end]
        }
    }
    func neighborsSequences(limitedTo length: Int, includesTail: Bool = false) -> [SubSequence] {
        .init(unfoldedNeighbors(limitedTo: length, includesTail: includesTail))
    }
    func neighborsArrays(limitedTo length: Int, includesTail: Bool = false) -> [[Element]] {
        unfoldedNeighbors(limitedTo: length, includesTail: includesTail).map([Element].init)
    }
}

Usage:

let numbers = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for neighbors in numbers.unfoldedNeighbors(limitedTo: 3, includesTail: true) {
    print(neighbors)
}

This will print:

[1, 2, 3]
[3, 4, 5]
[5, 6, 7]
[7, 8, 9]
[9, 10]


let neighborsSequences = a.neighborsSequences(limitedTo: 3, includesTail: true)  // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]] of type [Array<Int>.SubSequence]
let neighborsArrays = a.neighborsArrays(limitedTo: 3, includesTail: true)        // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]] of type [[Int]]
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

This operation goes by windows. It does exactly what you've requested, for count 2…

import Algorithms

Array(["1", "2", "3", "4", "5", "6", "7", "8", "9"].windows(ofCount: 2))

…but it's unclear if it does what you want for other counts.