Calculate the Number of Dimensions of a Multi-Dimensional Array in Swift

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



Leave a reply



Submit