Difference between revisions of "W2021 Two-Dimensional Arrays"

From Coder Merlin
(Created page with "Two-dimensional arrays are Swift arrays whose elements are other arrays. However, this is not the only interesting property of two-dimensional arrays. Two-dimensional arrays i...")
 
m (Editorial review and minor corrections)
 
(4 intermediate revisions by 2 users not shown)
Line 1: Line 1:
Two-dimensional arrays are Swift arrays whose elements are other arrays. However, this is not the only interesting property of two-dimensional arrays. Two-dimensional arrays in Swift are very similar to matrices in math.
== Introduction ==
As covered earlier in [[W1301_Arrays]], an array is a variable that holds many elements of the same/homogeneous type. A two-dimensional array follows the same principles, with the elements of the array being more arrays. Each of those arrays, in turn, has elements of the same type.


The benefit of using two-dimensional arrays is that they are very efficient when working with data organized in rows and columns. The problem with using one-dimensional arrays is that they are not efficient when working with data organized this way.
Two-dimensional arrays are especially useful when representing data that is in rows and columns, or a conceptually similar format such as matrices. For example, here is a two-dimensional array with 9 rows and 10 columns:


As an example, consider the following one-dimensional array representing a deck of cards where each index represents a card:
<syntaxhighlight>
[["R1C1", "R1C2", "R1C3", "R1C4", "R1C5", "R1C6", "R1C7", "R1C8", "R1C9", "R1C10"],
["R2C1", "R2C2", "R2C3", "R2C4", "R2C5", "R2C6", "R2C7", "R2C8", "R2C9", "R2C10"],
["R3C1", "R3C2", "R3C3", "R3C4", "R3C5", "R3C6", "R3C7", "R3C8", "R3C9", "R3C10"],
["R4C1", "R4C2", "R4C3", "R4C4", "R4C5", "R4C6", "R4C7", "R4C8", "R4C9", "R4C10"],
["R5C1", "R5C2", "R5C3", "R5C4", "R5C5", "R5C6", "R5C7", "R5C8", "R5C9", "R5C10"],
["R6C1", "R6C2", "R6C3", "R6C4", "R6C5", "R6C6", "R6C7", "R6C8", "R6C9", "R6C10"],
["R7C1", "R7C2", "R7C3", "R7C4", "R7C5", "R7C6", "R7C7", "R7C8", "R7C9", "R7C10"],
["R8C1", "R8C2", "R8C3", "R8C4", "R8C5", "R8C6", "R8C7", "R8C8", "R8C9", "R8C10"],
["R9C1", "R9C2", "R9C3", "R9C4", "R9C5", "R9C6", "R9C7", "R9C8", "R9C9", "R9C10"]]
</syntaxhighlight>


<syntaxhighlight lang="swift">
It is worth noting that the indexes for the array still start at zero; however, there is generally no row 0 or column 0, so the elements have been incremented to represent this.
var deckOfCards = [Card]()
</syntaxhighlight>


The deck of cards is not useful yet because it is empty. Now suppose you need to fill the first four cards with data. This could be done as follows:
== Defining a Two-Dimensional Array in Swift ==
=== Without a Predefined Size ===
To create a two-dimensional array without a predefined size, use a similar construct to a single-dimensional array:


<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
 
var twoDim = [[String]]()
deckOfCards.append(Card(rank: .Ace, suit: .Spades)) // 1st card
 
deckOfCards.append(Card(rank: .Two, suit: .Clubs)) // 2nd card
 
deckOfCards.append(Card(rank: .Three, suit: .Diamonds)) // 3rd card
 
deckOfCards.append(Card(rank: .Four, suit: .Hearts)) // 4th card
 
To access the data in any of these cards you would have to write code similar to this:
 
var firstCard = deckOfCards.first
 
var secondCard = deckOfCards.dropFirst().first
 
</syntaxhighlight>
</syntaxhighlight>


The <syntaxhighlight lang="swift" inline> .first </syntaxhighlight>
Note that the double brackets '''[ [''' and '''] ]''' define this as a two-dimensional array. The first level of the array can hold more arrays that, in turn, hold strings. Data can be written to such an array using '''append''' as usual:
property returns the first element of a Swift array, which is not very intuitive if you are not familiar with arrays. Similarly, the
<syntaxhighlight lang="swift" inline>.dropFirst()</syntaxhighlight> method returns a new array without the first element. This means that if you want to get any of the data stored in these 4 cards, you need to know both the indices of this data and which method was used to drop the first element of an array (in this case .first).
 
== Curriculum ==
{{MerlinCurriculumData|{{ROOTPAGENAME}}}}
 
== Motivation for Using Two-Dimensional Arrays in Swift==
 
The newer versions of Swift bring many improvements and enhancements to the language, which help beginners and professionals alike write better code with less effort, leading to a more pleasant user experience. One of these new features is the possibility of declaring arrays of an arbitrary type, rather than only of the types that have been available since Swift was first introduced. However, this new feature doesn't provide any new functionality, but rather it makes existing code easier to write and maintain.
 
Most programming languages that support arrays of arbitrary type use some form of the generic array where each element must be of the same type. For example, when using Swift to create an array with three i32 elements, one would write:  


<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
let intArray: [Int] = [1, 2, 3]
twoDim.append(["This", "is", "an", "array"])
twoDim.append(["This", "is", "another", "array"])
</syntaxhighlight>
</syntaxhighlight>


This array is polymorphic in the sense that it can hold any integer value. However, when dealing with arrays containing objects of different types, this type-safety must be relaxed in order to avoid a situation where the compiler forces us to add a cast for each object contained inside the array.
=== With a Predefined Size ===
 
To create an array with a predefined size, you can use the '''Array''' constructor:
Even though the compiler is aware of each object's type being inserted into the array, it does not flag any errors when these lines are compiled. Two-dimensional arrays allow us to work around this problem by declaring a type for each of its dimensions, i.e., each row and column.
 
By doing so, the compiler will raise an error if the types contained in a given row or column do not match those declared for that dimension. This allows us to have greater control over the creation of arrays whose elements consist of an arbitrary number of different types.
 
==Declaring and Initializing Multi-Dimensional Arrays in Swift==
 
As with single-dimensional arrays, multi-dimensional arrays can be initialized either at the declaration or upon initialization.
 
In this case, the compiler will raise an error if either of these dimensions is not a positive integer.
 
However, as with any value type of Array type, it is possible to initialize the array with any given set of values enclosed in an array literal or dictionary literal.
 
{{CodeExplorer
|exerciseID=1
|mode=swift
|height=300
|initialCode=import Foundation
 
var multiDimensionalArray = [[1, 2], [3, 4]]
 
multiDimensionalArray = [["Hello", "World"], ["Swift", "is fun"]]
 
}}
 
One thing to keep in mind when initializing an array of arrays (i.e., a two-dimensional array) is that the inner arrays must all have the same length. This is because multi-dimensional arrays are just a special case of Array where each element is another Array.
 
However, unlike other programming languages that support two-dimensional arrays, Swift doesn't allow us to increase the size of an array by adding new rows or columns after its creation. Rather, it forces us to either initialize the array with a predefined length or add elements one at a time.


<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
let intArray: [Int] = [1, 2, 3]
var twoDim = Array(repeating: Array(repeating: "", count: 10), count: 10)
</syntaxhighlight>
</syntaxhighlight>


This array is polymorphic in the sense that it can hold any integer value. However, when dealing with arrays containing objects of different types, this type-safety must be relaxed in order to avoid a situation where the compiler forces us to add a cast for each object contained inside the array.
This creates an array with 10 elements, each of which is an array of 10 strings.
 
Even though the compiler is aware of each object's type being inserted into the array, it does not flag any errors when these lines are compiled. Two-dimensional arrays allow us to work around this problem by declaring a type for each of its dimensions, i.e., each row and column.
 
By doing so, the compiler will raise an error if the types contained in a given row or column do not match those declared for that dimension. This allows us to have greater control over the creation of arrays whose elements consist of an arbitrary number of different types.
 
==Declaring and Initializing Multi-Dimensional Arrays in Swift==
 
As with single-dimensional arrays, multi-dimensional arrays can be initialized either at the declaration or upon initialization.
In this case, the compiler will raise an error if either of these dimensions is not a positive integer.
 
However, as with any value type of Array type, it is possible to initialize the array with any given set of values enclosed in an array literal or dictionary literal.
 
{{CodeExplorer
|exerciseID=2
|mode=swift
|height=300
|initialCode=import Foundation


var multiDimensionalArray = [[1, 2], [3, 4]]
=== With a Predefined Array ===
 
If the two-dimensional array is already known (or at least a default), it can be assigned to a variable just like a one-dimensional array:
multiDimensionalArray = [["Hello", "World"], ["Swift", "is fun"]]
 
}}
 
One thing to keep in mind when initializing an array of arrays (i.e., a two-dimensional array) is that the inner arrays must all have the same length. This is because multi-dimensional arrays are just a special case of Array where each element is another Array.
 
However, unlike other programming languages that support two-dimensional arrays, Swift doesn't allow us to increase the size of an array by adding new rows or columns after its creation. Rather, it forces us to either initialize the array with a predefined length or add elements one at a time.
 
== Accessing Multi-Dimensional Arrays in Swift ==
 
One way of accessing an element from a two-dimensional array is by using subscript syntax, just as we would with any other type of array.
{{CodeExplorer
|exerciseID=3
|mode=swift
|height=300
|initialCode=import Foundation
 
var multiDimensionalArray = [[1, 2], [3, 4]]
 
multiDimensionalArray = [["Hello", "World"], ["Swift", "is fun"]]
 
print(multiDimensionalArray [2][1])
 
}}
 
However, subscripts are limited to one-based indexing. This means that the first row or column of a two-dimensional array starts at index 1 instead of 0.
 
The way around this problem is by using another form of subscript syntax with an optional base parameter (i.e., the number to start counting from) which defaults to zero.


<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
multiDimensionalArray [2][1] = 6 // this is equivalent to multiDimensionalArray [2].0 [1].0 = 6
var twoDim = [["R1C1", "R1C2", "R1C3", "R1C4", "R1C5", "R1C6", "R1C7", "R1C8", "R1C9", "R1C10"],
              ["R2C1", "R2C2", "R2C3", "R2C4", "R2C5", "R2C6", "R2C7", "R2C8", "R2C9", "R2C10"],
              ["R3C1", "R3C2", "R3C3", "R3C4", "R3C5", "R3C6", "R3C7", "R3C8", "R3C9", "R3C10"],
              ["R4C1", "R4C2", "R4C3", "R4C4", "R4C5", "R4C6", "R4C7", "R4C8", "R4C9", "R4C10"],
              ["R5C1", "R5C2", "R5C3", "R5C4", "R5C5", "R5C6", "R5C7", "R5C8", "R5C9", "R5C10"],
              ["R6C1", "R6C2", "R6C3", "R6C4", "R6C5", "R6C6", "R6C7", "R6C8", "R6C9", "R6C10"],
              ["R7C1", "R7C2", "R7C3", "R7C4", "R7C5", "R7C6", "R7C7", "R7C8", "R7C9", "R7C10"],
              ["R8C1", "R8C2", "R8C3", "R8C4", "R8C5", "R8C6", "R8C7", "R8C8", "R8C9", "R8C10"],
              ["R9C1", "R9C2", "R9C3", "R9C4", "R9C5", "R9C6", "R9C7", "R9C8", "R9C9", "R9C10"]]
</syntaxhighlight>
</syntaxhighlight>


The following code shows how we would access an item in a nested array of arrays, where some of the elements are initialized with values and others are simply placeholders.  
 
== Indexes ==
Similar to a one-dimensional array, every element in a two-dimensional array has a unique index. In addition, the syntax for retrieving and setting an element is the same. The only difference is that after the first index is passed, an array is returned. To access an element, two levels of indexes need to be used; one for the "outer" array and one for the "inner" array. For example, to retrieve the element at row 3 column 5, the indexes must be 2 and 4 (because array indexing starts at zero):


