Ways of creating 2-dimensional array in Swift

What's the best practice when creating a 2-dimensional array in Swift?

My newbie approach to Swift is:

let list = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22]
let foobar = [[Int]]()
let row = -1

let (index, data) in list.enumerated() {
  if index % 3 == 0 {
    row += 1
    foobar.append([])
  }
  foobar[row].append(data)
}

Thought about a more elegant approach but the accumulated property is immutable and .reduce wouldn't work:

let x = list.reduce([]) { (acc, val) -> [[Int]] in
  if acc.count % 3 == 0 {
    acc.append([])
  }
  acc[acc.count].append(contentsOf: [val])
}

So, just wondering how people approach this in Swift,

Thanks!

1 Like
extension Array {
    func unflattening(dim: Int) -> [[Element]] {
        let hasRemainder = !count.isMultiple(of: dim)
        
        var result = [[Element]]()
        let size = count / dim
        result.reserveCapacity(size + (hasRemainder ? 1 : 0))
        for i in 0..<size {
            result.append(Array(self[i*dim..<(i + 1) * dim]))
        }
        if hasRemainder {
            result.append(Array(self[(size * dim)...]))
        }
        return result
    }
}

let list = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]
let matrix = list.unflattening(dim: 3)
1 Like

Here's a (little ugly) way which works if you use reduce(into:, _:):

let list = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]
let x = list.reduce(into: [[]]) { (r, i) in
  if r.isEmpty || r.last!.count == 3 { r.append([i]) }
  else { r[r.count-1].append(i) }
}
print(x) // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20, 21]]

But if you want a 2d array for eg a pixel image, you'd probably want to write your own type for that (allocating its own storage etc, for performance reasons). Conforming such a type to eg Collection wouldn't make much sense though (1-dimensional indices for a 2-dimensional collection, etc).

1 Like

Let me first say that it's possible you actually want to use your own data structure rather than nested array.

Now lets get to the fun part

let list = [...], rowCount = 3
var remaining = list[...]
var result = (0..<list.count / rowCount).map { _ -> [Int] in
    defer { remaining.removeFirst(rowCount) }
    return Array(remaining.prefix(rowCount))
}

// Handle the residual, you can remove this if you don't need it.
if !remaining.isEmpty {
    result.append(Array(remaining))
}

or

let list = [...], rowCount = 3
var remaining = list[...]
let result = (0..<(list.count + rowCount - 1) / rowCount).map { _ -> [Int] in
    defer { remaining = remaining.dropFirst(rowCount) }
    return Array(remaining.prefix(rowCount))
}
3 Likes
struct Matrix<Element> {
  private var _elements: [Element]

  // init
  // subscript
  // etc.
}
1 Like

This might be obvious, but n-dimensional arrays can be created from literal arrays:

let twoDimensional = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20, 21], [22]]
let threeDimensional = [[[1], [2]], [[3], [4], [5]]]

For square matrices, I wrote this struct for my Sudoku solver project, similar to what @nevin suggested:

The reasoning was to keep the data in a contiguous array, and then access rows, columns and sub-matrices, by index.

Rows can be easily extracted with swift ranges (as ArraySlice if needed). Columns can be easily extracted with stride skipping by number of elements in a row.

The new RangeSet introduced in Swift 5.3 will make this much better, allowing for accessing discontiguous data in the matrix without having to copy it into row/column arrays.

2 Likes

If what you are looking for is to split the Collection into subcollections of a given size e.g. [1,2,3,4,5,6,7] in 3 parts would become [[1, 2, 3], [4, 5, 6], [7]]. I think maybe an idea on how to do this would be a chunked collection e.g. Split Collection in chunks of given size proposal. But if you really want to represent a matrix I would think abstract in a Matrix<Element> class as others have mentioned is a good option :)

3 Likes

This is great! Thank you so much for sharing your knowledge :slight_smile: