0

I want custom Multi-dimensional Array in Swift. I declared MArray such like;

class MArray{
    private var flatten: [Any]
    private var shape: [Int]
    init(_ marray: [Any]){
         var shape: [Int] = []
         var marray: [Any] = marray
         (self.flatten, self.shape) = _call_flatten_row_major(&marray, &shape)
    }
}
fileprivate func _call_flatten_row_major(_ queue: inout [Any], _ shape: inout [Int]) -> (flatten: [Any], shape: [Int]){
    shape = [queue.count]
    return (_get_flatten_row_major(&queue, &shape), shape)
}
//breadth-first search
fileprivate func _get_flatten_row_major(_ queue: inout [Any], _ shape: inout [Int]) -> [Any]{
    precondition(shape.count == 1, "shape must have only one element")
    var cnt = 0 // count up the number that value is extracted from queue for while statement, reset 0 when iteration number reaches size
    var size = queue.count
    var axis = 0//the axis in searching

    while queue.count > 0 {
        //get first element
        let elements = queue[0]

        if let elements = elements as? [Any]{
            queue += elements

            if cnt == 0{ //append next dim
                shape.append(elements.count)
                axis += 1
            }
            else{// check if same dim is or not
                if shape[axis] != elements.count{
                    shape = shape.dropLast()
                }
            }
            cnt += 1
        }
        else{ // value was detected. this means queue in this case becomes flatten array
            break
        }
        //remove first element from array
        let _ = queue.removeFirst()

        if cnt == size{//reset count and forward next axis
            cnt = 0
            size *= shape[axis]
        }
    }

    return queue
}

However, as you can see, the argument of this MArray's initializer is [Any]. That is why MArray can't get flatten's type. Namely, MArray can't get Multi-dimensional Array's type.

Then, I used generics such like;

class MArray<T>{
    private var flatten: [T]
    private var shape: [Int]
    init(_ marray: [Any]){
         (self.flatten, self.shape): [T], [Int] = _call_flatten_row_major(&marray, &shape)
    }
}

and,

fileprivate func _call_flatten_row_major<T>(_ queue: inout [Any], _ shape: inout [Int]) -> (flatten: [T], shape: [Int]){
    shape = [queue.count]
    return (_get_flatten_row_major(&queue, &shape) as! [T], shape)
}

When I declared MArray<Int>([[1,2,3]]), it is OK. But when I declared MArray<Float>([[1,2,3]]),

Could not cast value of type 'Swift.Int' (0x7fff8b682600) to 'Swift.Float' (0x7fff8b6825e0).

I want Compiler to understand MArray<Float>([[1,2,3]])'s flatten type is Float.... (I should use ExpressibleByArrayLiteral??)

You may think that all I have to do is declaring MArray<Float>([[Float(1),Float(2),Float(3)]]). But Float(number) is annoying!!

Question

  • How can I get multi-dimensional Array's element type?
  • Should I use ExpressibleByArrayLiteral to achieve this? and if so, please tell me tips to use ExpressibleByArrayLiteral for Multi-dimensional Array...

For clawesome's answer

I tried Clawesome's answer, but compiler said

Type 'Float' does not conform to protocol 'ExpressibleByArrayLiteral'

clawesomes answer

code

jjjkkkjjj
  • 51
  • 1
  • 7

1 Answers1

0

You are declaring that you have an array of Floats with MArray<Float>. Instead you need to declare an array of an array of floats and the subsequently called functions must also be generacized. For your generic functions you just needed to have what T conforms to.

let mArray = MArray<Array<Float>>([[1,2,3]])

or

let mArray = MArray<[Float]>([[1,2,3]])

Class declaration:

class MArray<T: ExpressibleByArrayLiteral>{
    private var flatten: [T]
    private var shape: [Int] = [Int]()
    init(_ marray: [T]){
        // create temp var that can be passed by reference, let vars such 
        // as 'marray' cant be passed by reference unless declared as inout
        var temp = marray
        (self.flatten, self.shape) = _call_flatten_row_major(&temp, &shape)
    }
}

It can also be declared like the following:

class MArray<T> where T: ExpressibleByArrayLiteral {
    ...
}

Updated functions:

fileprivate func _call_flatten_row_major<T: ExpressibleByArrayLiteral>(_ queue: inout [T], _ shape: inout [Int]) -> (flatten: [T], shape: [Int]){
    shape = [queue.count]
    return (_get_flatten_row_major(&queue, &shape), shape)
}
//breadth-first search
fileprivate func _get_flatten_row_major<T: ExpressibleByArrayLiteral>(_ queue: inout [T], _ shape: inout [Int]) -> [T]{
    precondition(shape.count == 1, "shape must have only one element")
    var cnt = 0 // count up the number that value is extracted from queue for while statement, reset 0 when iteration number reaches size
    var size = queue.count
    var axis = 0//the axis in searching

    while queue.count > 0 {
        //get first element
        let elements = queue[0]

        if let elements = elements as? [T]{
            queue += elements

            if cnt == 0{ //append next dim
                shape.append(elements.count)
                axis += 1
            }
            else{// check if same dim is or not
                if shape[axis] != elements.count{
                    shape = shape.dropLast()
                }
            }
            cnt += 1
        }
        else{ // value was detected. this means queue in this case becomes flatten array
            break
        }
        //remove first element from array
        let _ = queue.removeFirst()

        if cnt == size{//reset count and forward next axis
            cnt = 0
            size *= shape[axis]
        }
    }

    return queue
}
clawesome
  • 1,223
  • 1
  • 5
  • 10
  • Thank you. But in your answer, compiler reads `T` as `Array` which does not match `flatten` property right? `flatten` will be `[Array]` not 1d. – jjjkkkjjj May 24 '20 at 03:01
  • Updated the answer, maybe that will help get you going. I only updated the syntax, I didn't check any logic but it compiles and runs and should at least get you past the current generic typing hump. – clawesome May 24 '20 at 06:37
  • Thanks. But compiler said `Type 'Float' does not conform to protocol 'ExpressibleByArrayLiteral'`... Should I separate the case T is Array and case T is Value(Int, Float...)? – jjjkkkjjj May 24 '20 at 10:16
  • What line is the compiler saying that on? That line indicates you're passing a `Float` but it's expecting an array. The code I posted compiles and runs, can you make a pastebin or something with a comment indicating the offending line? – clawesome May 24 '20 at 15:19
  • Thank you for your cooperation. The line was declaring line which was `let a = MArray([[[1,2,3]]])`. I updated question. I couldn't post image due to low reputation, so could you see this image from [here](https://i.stack.imgur.com/ynSpk.png)? – jjjkkkjjj May 24 '20 at 15:46
  • And code image is [here](https://i.stack.imgur.com/sLaib.png). – jjjkkkjjj May 24 '20 at 15:52
  • I'm on my phone at the moment, but you're declaring a type `Float` but trying to pass in a 3-dimenional array. You need to declare an array of floats via `[Float]` or Array and pass a 2-d array `[[1,2,3]]`, the line in your comment has 3 brackets – clawesome May 24 '20 at 16:21
  • In the picture, you're extending MArray, but what is the class definition? Have you tried copying the code I posted verbatim? – clawesome May 24 '20 at 16:23
  • Look at this screenshot, it's a very important but not inherently obvious distinction. https://imgur.com/a/PJvFxYI – clawesome May 24 '20 at 16:34
  • OK, You mean I must declare `MArray<[Float]>` when I want 2d array right? Namely must I declare MArray with `[[...[ ]...]]` (**N-1** times) when I want Nd array? – jjjkkkjjj May 24 '20 at 16:56
  • Yes, exactly. `MArray<...>` is declaring an array of whatever is in the middle. So if you want that array to contain arrays, what you pass in has to be an array itself. – clawesome May 24 '20 at 17:02
  • Thank you very much. Finally, is there any method to declare `MArray` with whatever dimensions? Because generics `T` conforms to my another protocol, I want to limit `T` with `Int, UInt,...` such like `Numeric` not `Array`. – jjjkkkjjj May 25 '20 at 00:58
  • Yes, you can limit `T` to any protocol or class you want. Swift doesn't have a protocol for every kind of number, but you can make one pretty easily. Check [this answer](https://stackoverflow.com/questions/25575513/what-protocol-should-be-adopted-by-a-type-for-a-generic-function-to-take-any-num) for more details. Any reason that you still haven't marked my answer as 'accepted'? – clawesome May 25 '20 at 01:28