<syntaxhighlight lang="swift">
<syntaxhighlight lang="swift">
multiDimensionalArray [1] = [[7, 8], 60] // this is equivalent to multiDimensionalArray .0 [1] .0 = [[7, 8], 60]
twoDim[2][4] // R3C5
</syntaxhighlight>
</syntaxhighlight>
The first multi-dimensional array has two dimensions ( i.e., each element is another array), and the first row has two elements. The first element of this first row is initialized by assigning it a value of 7 whereas the second element is initialized as a placeholder with the value 60.
==Traversal of Multi-Dimensional Arrays in Swift==
Swift has made multi-dimensional arrays much easier to manage. You can think of it as managing a grid where each row is an individual array, potentially with different lengths and the columns are indexes into the rows.
For example, you might have a 2D array where the first dimension is 3 elements long and the second dimension is 4 elements long. This would result in multiple arrays, one for each row in the grid.
Each of the inner arrays is a reference to an instance of that grid (i.e. an array). You can then adjust various elements in the multidimensional array like this:


{{CodeExplorer
{{CodeExplorer
|exerciseID=4
|exerciseID=1
|mode=swift
|height=250
|height=300
|language=swift
|initialCode=import Foundation
|initialCode=
 
var twoDim = [["R1C1", "R1C2", "R1C3", "R1C4", "R1C5", "R1C6", "R1C7", "R1C8", "R1C9", "R1C10"],
var twoDArray = [1, 2, 3, 4]
              ["R2C1", "R2C2", "R2C3", "R2C4", "R2C5", "R2C6", "R2C7", "R2C8", "R2C9", "R2C10"],
 
              ["R3C1", "R3C2", "R3C3", "R3C4", "R3C5", "R3C6", "R3C7", "R3C8", "R3C9", "R3C10"],
twoDArray [1][3] = 5
              ["R4C1", "R4C2", "R4C3", "R4C4", "R4C5", "R4C6", "R4C7", "R4C8", "R4C9", "R4C10"],
 
              ["R5C1", "R5C2", "R5C3", "R5C4", "R5C5", "R5C6", "R5C7", "R5C8", "R5C9", "R5C10"],
// results in an updated array: twoDArray == [1, 2, 5, 4]
              ["R6C1", "R6C2", "R6C3", "R6C4", "R6C5", "R6C6", "R6C7", "R6C8", "R6C9", "R6C10"],
              ["R7C1", "R7C2", "R7C3", "R7C4", "R7C5", "R7C6", "R7C7", "R7C8", "R7C9", "R7C10"],
              ["R8C1", "R8C2", "R8C3", "R8C4", "R8C5", "R8C6", "R8C7", "R8C8", "R8C9", "R8C10"],
              ["R9C1", "R9C2", "R9C3", "R9C4", "R9C5", "R9C6", "R9C7", "R9C8", "R9C9", "R9C10"]]


print(twoDim[2][4])
}}
}}


This is a great way to work with multi-dimensional arrays.
== Loops ==
 
Similar to one-dimensional arrays, two-dimensional arrays are also commonly used in ''for'' loops. For example, to print each inner array on its own line, a single for loop can be used:
==Searching for an Element in a Multi-Dimensional Array in Swift==
 
You can then use this to search for an element in the multi-dimensional array. This will return a Subscript Index (an Element) which you can then either store and adjust:


{{CodeExplorer
{{CodeExplorer
|exerciseID=5
|exerciseID=2
|mode=swift
|height=250
|height=300
|language=swift
|initialCode=import Foundation
|initialCode=
 
var twoDim = [["R1C1", "R1C2", "R1C3", "R1C4", "R1C5", "R1C6", "R1C7", "R1C8", "R1C9", "R1C10"],
var twoDArray = [1, 2, 3, 4]
              ["R2C1", "R2C2", "R2C3", "R2C4", "R2C5", "R2C6", "R2C7", "R2C8", "R2C9", "R2C10"],
 
              ["R3C1", "R3C2", "R3C3", "R3C4", "R3C5", "R3C6", "R3C7", "R3C8", "R3C9", "R3C10"],
let indexToFind = twoDArray.firstIndex(of: 5)!
              ["R4C1", "R4C2", "R4C3", "R4C4", "R4C5", "R4C6", "R4C7", "R4C8", "R4C9", "R4C10"],
 
              ["R5C1", "R5C2", "R5C3", "R5C4", "R5C5", "R5C6", "R5C7", "R5C8", "R5C9", "R5C10"],
// twoDArray is now [1, 2, 3, 4]  
              ["R6C1", "R6C2", "R6C3", "R6C4", "R6C5", "R6C6", "R6C7", "R6C8", "R6C9", "R6C10"],
              ["R7C1", "R7C2", "R7C3", "R7C4", "R7C5", "R7C6", "R7C7", "R7C8", "R7C9", "R7C10"],
              ["R8C1", "R8C2", "R8C3", "R8C4", "R8C5", "R8C6", "R8C7", "R8C8", "R8C9", "R8C10"],
              ["R9C1", "R9C2", "R9C3", "R9C4", "R9C5", "R9C6", "R9C7", "R9C8", "R9C9", "R9C10"]]


twoDArray [indexToFind] = 5
func prettyPrint(twoDim: [[String]]) {
 
    for row in 0..<twoDim.count {
// results in an updated array: twoDArray == [1, 2, 5, 4]
        print(twoDim[row])
    }
}


prettyPrint(twoDim: twoDim)
}}
}}


