134

I get so confused about 2D arrays in Swift. Let me describe step by step. And would you please correct me if I am wrong.

First of all; declaration of an empty array:

class test{
    var my2Darr = Int[][]()
}

Secondly fill the array. (such as my2Darr[i][j] = 0 where i, j are for-loop variables)

class test {
    var my2Darr = Int[][]()
    init() {
        for(var i:Int=0;i<10;i++) {
            for(var j:Int=0;j<10;j++) {
                my2Darr[i][j]=18   /*  Is this correct?  */
            }
        }
    }
}

And Lastly, Editing element of in array

class test {
    var my2Darr = Int[][]()
    init() {
        ....  //same as up code
    }
    func edit(number:Int,index:Int){
        my2Darr[index][index] = number
        // Is this correct? and What if index is bigger
        // than i or j... Can we control that like 
        if (my2Darr[i][j] == nil) { ...  }   */
    }
}
ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Antiokhos
  • 2,944
  • 5
  • 23
  • 32
  • Are you having problems with your approach? – Alex Wayne Aug 04 '14 at 21:22
  • 5
    Just so you know, your entire second step can be reduced down to this `var my2DArray = Array(count: 10, repeatedValue: Array(count: 10, repeatedValue: 18))` And you should really upgrade to a newer beta. `Int[][]()` is no longer valid syntax. It's been changed to `[[Int]]()`. – Mick MacCallum Aug 04 '14 at 21:23
  • 1
    The 2D init using repeated values won't work. All the rows will point to the same sub-array, and thus no be uniquely writable. – hotpaw2 Aug 04 '14 at 21:26
  • I wrap a multidimensional array in a new struct for a better API. – Pranav Kasetti Aug 28 '21 at 13:20

12 Answers12

276

Define mutable array

// 2 dimensional array of arrays of Ints 
var arr = [[Int]]() 

OR:

// 2 dimensional array of arrays of Ints 
var arr: [[Int]] = [] 

OR if you need an array of predefined size (as mentioned by @0x7fffffff in comments):

// 2 dimensional array of arrays of Ints set to 0. Arrays size is 10x5
var arr = Array(count: 3, repeatedValue: Array(count: 2, repeatedValue: 0))

// ...and for Swift 3+:
var arr = Array(repeating: Array(repeating: 0, count: 2), count: 3)

Change element at position

arr[0][1] = 18

OR

let myVar = 18
arr[0][1] = myVar

Change sub array

arr[1] = [123, 456, 789] 

OR

arr[0] += 234

OR

arr[0] += [345, 678]

If you had 3x2 array of 0(zeros) before these changes, now you have:

[
  [0, 0, 234, 345, 678], // 5 elements!
  [123, 456, 789],
  [0, 0]
]

So be aware that sub arrays are mutable and you can redefine initial array that represented matrix.

Examine size/bounds before access

let a = 0
let b = 1

if arr.count > a && arr[a].count > b {
    println(arr[a][b])
}

Remarks: Same markup rules for 3 and N dimensional arrays.

Aron
  • 3,419
  • 3
  • 30
  • 42
Keenle
  • 12,010
  • 3
  • 37
  • 46
  • ok one dumy question: how we assign that array, In C we do like that: arr[i][j]=myVar; but in swift when i try to do same way I got this error " '[([(Int)])].Type' does not have a member named 'subscript' " – Antiokhos Aug 05 '14 at 09:56
  • If you have `arr` defined as in the answer then `myVar` should be Int, is it? – Keenle Aug 05 '14 at 10:25
  • yes it is int. And thank you very much for detailed answer.. now its clear:D – Antiokhos Aug 05 '14 at 10:40
  • Trying to figure out if I need to add anything else to the answer... Does latest update to the answer clarify what you've asked in the comment? – Keenle Aug 05 '14 at 10:44
  • Just a note, with Swift 3 the initialization has changed a little. The init call is now init(repeating:count:). – McCygnus Jul 08 '16 at 11:10
  • 7
    In Swift 3, for copy pasters: `var arr = Int(repeating: Int(repeating: 0, count: 2), count: 3)` – kar Jan 09 '17 at 11:53
  • In Swift 4: `var arr = Array(repeating: Array(repeating: 0, count: 2), count: 3)` – Rolf Staflin Dec 03 '17 at 11:42
  • 1
    In Swift 4.2: for example,3 row, 2 column, 3 * 2 `var arr = Array(count: 2, repeatedValue: Array(count: 3, repeatedValue: 0))` – Zgpeace Jul 20 '18 at 11:01
  • You may want to use ContiguousArray rather than Array. Array may give you an ordered list, not quite what you are expecting of an array type. From Apple's doc: "The ContiguousArray type is a specialized array that always stores its elements in a contiguous region of memory. This contrasts with Array, which can store its elements in either a contiguous region of memory or an NSArray instance if its Element type is a class or @objc protocol." – perpenso Aug 17 '19 at 07:50
  • 1
    in swift 5 they flip flopped the arugments it is `Array(repeating: Array(repeating: 0, count: 2), count: 3)` – odyth Sep 05 '19 at 04:42
  • If I create a 2D array using the first two approaches shown here, I get Index out of bounds error when I try to initialize the array with values. See here for a demo - https://swiftfiddle.com/366p4taxtnf23md7qispb3nyaq. Other approaches do work. I wish there would have been a way to specify the size in the first two approaches you mentioned. – Ram Patra Jun 26 '22 at 19:22
30

From the docs:

You can create multidimensional arrays by nesting pairs of square brackets, where the name of the base type of the elements is contained in the innermost pair of square brackets. For example, you can create a three-dimensional array of integers using three sets of square brackets:

var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]

When accessing the elements in a multidimensional array, the left-most subscript index refers to the element at that index in the outermost array. The next subscript index to the right refers to the element at that index in the array that’s nested one level in. And so on. This means that in the example above, array3D[0] refers to [[1, 2], [3, 4]], array3D[0][1] refers to [3, 4], and array3D[0][1][1] refers to the value 4.

Woodstock
  • 22,184
  • 15
  • 80
  • 118
28

Make it Generic Swift 4

struct Matrix<T> {
    let rows: Int, columns: Int
    var grid: [T]
    init(rows: Int, columns: Int,defaultValue: T) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: defaultValue, count: rows * columns) as! [T]
    }
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> T {
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}


var matrix:Matrix<Bool> = Matrix(rows: 1000, columns: 1000,defaultValue:false)

matrix[0,10] = true


print(matrix[0,10])
dimohamdy
  • 2,917
  • 30
  • 28
  • 1
    I adapted your answer to create a 2D toroidal array. Thanks a lot! https://gist.github.com/amiantos/bb0f313da1ee686f4f69b8b44f3cd184 – Brad Root Jun 08 '19 at 17:51
  • Useful but no need to forced cast of `[T]` it could be removed `as! [T]` – zeytin May 05 '21 at 18:19
  • Here are some thoughts. Would it be worthwhile to make this conform to `RandomAccessCollection` or is it enough just to access the underlying `grid` member? Is this a better fit for `precondition` instead of `assert`? I don't think you need the cast: `as! [T]` – Justin Meiners Feb 09 '22 at 01:38
22

You should be careful when you're using Array(repeating: Array(repeating: {value}, count: 80), count: 24).

If the value is an object, which is initialized by MyClass(), then they will use the same reference.

Array(repeating: Array(repeating: MyClass(), count: 80), count: 24) doesn't create a new instance of MyClass in each array element. This method only creates MyClass once and puts it into the array.

Here's a safe way to initialize a multidimensional array.

private var matrix: [[MyClass]] = MyClass.newMatrix()

private static func newMatrix() -> [[MyClass]] {
    var matrix: [[MyClass]] = []

    for i in 0...23 {
        matrix.append( [] )

        for _ in 0...79 {
            matrix[i].append( MyClass() )
        }
    }

    return matrix
}
Ian
  • 45
  • 5
Kimi Chiu
  • 2,103
  • 3
  • 22
  • 37
17

In Swift 4

var arr = Array(repeating: Array(repeating: 0, count: 2), count: 3)
// [[0, 0], [0, 0], [0, 0]]
nikolovski
  • 4,049
  • 1
  • 31
  • 37
garg
  • 2,651
  • 1
  • 24
  • 21
14

According to Apple documents for swift 4.1 you can use this struct so easily to create a 2D array:

Link: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Subscripts.html

Code sample:

struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]
    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: 0.0, count: rows * columns)
    }
    func indexIsValid(row: Int, column: Int) -> Bool {
        return row >= 0 && row < rows && column >= 0 && column < columns
    }
    subscript(row: Int, column: Int) -> Double {
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}
Roman Podymov
  • 4,168
  • 4
  • 30
  • 57
Kawe
  • 659
  • 8
  • 18
  • 1
    I like it. It's reminiscent of C pointer arithmetic. It would be better if rewritten using Generics though, so it would apply to 2-dimensional arrays of any data type. For that matter, you could use this approach to create arrays any arbitrary dimension. – Duncan C Sep 10 '18 at 23:51
  • 1
    @vacawama, cool, except your n-dimensional array has the same problem as all the solutions that populate the array using `Array(repeating:count:)`. See the comment I posted to your other answer. – Duncan C Sep 11 '18 at 00:39
7

Before using multidimensional arrays in Swift, consider their impact on performance. In my tests, the flattened array performed almost 2x better than the 2D version:

var table = [Int](repeating: 0, count: size * size)
let array = [Int](1...size)
for row in 0..<size {
    for column in 0..<size {
        let val = array[row] * array[column]
        // assign
        table[row * size + column] = val
    }
}

Average execution time for filling up a 50x50 Array: 82.9ms

vs.

var table = [[Int]](repeating: [Int](repeating: 0, count: size), count: size)
let array = [Int](1...size)
for row in 0..<size {
    for column in 0..<size {
        // assign
        table[row][column] = val
    }
}

