[Closed] Flatten a multidimensional/nested array
I’m looking for a good way to flatten a multidimensional array that works regardless how many levels deep the array is.
If I knew how many levels the array was, I could create X amount of loops for how many levels down in the array to check, but I don’t.
I’ve thought about converting the array to a string, then just remove all of the array declarations from the string, and lastly split it and convert it back to an array. The problem with that is that I loose the actual node information, I could run getNodeByName on all of the elements in the array lastly, but if I have multiple object’s sharing the same name it won’t work. I would also need to accept data that isn’t nodes, so it gets a bit messy regardless.
So I was wondering if anyone have any tips on how to do this efficiently?
Here’s an example: (this is just 4 levels deep, but as said, I want it to work even with 50 levels)
inputArray = #(“Item1”, #(“Item2”, “Item3”, #(“Item4”, #(“Item5”, #(“Item6”)))), “Item7”, #(“Item8”, #(“Item9”, “Item10”)))
desiredResult = #(“Item1”, “Item2”, “Item3”, “Item4”, “Item5”, “Item6”, “Item7”, “Item8”, “Item9”, “Item10”)
inputArray = #("Item1", #("Item2", "Item3", #("Item4", #("Item5", #("Item6")))), "Item7", #("Item8", #("Item9", "Item10")))
fn addnext n = (
local x = #()
if classof n == Array then (
for s in n do (
case of (
(classof s == array): join x (addnext s)
default: append x s
)
)
) else (
append x n
)
x
)
clearlistener()
format "Unformatted: %
" inputArray
do (
local arrays = 0
inputArray = for i=1 to inputArray.count collect (addnext inputArray[i])
for s in inputArray where classof s == array do arrays = arrays + 1
if arrays == inputArray.count then (
local tmp = #()
for s in inputArray do join tmp s
inputArray = tmp
exit
)
) while true
format "Formatted: %
" inputArray
recursion way
and there’s must be another ways for sure
If the inputArray contains only string values:
(
fn readItems inputArray resultArray =
(
for item in inputArray do
(
if classOf item == String then append resultArray item
else readItems item resultArray
)
return resultArray
)
resultArray = #()
inputArray = #("Item1", #("Item2", "Item3", #("Item4", #("Item5", #("Item6")))), "Item7", #("Item8", #("Item9", "Item10")))
readItems inputArray resultArray
format "%
" resultArray
)
If you wait a little, DenisT will give you a one line solution for sure!
Stupid me :banghead:
The same code for whatever the inputArray contains:
(
fn readItems theArray resultArray =
(
for item in theArray do (if classOf item == Array then readItems item resultArray else append resultArray item)
)
resultArray = #()
inputArray = #(7, #("Item2", "Item3", #(25, #("Item5", #("Item6")))), "Item7", #("Item8", #("Item9", "Item10")))
readItems inputArray resultArray
format "%
" resultArray
)
another one
clearlistener()
format " in: %
" arr
do (
for index=arr.count to 1 by -1 do (
case of (
(classof arr[index] == array): (
for s in arr[index] do append arr s
deleteitem arr index
)
default: ()
)
)
format "out: %
" arr
) while ((for s in arr where classof s == array collect s).count > 0)
Awesome! Thanks guys
What a clever way to do it, keeps the order also, good stuff! :keenly:
Try this (should be about 3 times faster):
mapped fn FlattenArray arr &result = append result arr
Ussage:
(
mapped fn FlattenArray arr &result = append result arr
arr = #(#(0))
for i = 1 to 50 do arr[1] = #(i,arr[1])
input = for j = 1 to 10000 collect arr
st = timestamp(); sh = heapfree
result = #()
FlattenArray input result
format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
format " INPUT: %
RESULT: %
" input result
)
EDIT: Added example.
Maxscript skill level: Ninja
Quick question while we’re at it, you wouldn’t happen to know a clean way get specific parts of an array? Say you want the third to the last index of an array [3:arr.count]
very nice!
a little addition to support unique items:
mapped fn FlattenArray arr result method:append = method result arr
(
inputArray = #("Item1", #("Item2", "Item3", #("Item1", #("Item3", #("Item6")))), "Item7", #("Item8", #("Item7", "Item10")))
result = #()
FlattenArray inputArray result method:appendifunique
result
)
Great!
It might be a little faster to call makeUniqueArray() with the resulting array instead of appendIfUnique(), but for large arrays it may also take more memory.
You mean something like this?
(
arr = #(1,2,3,#(1,2,"me",4))
arr[arr.count][3]
)
Nope, I should have added “inbetween” in my question. I want all of the elements between index 3 to the last one.
myArray = #(“Stuff1”, “Stuff2”, “Stuff3”, 4, 5, 6, 7, “Stuff8”, “Stuff9”)
With e.g. python, myArray[2:7] will give me #(“Stuff3”, 4, 5, 6, 7, “Stuff8”), I’m wondering if there’s anything similar in maxscript to get parts of an array
there is no anything similar to python list syntax for mxs arrays
arr[3:7] in mxs will be:
for k=4 to 7 collect arr[k]
arr[::3] in mxs will be:
for k=1 to arr.count by 3 collect arr[k]
etc.