==Insertion of an Element in a Multi-Dimensional Array in Swift==
However, since each element of the outer array is itself an array, two levels of for loops are required to interact with each individual element. For example, to initialize '''twoDim''' with each row and column labeled, two for loops can be used to keep track of the row and then column:


You can insert an element into a multi-dimensional array at a specified index. This will then return the new index of that reference in the updated array:


{{CodeExplorer
{{CodeExplorer
|exerciseID=6
|exerciseID=3
|mode=swift
|height=250
|height=300
|language=swift
|initialCode=import Foundation
|initialCode=


let twoDArray = [1, 2, 3]
// Create a two-dimensional array with 9 rows and 10 columns
var twoDim = Array(repeating: Array(repeating: "", count: 10), count: 9)


var newIndex = twoDArray.insert("X", at: 1)
// Iterate through each row
for row in 0..<twoDim.count {
    // Iterate through each column
    for col in 0..<twoDim[row].count {
        twoDim[row][col] = "R\(row + 1)C\(row+1)"
    }
}


// newIndex is now 1
// Print each row on its own line
func prettyPrint(twoDim: [[String]]) {
    for row in 0..<twoDim.count {
        print(twoDim[row])
    }
}


prettyPrint(twoDim: twoDim)
}}
}}
==Deleting an Element in a Multi-Dimensional Array in Swift==
To delete an element from a multi-dimensional array, you need to know the index of the reference.
For example:
{{CodeExplorer
|exerciseID=7
|mode=swift
|height=300
|initialCode=import Foundation
let twoDArray = [1, 2, 3]
twoDArray.removeAtIndex(2)
// results in an updated array: twoDArray == [1, 3]
}}
==Conclusion==
To conclude,  multi-dimensional arrays in Swift are a lot easier to manage because of the support for Subscript Indices.
This allows you to work with data organized as a grid. You can search, update and delete elements from this grid very easily by knowing their indices.
In addition, having setter access to these multi-dimensional arrays is nice because you can adjust the size of the grid dynamically.
These are some points that I found really helpful when working with multi-dimensional arrays in Swift and working with these grids.

Latest revision as of 09:47, 11 February 2023

Within these castle walls be forged Mavens of Computer Science ...
— Merlin, The Coder

Introduction[edit]

As covered earlier in W1301_Arrays, an array is a variable that holds many elements of the same/homogeneous type. A two-dimensional array follows the same principles, with the elements of the array being more arrays. Each of those arrays, in turn, has elements of the same type.

Two-dimensional arrays are especially useful when representing data that is in rows and columns, or a conceptually similar format such as matrices. For example, here is a two-dimensional array with 9 rows and 10 columns:

