2

I got stuck in the resolution of the next problem:

Imagine we have an array structure, any structure, but for this example let's use:

[
    [ [1, 2], [3, 4], [5, 6] ],
    [ 7, 8, 9, 10 ]
]

For convenience, I transform this structure into a flat array like:

[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]

Imagine that after certain operations our array looks like this:

[ 1, 2, 3, 4, 12515, 25125, 12512, 8, 9, 10]

NOTE: those values are a result of some operation, I just want to point out that is independent from the structure or their positions.

What I would like to know is... given the first array structure, how can I transform the last flat array into the same structure as the first? So it will look like:

[ 
   [ [1, 2], [3, 4] , [12515, 25125] ],
   [ 12512, 8, 9, 10] 
]

Any suggestions? I was just hardcoding the positions in to the given structure. But that's not dynamic.

Will Ness
  • 70,110
  • 9
  • 98
  • 181
SirPeople
  • 4,248
  • 26
  • 46
  • Is your structure restricted to only arrays and sub arrays? – Yevgeniy.Chernobrivets May 27 '18 at 17:20
  • @Yevgeniy.Chernobrivets yes, only arrays and arrays of arrays, no objects and no other kind of value... if you want to contraint it more. Array of ints – SirPeople May 27 '18 at 17:21
  • How about holding for each number in array "coordinates" in initial structure as follows: first coordinate is place in first level array, second coordinate in second level, etc. You can have not all coordinates specified for example only first which means that number is located in first array and not in sub array. – Yevgeniy.Chernobrivets May 27 '18 at 17:23
  • it would create a lot of additional data that I already know (by having the first structure I know that first element in the array map to the first position in the sub array of the sub array) so I dont think that adding coords to each number is a good solution in this case. – SirPeople May 27 '18 at 17:26
  • Well this will add maximum flexibility from what i can tell. I would start from it and continue eliminating redundant information. You will need to store some additional info in order to rebuild initial structure i think. – Yevgeniy.Chernobrivets May 27 '18 at 17:28
  • How about just storing depth of the element? – Yevgeniy.Chernobrivets May 27 '18 at 17:30
  • No, just depth won't work cause you won't be able to distinguish between [1, 2], [3, 4] and [1,2,3,4]. – Yevgeniy.Chernobrivets May 27 '18 at 17:31
  • Feels, like I could infer those coordinates by having the first structure, but I cannot find a nice process or method to do so – SirPeople May 27 '18 at 17:51
  • Was this intended to be JS question? Then maybe one should tag it as such. – Andrey Tyukin May 27 '18 at 23:51
  • Not intended to be a JS question... wanted to keep it as agnostic as possible. Today i will read both questions and see what fits better the question I realised :D – SirPeople May 28 '18 at 10:36
  • 1
    here's a [very similar entry](https://stackoverflow.com/q/73373180/849891) in python. – Will Ness Aug 16 '22 at 13:52

3 Answers3

4

Just recurse through the structure, and use an iterator to generate the values in order:

function fillWithStream(structure, iterator) {
    for (var i=0; i<structure.length; i++)
        if (Array.isArray(structure[i]))
            fillWithStream(structure[i], iterator);
        else
            structure[i] = getNext(iterator);
}
function getNext(iterator) {
    const res = iterator.next();
    if (res.done) throw new Error("not enough elements in the iterator");
    return res.value;
}

var structure = [
    [ [1, 2], [3, 4], [5, 6] ],
    [ 7, 8, 9, 10 ]
];
var seq = [1, 2, 3, 4, 12515, 25125, 12512, 8, 9, 10];
fillWithStream(structure, seq[Symbol.iterator]())
console.log(JSON.stringify(structure));
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    Thanks Bergi, I like your approach but it is a bit poorly explained in comparison with the selected answer. But I like your approach with iterators and recursion. – SirPeople May 29 '18 at 14:57
  • this is very nice. I've added a CPS-based answer btw; interesting how seemingly different approaches actually describe the very same thing. – Will Ness May 29 '18 at 19:17
  • @WillNess What I find even more interesting is that the question is getting answered in three different languages :-) I usually watch [javascript] questions only, so when I saw this one and a JSON-like data structure it didn't occur to me that it's language-agnostic until Andrey's Scala answer. For JS, I just wanted to answer that iterators are the obvious choice and sketch out a small example, that's why it's lacking explanation. – Bergi May 29 '18 at 20:46
  • the code itself is plenty enough. :) in fact many a problem fits this pattern, come to think of it. first of all, parsing. and parsing is an instance of state_passing... this is good stuff. – Will Ness May 29 '18 at 21:09
2

Here is a sketch in Scala. Whatever your language is, you first have to represent the tree-like data structure somehow:

sealed trait NestedArray
case class Leaf(arr: Array[Int]) extends NestedArray {
  override def toString = arr.mkString("[", ",", "]")
}
case class Node(children: Array[NestedArray]) extends NestedArray {
  override def toString = 
    children
      .flatMap(_.toString.split("\n"))
      .map("  " + _)
      .mkString("[\n", "\n", "\n]")
}

