问题
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.
回答1:
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.
回答2:
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));
回答3:
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 – continuation-passing 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.
来源:https://stackoverflow.com/questions/50554603/fill-a-nested-structure-with-values-from-a-linear-supply-stream