[["R1C1", "R1C2", "R1C3", "R1C4", "R1C5", "R1C6", "R1C7", "R1C8", "R1C9", "R1C10"],
["R2C1", "R2C2", "R2C3", "R2C4", "R2C5", "R2C6", "R2C7", "R2C8", "R2C9", "R2C10"],
["R3C1", "R3C2", "R3C3", "R3C4", "R3C5", "R3C6", "R3C7", "R3C8", "R3C9", "R3C10"],
["R4C1", "R4C2", "R4C3", "R4C4", "R4C5", "R4C6", "R4C7", "R4C8", "R4C9", "R4C10"],
["R5C1", "R5C2", "R5C3", "R5C4", "R5C5", "R5C6", "R5C7", "R5C8", "R5C9", "R5C10"],
["R6C1", "R6C2", "R6C3", "R6C4", "R6C5", "R6C6", "R6C7", "R6C8", "R6C9", "R6C10"],
["R7C1", "R7C2", "R7C3", "R7C4", "R7C5", "R7C6", "R7C7", "R7C8", "R7C9", "R7C10"],
["R8C1", "R8C2", "R8C3", "R8C4", "R8C5", "R8C6", "R8C7", "R8C8", "R8C9", "R8C10"],
["R9C1", "R9C2", "R9C3", "R9C4", "R9C5", "R9C6", "R9C7", "R9C8", "R9C9", "R9C10"]]

It is worth noting that the indexes for the array still start at zero; however, there is generally no row 0 or column 0, so the elements have been incremented to represent this.

Defining a Two-Dimensional Array in Swift[edit]

Without a Predefined Size[edit]

To create a two-dimensional array without a predefined size, use a similar construct to a single-dimensional array:

var twoDim = [[String]]()

Note that the double brackets [ [ and ] ] define this as a two-dimensional array. The first level of the array can hold more arrays that, in turn, hold strings. Data can be written to such an array using append as usual:

twoDim.append(["This", "is", "an", "array"])
twoDim.append(["This", "is", "another", "array"])

With a Predefined Size[edit]

To create an array with a predefined size, you can use the Array constructor:

var twoDim = Array(repeating: Array(repeating: "", count: 10), count: 10)

This creates an array with 10 elements, each of which is an array of 10 strings.

With a Predefined Array[edit]

If the two-dimensional array is already known (or at least a default), it can be assigned to a variable just like a one-dimensional array:

var twoDim = [["R1C1", "R1C2", "R1C3", "R1C4", "R1C5", "R1C6", "R1C7", "R1C8", "R1C9", "R1C10"],
              ["R2C1", "R2C2", "R2C3", "R2C4", "R2C5", "R2C6", "R2C7", "R2C8", "R2C9", "R2C10"],
              ["R3C1", "R3C2", "R3C3", "R3C4", "R3C5", "R3C6", "R3C7", "R3C8", "R3C9", "R3C10"],
              ["R4C1", "R4C2", "R4C3", "R4C4", "R4C5", "R4C6", "R4C7", "R4C8", "R4C9", "R4C10"],
              ["R5C1", "R5C2", "R5C3", "R5C4", "R5C5", "R5C6", "R5C7", "R5C8", "R5C9", "R5C10"],
              ["R6C1", "R6C2", "R6C3", "R6C4", "R6C5", "R6C6", "R6C7", "R6C8", "R6C9", "R6C10"],
              ["R7C1", "R7C2", "R7C3", "R7C4", "R7C5", "R7C6", "R7C7", "R7C8", "R7C9", "R7C10"],
              ["R8C1", "R8C2", "R8C3", "R8C4", "R8C5", "R8C6", "R8C7", "R8C8", "R8C9", "R8C10"],
              ["R9C1", "R9C2", "R9C3", "R9C4", "R9C5", "R9C6", "R9C7", "R9C8", "R9C9", "R9C10"]]


Indexes[edit]

Similar to a one-dimensional array, every element in a two-dimensional array has a unique index. In addition, the syntax for retrieving and setting an element is the same. The only difference is that after the first index is passed, an array is returned. To access an element, two levels of indexes need to be used; one for the "outer" array and one for the "inner" array. For example, to retrieve the element at row 3 column 5, the indexes must be 2 and 4 (because array indexing starts at zero):

twoDim[2][4] // R3C5

CoderMerlin™ Code Explorer: W0000 (1) 🟢


Loops[edit]

Similar to one-dimensional arrays, two-dimensional arrays are also commonly used in for loops. For example, to print each inner array on its own line, a single for loop can be used:

CoderMerlin™ Code Explorer: W0000 (2) 🟢


However, since each element of the outer array is itself an array, two levels of for loops are required to interact with each individual element. For example, to initialize twoDim with each row and column labeled, two for loops can be used to keep track of the row and then column:


CoderMerlin™ Code Explorer: W0000 (3) 🟢