object NestedArray {
  def apply(ints: Int*) = Leaf(ints.toArray)
  def apply(cs: NestedArray*) = Node(cs.toArray)
}

The only important part is the differentiation between the leaf nodes that hold arrays of integers, and the inner nodes that hold their child-nodes in arrays. The toString methods and extra constructors are not that important, it's mostly just for the little demo below.

Now you essentially want to build an encoder-decoder, where the encode part simply flattens everything, and decode part takes another nested array as argument, and reshapes a flat array into the shape of the nested array. The flattening is very simple:

def encode(a: NestedArray): Array[Int] = a match {
  case Leaf(arr) => arr
  case Node(cs) => cs flatMap encode
}

The restoring of the structure isn't all that difficult either. I've decided to keep the track of the position in the array by passing around an explicit int-index:

def decode(
  shape: NestedArray, 
  flatArr: Array[Int]
): NestedArray = {
  def recHelper(
    startIdx: Int, 
    subshape: NestedArray
  ): (Int, NestedArray) = subshape match {
    case Leaf(a) => {
      val n = a.size
      val subArray = Array.ofDim[Int](n)
      System.arraycopy(flatArr, startIdx, subArray, 0, n)
      (startIdx + n, Leaf(subArray))
    }
    case Node(cs) => {
      var idx = startIdx
      val childNodes = for (c <- cs) yield {
        val (i, a) = recHelper(idx, c)
        idx = i
        a
      }
      (idx, Node(childNodes))
    }
  }
  recHelper(0, shape)._2
}

Your example:

val original = NestedArray(
  NestedArray(NestedArray(1, 2), NestedArray(3, 4), NestedArray(5, 6)),
  NestedArray(NestedArray(7, 8, 9, 10))
)

println(original)

Here is what it looks like as ASCII-tree:

[
  [
    [1,2]
    [3,4]
    [5,6]
  ]
  [
    [7,8,9,10]
  ]
]

Now reconstruct a tree of same shape from a different array:

val flatArr = Array(1, 2, 3, 4, 12515, 25125, 12512, 8, 9, 10)
val reconstructed = decode(original, flatArr)

println(reconstructed)

this gives you:

[
  [
    [1,2]
    [3,4]
    [12515,25125]
  ]
  [
    [12512,8,9,10]
  ]
]

I hope that should be more or less comprehensible for anyone who does some functional programming in a not-too-remote descendant of ML.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
2

Turns out I've already answered your question a few months back, a very similar one to it anyway.

The code there needs to be tweaked a little bit, to make it fit here. In Scheme:

(define (merge-tree-fringe vals tree k)
  (cond
    [(null? tree)
     (k vals '())]
    [(not (pair? tree))                  ; for each leaf:
     (k (cdr vals) (car vals))]          ;   USE the first of vals
    [else
     (merge-tree-fringe vals (car tree) (lambda (Avals r)      ; collect 'r' from car,
      (merge-tree-fringe Avals (cdr tree) (lambda (Dvals q)    ;  collect 'q' from cdr,
       (k Dvals (cons r q))))))]))       ; return the last vals and the combined results

The first argument is a linear list of values, the second is the nested list whose structure is to be re-created. Making sure there's enough elements in the linear list of values is on you.

We call it as

> (merge-tree-fringe '(1 2 3 4 5 6 7 8) '(a ((b) c) d) (lambda (vs r) (list r vs)))
'((1 ((2) 3) 4) (5 6 7 8))

> (merge-tree-fringe '(1 2 3 4 5 6 7 8) '(a ((b) c) d) (lambda (vs r) r))
'(1 ((2) 3) 4)

There's some verbiage at the linked answer with the explanations of what's going on. Short story short, it's written in CPS style:

We process a part of the nested structure while substituting the leaves with the values from the linear supply; then we're processing the rest of the structure with the remaining supply; then we combine back the two results we got from processing the two sub-parts. For LISP-like nested lists, it's usually the "car" and the "cdr" of the "cons" cell, i.e. the tree's top node.

This is doing what Bergi's code is doing, essentially, but in a functional style.


In an imaginary pattern-matching pseudocode, which might be easier to read/follow, it is

merge-tree-fringe vals tree = g vals tree (vs r => r)
    where
    g vals [a, ...d] k = g vals a (avals r =>    -- avals: vals remaining after 'a'
                             g avals d (dvals q =>    -- dvals: remaining after 'd'
                                 k dvals [r, ...q] ))     -- combine the results
    g vals        [] k = k vals []                           -- empty 
    g [v, ...vs]  _  k = k vs   v                            -- leaf: replace it

This computational pattern of threading a changing state through the computations is exactly what the State monad is about; with Haskell's do notation the above would be written as

merge_tree_fringe vals tree = evalState (g tree) vals
    where
    g [a, ...d] = do { r <- g a ; q <- g d ; return [r, ...q] }  
    g        [] = do { return [] }           
    g        _  = do { [v, ...vs] <- get ; put vs ; return v }  -- leaf: replace

put and get work with the state being manipulated, updated and passed around implicitly; vals being the initial state; the final state being silently discarded by evalState, like our (vs r => r) above also does, but explicitly so.

Will Ness
  • 70,110
  • 9
  • 98
  • 181