简体   繁体   中英

N-Dimensional array swift

Is there any way to have an n dimensional array in swift? I would like to be able to make a function that creates an array with n dimensions but I cannot figure out how.

Basically something like this:

func ndarray <T> (dimensions: Int...) -> [[T]] { // What do I tell it I return?
    var out
    for d in dimensions {
        out = Array<T>(repeating: out, count: d)
    }
    return out
} 

The above code does not work for obvios reasons but, I think it points out the main problems I am having:

  • How do I define a return type
  • How do I actually create the array
  • Once created how do I traverse and populate the array

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

Nope, it's not possible. Array dimensions is something that needs to be determined at compile time, while the argument you want to pass to the initializer will not be known until runtime. If you really want to achieve something like this, then you'll need to move the array indexing from compile time to runtime, eg by accessing the array via an array of indexes. Still you don't have compile validation, since the array length can at runtime to not match the dimensions of the array.

This problem is similar to the one that attempts to convert a tuple to an array.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM