Two-dimensional array in Swift

ArraysSwift

Arrays Problem Overview


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) { ...  }   */
    }
}

Arrays Solutions


Solution 1 - Arrays

##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.

Solution 2 - Arrays

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.

Solution 3 - Arrays

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

Solution 4 - Arrays

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
}

Solution 5 - Arrays

In Swift 4

var arr = Array(repeating: Array(repeating: 0, count: 2), count: 3)
// [[0, 0], [0, 0], [0, 0]]

Solution 6 - Arrays

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

Solution 7 - Arrays

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

Solution 8 - Arrays

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

Solution 9 - Arrays

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

Solution 10 - Arrays

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: m.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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAntiokhosView Question on Stackoverflow
Solution 1 - ArraysKeenleView Answer on Stackoverflow
Solution 2 - ArraysWoodstockView Answer on Stackoverflow
Solution 3 - ArraysdimohamdyView Answer on Stackoverflow
Solution 4 - ArraysKimi ChiuView Answer on Stackoverflow
Solution 5 - ArraysgargView Answer on Stackoverflow
Solution 6 - ArraysKeyhan KamangarView Answer on Stackoverflow
Solution 7 - ArraysKaroly NyisztorView Answer on Stackoverflow
Solution 8 - ArrayspimisiView Answer on Stackoverflow
Solution 9 - ArraysMycroft CannerView Answer on Stackoverflow
Solution 10 - ArraysJustin MeinersView Answer on Stackoverflow