Calculate the number of dimensions of a multi-dimensional array in Swift
If you're looking to get the depth of a nested array (Swift's standard library doesn't technically provide you with multi-dimensional arrays, only jagged arrays) – then, as shown in this Q&A, you can use a 'dummy protocol' and typecasting.
protocol _Array {
var nestingDepth: Int { get }
}
extension Array : _Array {
var nestingDepth: Int {
return 1 + ((first as? _Array)?.nestingDepth ?? 0)
}
}
let a = [1, 2, 3]
print(a.nestingDepth) // 1
let b = [[1], [2, 3], [4]]
print(b.nestingDepth) // 2
let c = [[[1], [2]], [[3]], [[4], [5]]]
print(c.nestingDepth) // 3
(I believe this approach would've still worked when you had originally posted the question)
In Swift 3, this can also be achieved without a dummy protocol, but instead by casting to [Any]
. However, as noted in the linked Q&A, this is inefficient as it requires traversing the entire array in order to box each element in an existential container.
Also note that this implementation assumes that you're calling it on a homogenous nested array. As Paul notes, it won't give a correct answer for [[[1], 2], 3]
.
If this needs to be accounted for, you could write a recursive method which will iterate through each of the nested arrays and returning the minimum depth of the nesting.
protocol _Array {
func _nestingDepth(minimumDepth: Int?, currentDepth: Int) -> Int
}
extension Array : _Array {
func _nestingDepth(minimumDepth: Int?, currentDepth: Int) -> Int {
// for an empty array, the minimum depth is the current depth, as we know
// that _nestingDepth is called where currentDepth <= minimumDepth.
guard !isEmpty else { return currentDepth }
var minimumDepth = minimumDepth
for element in self {
// if current depth has exceeded minimum depth, then return the minimum.
// this allows for the short-circuiting of the function.
if let minimumDepth = minimumDepth, currentDepth >= minimumDepth {
return minimumDepth
}
// if element isn't an array, then return the current depth as the new minimum,
// given that currentDepth < minimumDepth.
guard let element = element as? _Array else { return currentDepth }
// get the new minimum depth from the next nesting,
// and incrementing the current depth.
minimumDepth = element._nestingDepth(minimumDepth: minimumDepth,
currentDepth: currentDepth + 1)
}
// the force unwrap is safe, as we know array is non-empty, therefore minimumDepth
// has been assigned at least once.
return minimumDepth!
}
var nestingDepth: Int {
return _nestingDepth(minimumDepth: nil, currentDepth: 1)
}
}
let a = [1, 2, 3]
print(a.nestingDepth) // 1
let b = [[1], [2], [3]]
print(b.nestingDepth) // 2
let c = [[[1], [2]], [[3]], [[5], [6]]]
print(c.nestingDepth) // 3
let d: [Any] = [ [[1], [2], [[3]] ], [[4]], [5] ]
print(d.nestingDepth) // 2 (the minimum depth is at element [5])
Multidimensional arrays in Swift
For future readers, here is an elegant solution(5x5):
var matrix = [[Int]](repeating: [Int](repeating: 0, count: 5), count: 5)
and a dynamic approach:
var matrix = [[Int]]() // creates an empty matrix
var row = [Int]() // fill this row
matrix.append(row) // add this row
Multi-Dimensional Arrays of Different Types in Swift
I would recommend using an array of tuples instead. What you want could be accomplished using an array of type Any, but it is not a good idea.
Instead, your array should be [[(String, Int)]]. This would also be more compact than what you want to do.
var myArray: [[(String, Int)]] = []
How do I determine the number of rows and columns in a Swift [[AnyObject]]
What about this?
var a : [[AnyObject]] = [[1,2,3],[4,5,6]]
var row = a.count
var col = a[0].count
println("row = \(row)") // "row = 2"
println("col = \(col)") // "col = 3"
But be carreful because this var a : [[AnyObject]] = [[1,2,3],[4,6]]
will return the same since the [[AnyObject]] is not a rectangular, each array in it can have different size.
If you know that all lines have same number of row, you are safe, else, there is no solution, you will have to count cols of each rows.
for(var i=0;i<row;++i)
{
println("col = \(a[i].count)")
}
This returns:
"col = 3"
"col = 2"
N-Dimensional array swift
Here is the implementation of an N-Dimensional Array. It uses a normal array internally for storage and converts the multi-dimensional indices into a single index for the internal array.
struct NDimArray<T> {
let dimensions: [Int]
var data: [T]
init(dimensions: Int..., initialValue: T) {
self.dimensions = dimensions
data = Array(repeating: initialValue, count: dimensions.reduce(1, *))
}
init(dimensions: Int..., initUsing initializer: () -> T) {
self.dimensions = dimensions
data = (0 ..< dimensions.reduce(1, *)).map { _ in initializer() }
}
// Compute index into data from indices
private func computeIndex(_ indices: [Int]) -> Int {
guard indices.count == dimensions.count else { fatalError("Wrong number of indices: got \(indices.count), expected \(dimensions.count)") }
zip(dimensions, indices).forEach { dim, idx in
guard (0 ..< dim) ~= idx else { fatalError("Index out of range") }
}
var idx = indices
var dims = dimensions
var product = 1
var total = idx.removeLast()
while !idx.isEmpty {
product *= dims.removeLast()
total += (idx.removeLast() * product)
}
return total
}
subscript(_ indices: Int...) -> T {
get {
return data[computeIndex(indices)]
}
set {
data[computeIndex(indices)] = newValue
}
}
}
Example:
// Create a 3 x 4 x 5 array of String with initial value ""
var arr = NDimArray<String>(dimensions: 3, 4, 5, initialValue: "")
for x in 0 ..< 3 {
for y in 0 ..< 4 {
for z in 0 ..< 5 {
// Encode indices in the string
arr[x, y, z] = "(\(x),\(y),\(z))"
}
}
}
// Show internal storage of data
print(arr.data)
["(0,0,0)", "(0,0,1)", "(0,0,2)", "(0,0,3)", "(0,0,4)", "(0,1,0)", "(0,1,1)", "(0,1,2)", "(0,1,3)", "(0,1,4)", "(0,2,0)", "(0,2,1)", "(0,2,2)", "(0,2,3)", "(0,2,4)", "(0,3,0)", "(0,3,1)", "(0,3,2)", "(0,3,3)", "(0,3,4)", "(1,0,0)", "(1,0,1)", "(1,0,2)", "(1,0,3)", "(1,0,4)", "(1,1,0)", "(1,1,1)", "(1,1,2)", "(1,1,3)", "(1,1,4)", "(1,2,0)", "(1,2,1)", "(1,2,2)", "(1,2,3)", "(1,2,4)", "(1,3,0)", "(1,3,1)", "(1,3,2)", "(1,3,3)", "(1,3,4)", "(2,0,0)", "(2,0,1)", "(2,0,2)", "(2,0,3)", "(2,0,4)", "(2,1,0)", "(2,1,1)", "(2,1,2)", "(2,1,3)", "(2,1,4)", "(2,2,0)", "(2,2,1)", "(2,2,2)", "(2,2,3)", "(2,2,4)", "(2,3,0)", "(2,3,1)", "(2,3,2)", "(2,3,3)", "(2,3,4)"]
print(arr[2, 2, 2]) // "(2,2,2)"
print(arr[3, 0, 0]) // Fatal error: Index out of range
print(arr[0, 4, 0]) // Fatal error: Index out of range
print(arr[2]) // Fatal error: Wrong number of indices: got 1, expected 3
Initializing an Array with a Reference Type
As @DuncanC noted in the comments, you have to be careful when initializing an array with a value which is a reference type, because the array will be filled with references to the object and modifying the object at any index will modify all of them.
To solve this, I added a second initializer:
init(dimensions: Int..., initUsing initializer: () -> T)
which takes a closure () -> T
which can be used to create a new object for each element of the array.
For example:
class Person {
var name = ""
}
// Pass a closure which creates a `Person` instance to fill the array
// with 25 person objects
let arr = NDimArray(dimensions: 5, 5, initUsing: { Person() })
arr[3, 3].name = "Fred"
arr[2, 2].name = "Wilma"
print(arr[3, 3].name, arr[2, 2].name)
Fred Wilma
Two-dimensional array in Swift
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.
Find biggest array from a multi dimensional array by count in Swift
If you are looking for the longest subarray within the given
array then you can simply use max(by:)
with a comparison
using the array count:
let a = [["a", "b", "c"], ["d", "e", "f", "g", "h", "i", "j"], ["k"]]
let longestSubArray = a.max(by: { $0.count < $1.count })!
print(longestSubArray)
// ["d", "e", "f", "g", "h", "i", "j"]
Here I have assumed that a
is not empty, otherwise max(by:)
will return nil
. If that can happen, use optional binding:
if let longestSubArray = a.max(by: { $0.count < $1.count }) {
print(longestSubArray)
} else {
print("a is empty")
}
Remark: Array
is a RandomAccessCollection
and therefore getting
its count
is a O(1)
operation.
If you need both the longest element and its index in the containing
array then you can apply the above to a.enumerated()
:
if let (idx, longest) = a.enumerated().max(by: { $0.element.count < $1.element.count }) {
print("longest subarray", longest)
print("at index", idx)
}
If there is more than one subarray with the maximal length then the above
solutions will return one of them. @dfri's answer shows how to get all
subarrays with the maximal length.
Related Topics
Table View Cellforrowatindexpath Warning
Get Nil When Looking for File in Subdirectory of Main Bundle
Passing Data from Simple Nsview to Swiftui View
Why I Can Not Use Equatable Function in My View When the View Can Use It in Swiftui
Obtain Nsurl from Uiimagepickercontroller
How to Return a Value from a Void Closure in Swift
Swift: Dictionaries Inside Array
Swift MAC App, Run Terminal Command Without Knowing the Path (So It Looks in Every Path in $Path)
How Have Multiple Init() with Swift
Swift 2, Protocol Extensions & Respondstoselector
What's Wrong with My #If Target_Os_Simulator Code for Realm Path Definition
Update Nstouchbar on the Fly to Add/Remove Items Programmatically
How to Make Prepareforsegue with Uicollectionview
Cannot Invoke Initializer for Type 'Sqlite3_Destructor_Type'
Cast to Different C Struct Unsafe Pointer in Swift
How to Handle Parameter Validation Swift