2

A multilinear map M has its elements stored in a one-dimension array of length N, with a Shape S defined by S:[Int] = [p,q,r,...] so that q*p*r*... = N. The Shape is of variable size, not known at compile time.

The issue I'm trying to solve is a generic approach to accessing the map's elements using an array of integers, which individual values are coordinates in the Shape S, ex: M[1,3,2], M[2,3,3,3] etc... This is a problem different from a simple enumeration of the map's elements.

One method is to use M[i,j,k] and implement a subscript method. Unfortunately, this approach hardcodes the map's shape, and the algorithm is no longer generic.

Say there's a utility function that returns an element index from a tuple derived from the map's Shape, so that:

func index(_ indexes:[Int]) -> Int {....}

func elementAt(indexes:[Int]) -> Element { 
   return elements_of_the_map[self.index(indexes)]
}

M.elementAt(indexes:[i,j,k]) or M.elementAt(indexes:[i,j,k,l,m]) always work. So the problem at this point is to build the array [i,j,k,...]

Question: Is there an algorithm to efficiently enumerate those indexes? Nested loops won't work since the number of loops isn't known at compile time, and recursive function seem to add a lot of complexity (in particular keeping track of previous indexes).

I'm thinking about an algorithm 'a la' base-x counting, that is adding one unit to the top right index, and moving leftwards one unit if the count exceeds the number of elements by the map's Shape.

Alex
  • 1,581
  • 1
  • 11
  • 27
  • Given a shape (e.g. [3, 2, 2]) you want to enumerate all possible indices [0, 0, 0], [0, 0, 1], [0, 1, 0], ... [2, 1, 1] ? – Martin R May 17 '18 at 08:48
  • yes. But please note the shape isn't known at compile time. – Alex May 17 '18 at 09:11
  • note: I quickly tested an algorithm counting 'a la' base-x, seems to be working, although clunky. – Alex May 17 '18 at 09:12
  • For each element index (in the range `0.. – Martin R May 17 '18 at 09:14
  • It's IMO impossible to compute the indices array from the index in the range `0.. – Alex May 17 '18 at 09:19
  • It's ugly coding for now, so keeping it as 'horrifying draft'..... – Alex May 17 '18 at 09:21
  • Given an element index *and the shape* one can compute the indices array. – Martin R May 17 '18 at 09:23
  • mmm.... not sure about that. 2x4 and 4x2 in an array of Shape 5x5 do not point to the same element, but have the same horizontal ordinate (8). I may be wrong, it just didn't strike my as trivial at first. – Alex May 17 '18 at 09:28

2 Answers2

1

Here's the code, it's primitive, but should work. The idea is to increment, right-to-left, to move say to [1,2,2] from [1,2,1] with the shape constraint [2,3,3].

func add_one_unit(shape:[Int],indexes:[Int]) -> [Int]? {
    //Addition is right to left, so we have to reverse the arrays. Shape Arrays are usually very small, so it's fast.
    let uu = Array(indexes.reversed()); //Array to add one index to.
    let shape_reversed = Array(shape.dimensions.reversed()); //Shape array.

    var vv:[Int] = [];
    var move_next:Bool = true;

    for i in 0..<uu.count {
        if move_next {
            if uu[i] < shape_reversed[i] - 1 { //Shape constraint is OK.
                vv.append(uu[i] + 1)
                move_next = false;
            } else {
                vv.append(0) //Shape constraint is reached.
                move_next = true;//we'll flip the next index.
            }
        } else {
            vv.append(uu[i]) //Nothing to change.
        }
    }
    return ( vv.reduce(true, { $0&&($1 == 0) }) ) ? nil : Array(vv.reversed()); //Returns nil once we reached the Zero Vector.
}

Which gives

add_one_unit(shape:[2,3,3],indexes:[0,0,0]) -> [0,0,1]
add_one_unit(shape:[2,3,3],indexes:[1,2,2]) -> [0,0,0]/nil

Once this is done, this function can be used to enumerate a multilinear map of any shape (a mapping of [i,j,k,...] to a unique index such as matrix to index mapping is necessary and depends on your implementation), or slice a map starting from any particular vector.

Alex
  • 1,581
  • 1
  • 11
  • 27
1

Same idea, but less code:

func addOneUnit(shape: [Int], indexes: [Int]) -> [Int]? {
    var next = indexes
    for i in shape.indices.reversed() {
        next[i] += 1
        if next[i] < shape[i] {
            return next 
        }
        next[i] = 0
    }
    return nil
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Hey thanks, that's such clean code! Much better than my horrible stuff. Really helpful, now I can peek into tensors the way I want. Cheers! – Alex May 18 '18 at 04:47