Average execution time for filling up a 50x50 2D Array: 135ms

Both algorithms are O(n^2), so the difference in execution times is caused by the way we initialize the table.

Finally, the worst you can do is using append() to add new elements. That proved to be the slowest in my tests:

var table = [Int]()    
let array = [Int](1...size)
for row in 0..<size {
    for column in 0..<size {
        table.append(val)
    }
}

Average execution time for filling up a 50x50 Array using append(): 2.59s

Conclusion

Avoid multidimensional arrays and use access by index if execution speed matters. 1D arrays are more performant, but your code might be a bit harder to understand.

You can run the performance tests yourself after downloading the demo project from my GitHub repo: https://github.com/nyisztor/swift-algorithms/tree/master/big-o-src/Big-O.playground

Karoly Nyisztor
  • 3,505
  • 1
  • 26
  • 21
4

In Swift 5.5

It's just to understand how the 2D array can be mounted

enter image description here

import UIKit

var greeting = "Hello, playground"

var arrXY = [[String]]()

//             0   1   2   3
arrXY.append(["A","B","C","D"]) // 0
arrXY.append(["E","F","G","H"]) // 1
arrXY.append(["J","K","L","M"]) // 2
arrXY.append(["N","O","P","Q"]) // 3

print(arrXY)

print(arrXY[2][1]) // xy(2,1)
print(arrXY[0][3]) // xy(0,3)
print(arrXY[3][3]) // xy(3,3)
Claudio Silva
  • 3,743
  • 1
  • 26
  • 27
2

This can be done in one simple line.

Swift 5

var my2DArray = (0..<4).map { _ in Array(0..<) }

You could also map it to instances of any class or struct of your choice

struct MyStructCouldBeAClass {
    var x: Int
    var y: Int
}

var my2DArray: [[MyStructCouldBeAClass]] = (0..<2).map { x in
    Array(0..<2).map { MyStructCouldBeAClass(x: x, y: $0)}
}
pimisi
  • 409
  • 4
  • 5
0

Swift version 5.5

This version uses an underlying array of optionals to avoid runtime surprises that would be caused by the use of assertions.

struct Matrix<T> {
  let numberOfRows: Int
  let numberOfColumns: Int
  
  var elements: [T?]
  
  /// Creates a new matrix whose size is defined by the specified number of rows and columns.
  /// containing a single, repeated optional value.
  ///
  ///  - Parameters:
  ///    - repeatedValue: The element to repeat
  ///    - numberOfRows: The number of rows
  ///    - numberOfColumns: The number of columns
  init(repeating repeatedValue: T? = nil, numberOfRows: Int, numberOfColumns: Int) {
    // counts must be zero or greater.
    let numberOfRows = numberOfRows > 0 ? numberOfRows : 0
    let numberOfColumns = numberOfColumns > 0 ? numberOfColumns : 0
    
    self.numberOfRows = numberOfRows
    self.numberOfColumns = numberOfColumns
    self.elements = Array(repeating: repeatedValue, count: numberOfRows * numberOfColumns)
  }
  
  subscript(row: Int, column: Int) -> T? {
    get { elements[(row * numberOfColumns) + column] }
    set { elements[(row * numberOfColumns) + column] = newValue }
  }
}
Mycroft Canner
  • 1,828
  • 1
  • 11
  • 24
0

I would use a 1D array and just index it separately. For example consider using:

struct MatrixIndexer {
    var rows: Int
    var columns: Int
    
    var count: Int {
        return rows * columns
    }
    
    subscript(_ r: Int, _ c: Int) -> Int {
        precondition(r < rows && c < columns)
        return r * columns + c
    }
    
    func coordinate(_ i: Int) -> (row: Int, column: Int) {
        precondition(i < count)
        return (i / columns, i % columns)
    }
}

let at = MatrixIndexer(rows: 16, columns: 8)
var array2D: [Float] = Array(repeating: 0, count: at.count)

array2D[at[3, 4]] = 3
array2D[at[5, 2]] = 6

(If you want a single struct to pass around, you could introduce an Array2D which contains the MatrixIndexer and an Array).

Why is this a good idea? First, you get nicer operations out of the box such as:

  • Collection extensions: map, filter, reduce
  • iterate all elements in a singe loop
  • easy copying
  • Ensure the invariant that each row and column is the same size

Second, it is also much better for performance.

  • Faster access time by reducing memory allocation. Each element is contiguous with the next, instead of each row being a separate point off in memory.
  • Use one allocation and deallocation command instead of many (allocators are often slow for a variety of reasons)
Justin Meiners
  • 10,754
  • 6
  • 50
  • 92
0

It can sometimes help to make things simpler if you use a typealias for the underlying array type. Here is an example for a 2D array of String:

typealias Strings = [String]

let stringGroups: [Strings] // aka [[String]]
Benzy Neez
  • 1,546
  • 2
  • 3
  • 10