Lypsin Lypsin - 4 months ago 18
Swift Question

How to create 3D NSMutableArray

How can I create a 3D Mutable Array of Int?

I want to be able to insert an element by saying

arr[4][3][5] = 4
, and not have to determine the size of the array beforehand.

Answer

Discussion (see below for final code)

Here is a way to do it: First we extend _ArrayType with a subscript that automatically extends the array when accessing an index out of bounds. We will the array with a given fallback value:

extension _ArrayType {
    subscript (extending pos: Int, fallback fallback: Generator.Element) -> Generator.Element {
        mutating get {
            while count <= pos { append(fallback) }
            return self[pos]
        }
        mutating set {
            while count <= pos { append(fallback) }
            self[pos] = newValue
        }
    }
}

This is useable like this:

var a : [Int] = []
a[extending: 3, fallback: 0] = 10
print(a) // [0, 0, 0, 10]

Now we can extend the 3-dimensional arrays to have a convenient subscript that auto-extends like this:

extension _ArrayType where
    Generator.Element : _ArrayType,
    Generator.Element.Generator.Element : _ArrayType {

    typealias T = Generator.Element.Generator.Element.Generator.Element

    subscript (x: Int, y: Int, z: Int, `default`: T) -> T {
        mutating get {
            return self[extending: x, fallback: []][extending: y, fallback: []][extending: z, fallback: `default`]
        }
        mutating set {
            self[extending: x, fallback: []][extending: y, fallback: []][extending: z, fallback: `default`] = newValue
        }
    }
}

Usage:

var array : [[[Int?]]] = []
array[1, 1, 0, nil] = 10
array[2, 0, 1, nil] = 4
print(array) // [[], [[], [Optional(10)]], [[nil, Optional(4)]]]

Now if you want it even more convenient by telling beforehand what the default value should be for a certain type you can create a HasDefault protocol:

protocol HasDefault {
    static var `default` : Self { get }
}

And extend your types with it:

extension Int : HasDefault {
    static var `default` = 0
}

extension Bool : HasDefault {
    static var `default` = false
}

Also make a subscript that doesn't take the last argument:

extension _ArrayType where
    Generator.Element : _ArrayType,
    Generator.Element.Generator.Element : _ArrayType,
    Generator.Element.Generator.Element.Generator.Element : HasDefault {

    typealias E = Generator.Element.Generator.Element.Generator.Element

    subscript (x: Int, y: Int, z: Int) -> E {
        mutating get {
            return self[x, y, z, E.`default`]
        }
        mutating set {
            self[x, y, z, E.`default`] = newValue
        }
    }
}

Now this is useable the way you want it:

var array : [[[Int]]] = []
array[0, 1, 0] = 3
array[1, 2, 3] = 10

print(array[2, 0, 1]) // 0
print(array) // [[[], [3]], [[], [], [0, 0, 0, 10]], [[0, 0]]]

Unfortunately it's not (yet) possible to extend Optional with HasDefault (which would be nil), but you can make a custom subscript for NilLiteralConvertibles which Optional conforms to:

extension _ArrayType where
    Generator.Element : _ArrayType,
    Generator.Element.Generator.Element : _ArrayType,
    Generator.Element.Generator.Element.Generator.Element : NilLiteralConvertible {

    subscript (x: Int, y: Int, z: Int) -> Generator.Element.Generator.Element.Generator.Element {
        mutating get {
            return self[x, y, z, nil]
        }
        mutating set {
            self[x, y, z, nil] = newValue
        }
    }
}

Use like this:

var array : [[[Int?]]] = []
array[0, 1, 0] = 3
array[1, 2, 3] = 10

print(array[2, 0, 1]) // nil
print(array) // [[[], [3]], [[], [], [nil, nil, nil, 10]], [[nil, nil]]]

I heavily recommend this approach, since the whole purpose of Optionals is to represent no value.

TL;DR / The final code

extension _ArrayType {
    subscript (extending pos: Int, fallback fallback: Generator.Element) -> Generator.Element {
        mutating get {
            while count <= pos { append(fallback) }
            return self[pos]
        }
        mutating set {
            while count <= pos { append(fallback) }
            self[pos] = newValue
        }
    }
}

extension _ArrayType where
    Generator.Element : _ArrayType,
    Generator.Element.Generator.Element : _ArrayType,
    Generator.Element.Generator.Element.Generator.Element : NilLiteralConvertible {

    subscript (x: Int, y: Int, z: Int) -> Generator.Element.Generator.Element.Generator.Element {
        mutating get {
            return self[extending: x, fallback: []][extending: y, fallback: []][extending: z, fallback: nil]
        }
        mutating set {
            self[extending: x, fallback: []][extending: y, fallback: []][extending: z, fallback: nil] = newValue
        }
    }
}

var array : [[[Int?]]] = []
array[0, 1, 0] = 3
array[1, 2, 3] = 10

print(array[2, 0, 1]) // nil
print(array) // [[[], [3]], [[], [], [nil, nil, nil, 10]], [[nil, nil]]]