Notifications
Clear all

[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”)

14 Replies
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.

1 Reply
(@shcmack)
Joined: 11 months ago

Posts: 0

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
)
1 Reply
(@polytools3d)
Joined: 11 months ago

Posts: 0

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]
)
1 Reply
(@shcmack)
Joined: 11 months ago

Posts: 0

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.

Page 1 / 2