10

I have large list of objects and I need to split them in a group of two elements for UI propouse.

Example:

[0, 1, 2, 3, 4, 5, 6]

Becomes an array with these four arrays

[[0, 1], [2, 3], [4, 5], [6]]

There are a ton of ways to split an array. But, what is the most efficient (least costly) if the array is huge.

5
  • 2
    I did not test the efficiency, but this contains a possible solution: stackoverflow.com/questions/26691123/…. Commented Jul 2, 2015 at 13:16
  • see this stackoverflow.com/questions/5731042/… Commented Jul 2, 2015 at 13:17
  • 3
    @heaphach: I don't think the Swift compiler accepts Java source code :) Commented Jul 2, 2015 at 13:18
  • Oh sry .. Looking at the tags could help :-D Commented Jul 2, 2015 at 14:26
  • See my answer for a similar question that shows up to 5 different ways to solve your problem. Commented Jun 29, 2017 at 12:54

7 Answers 7

5

If you want an array of subslices, you can use the split function to generate it using a closure that captures a state variable and increments it as it passes over each element, splitting only on every nth element. As an extension of Sliceable (Swift 2.0 only, would need to be a free function in 1.2):

extension Sliceable {
    func splitEvery(n: Index.Distance) -> [SubSlice] {
        var i: Index.Distance = 0
        return split(self) { _ in ++i % n == 0 }
    }
}

Subslices are very efficient in so much as they usually share internal storage with the original sliceable entity. So no new memory will be allocated for storing the elements - only memory for tracking the subslices' pointers into the original array.

Note, this will work on anything sliceable, like strings:

"Hello, I must be going"
    .characters
    .splitEvery(3)
    .map(String.init)

returns ["He", "lo", " I", "mu", "t ", "e ", "oi", "g"].

If you want to lazily split the array up (i.e. generate a sequence that only serves up subslices on demand) you could write it using anyGenerator:

extension Sliceable {
    func lazilySplitEvery(n: Index.Distance) -> AnySequence<SubSlice> {

        return AnySequence { () -> AnyGenerator<SubSlice> in
            var i: Index = self.startIndex
            return anyGenerator {
                guard i != self.endIndex else { return nil }
                let j = advance(i, n, self.endIndex)
                let r = i..<j
                i = j
                return self[r]
            }
        }
    }
}


for x in [1,2,3,4,5,6,7].lazilySplitEvery(3) {
    print(x)
}
// prints [1, 2, 3]
//        [4, 5, 6]
//        [7]
Sign up to request clarification or add additional context in comments.

Comments

4

If you're looking for efficiency, you could have a method that would generate each array of 2 elements lazily, so you'd only store 2 elements at a time in memory:

public struct ChunkGen<G : GeneratorType> : GeneratorType {

  private var g: G
  private let n: Int
  private var c: [G.Element]

  public mutating func next() -> [G.Element]? {
    var i = n
    return g.next().map {
      c = [$0]
      while --i > 0, let next = g.next() { c.append(next) }
      return c
    }
  }

  private init(g: G, n: Int) {
    self.g = g
    self.n = n
    self.c = []
    self.c.reserveCapacity(n)
  }
}

public struct ChunkSeq<S : SequenceType> : SequenceType {

  private let seq: S
  private let n: Int

  public func generate() -> ChunkGen<S.Generator> {
    return ChunkGen(g: seq.generate(), n: n)
  }
}

public extension SequenceType {
  func chunk(n: Int) -> ChunkSeq<Self> {
    return ChunkSeq(seq: self, n: n)
  }
}

var g = [1, 2, 3, 4, 5].chunk(2).generate()

g.next() // [1, 2]
g.next() // [3, 4]
g.next() // [5]
g.next() // nil

This method works on any SequenceType, not just Arrays.

For Swift 1, without the protocol extension, you've got:

public struct ChunkGen<T> : GeneratorType {

  private var (st, en): (Int, Int)
  private let n: Int
  private let c: [T]

  public mutating func next() -> ArraySlice<T>? {
    (st, en) = (en, en + n)
    return st < c.endIndex ? c[st..<min(en, c.endIndex)] : nil
  }

  private init(c: [T], n: Int) {
    self.c = c
    self.n = n
    self.st = 0 - n
    self.en = 0
  }
}

public struct ChunkSeq<T> : SequenceType {

  private let c: [T]
  private let n: Int

  public func generate() -> ChunkGen<T> {
    return ChunkGen(c: c, n: n)
  }
}

func chunk<T>(ar: [T], #n: Int) -> ChunkSeq<T> {
  return ChunkSeq(c: ar, n: n)
}

For Swift 3:

public struct ChunkIterator<I: IteratorProtocol> : IteratorProtocol {

  fileprivate var i: I
  fileprivate let n: Int

  public mutating func next() -> [I.Element]? {
    guard let head = i.next() else { return nil }
    var build = [head]
    build.reserveCapacity(n)
    for _ in (1..<n) {
      guard let x = i.next() else { break }
      build.append(x)
    }
    return build
  }

}

public struct ChunkSeq<S: Sequence> : Sequence {

  fileprivate let seq: S
  fileprivate let n: Int

  public func makeIterator() -> ChunkIterator<S.Iterator> {
    return ChunkIterator(i: seq.makeIterator(), n: n)
  }
}

public extension Sequence {
  func chunk(_ n: Int) -> ChunkSeq<Self> {
    return ChunkSeq(seq: self, n: n)
  }
}

var g = [1, 2, 3, 4, 5].chunk(2).makeIterator()

g.next() // [1, 2]
g.next() // [3, 4]
g.next() // [5]
g.next() // nil

1 Comment

How can I use this in Swift 1.2 without protocol extension?
3

Swift 2 Gist

let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

extension Array {
    func splitBy(subSize: Int) -> [[Element]] {
        return 0.stride(to: self.count, by: subSize).map { startIndex in
            let endIndex = startIndex.advancedBy(subSize, limit: self.count)
            return Array(self[startIndex ..< endIndex])
        }
    }
}

let chunks = arr.splitBy(5)

print(chunks) // [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12]]

Comments

3

The shortest solution (Swift 4), I have seen so far, is from a Gist:

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

}

Comments

0

You can use oisdk's awesome SwiftSequence framework. There is the chunk function which does exactly what you want:

[1, 2, 3, 4, 5].chunk(2)

[[1, 2], [3, 4], [5]]

Also there's a LOT more functions for sequences, you should definitely check it out.

You can have a look at his implementation of chunk here (It uses generators)

Comments

0

Maybe not the most efficient solution, but the most direct solution:

func toPairs(numbers:[Int])->[[Int]]
{
    var pairs:[[Int]]=[]
    var pair:[Int]=[]
    for var index=0;index<numbers.count;index++ {
        pair.append(numbers[index])
        if pair.count == 2 || index==numbers.count-1 {
            pairs.append(pair)
            pair=[]
        }
    }
    return pairs
}

var numbers=[0,1,2,3,4,5]

var pairs=toPairs(numbers)

print(pairs)

Output on my laptop:

[[0, 1], [2, 3], [4, 5]]
Program ended with exit code: 0

Comments

0

Alternatively, you can use reduce for this, but this is probably not the most efficient:

let res = a.reduce([[Int]]()) { (var acc: [[Int]], current: Int) in

    if acc.last != nil && acc.last?.count < 2 {
        var newLast = acc.last
        newLast?.append(current)
        acc.removeLast()

        acc.append(newLast!)
    } else {
        acc.append([current])
    }
    return acc
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.