View Full Version : How to convert a file into an array with sublevels ?

 prettyPixel09 September 2005, 03:37 PMHi guys, Let us say that I define an array in a file. the file: ( a1 a2 ( b1 b2 ( c1 c2 ) b3 ) a3 ( b4 ( c3 ) b5 ) ) For practical reasons, I converted this file into an linear array: thefile=#("(","a1", "a2", "(", "b1", "b2", "(", "c1", "c2", ")", "B3", ")", "a3", "(", "b4", "(", "c3", ")", "b5", ")",")") Now I would like to transform this file into an array with sublevels. Here the expected result written by hand: #(a1,a2,#(b1,b2,#(c1,c2),b3),a3,#(b4,#(c3),b5)) How do you make that ? I tested several recursive functions but without success. Of course I cannot just add # in front of ( and some commas :cool: Can anyone point me in the right direction ? Thanks
stuh505
09 September 2005, 05:32 AM
heres some pretty basic pseudocode to get you going:

new array anArray

position = 0
while (elt != flag)
(
anArray append elt
elt = getElement file position != flag
)

getElement file possition
(
if token file position == "(" then
(
new array2
while token file position != ")"
(
array2.append token
)
return array2
) else

)

prettyPixel
09 September 2005, 10:40 AM
This code is pretty similar to the functions that I have tried.
But there are no recursive logic and because of that the array is not yet correct.

Test this code:
(
if pos>array.count
then false
else (
end=pos
bloc=0
do (
if array[end]=="(" then bloc+=1
if array[end]==")" then bloc-=1
end+=1
)
while ( bloc>0 and end<=array.count )
if pos==(end-1)
then array[pos]
else ( newArray=for i=pos to (end-1) collect array[i] )
)
)--fn

fn getArrayFromPseudoFile pseudoFile =
(
newArray=#()
position=1
element=true
while element!=false do
(
if element!=false do append newArray element
if classof element==Array
then position+=element.count
else position+=1
)--while
format "\n\nnewArray=%\n" newArray
format "\nprint newArray\n\n"
print newArray
format "\n===========\n"
)--fn

clearListener()
pseudoFile=#("a1", "a2", "(", "b1", "b2", "(", "c1", "c2", ")", "b3", ")", "a3", "(", "b4", ")","a4")
getArrayFromPseudoFile pseudoFile

the array:
a1
a2
(
b1
b2
(
c1
c2
)
b3
)
a3
(
b4
)
a4

The function return this array:
newArray=#("a1", "a2", #("(", "b1", "b2", "(", "c1", "c2", ")", "b3", ")"), "a3", #("(", "b4", ")"), "a4")

print newArray

"a1"
"a2"
#("(", "b1", "b2", "(", "c1", "c2", ")", "b3", ")")
"a3"
#("(", "b4", ")")
"a4"

As you can see the result is not yet correct.
Nevertheless Thanks for your help. It is a step moreover towards the solution. :)
It misses a recursive logic.

Does somebody have an idea?

prettyPixel
09 September 2005, 12:47 PM
I have just found a solution.
fn arrayTransformer array =
(
pos=1
startBloc=0
while ( array[pos]!=")" and pos<=array.count) do
(
if array[pos]=="(" do startBloc=pos
pos+=1
)
if startBloc==0 then false
else (
startArray=for i=1 to startBloc-1 collect array[i]
blocArray=for i=startBloc+1 to pos-1 collect array[i]
endArray=for i=pos+1 to array.count collect array[i]
startArray+#(blocArray)+endArray
)
)--fn

fn arrayStructureFinder array =
(
retValue=array
do (
array=retValue
retValue=arrayTransformer array
)
while retValue!=false
array
)--fn

pseudoFile=#("a1", "a2", "(", "b1", "b2", "(", "c1", "c2", ")", "b3", ")", "a3", "(", "b4", ")","a4")
structuredArray=arrayStructureFinder pseudoFile

clearListener()
format "structuredArray=%\n" structuredArray

It requires many manipulations in memory.
I do not think that it is the ideal solution... but It works.
If somebody has a better method. I am still interested to know it.

Have a good day.

stuh505
09 September 2005, 03:13 PM
I'ts inefficient because you uneccessarily make many copies of the array.

You should make one function that can return either an element or an array. This function should call itself in order to find the next element if it's going to be returning an array.

In the place where I wrote "array2.append token", if you replace that with a call to "getElement" then it will be recursive and the concept of it will work.

prettyPixel
09 September 2005, 10:57 AM
Ok I restart from the begining.
If you help me a bit, I could write an optimized script.
First I convert your pseudo code in maxscript.

I make some modifications.
I add the variable 'bloc' to avoid an error : the function would return
an incomplete structure if it stopped with the first ")"

ok after that getElement return:
getElement pseudoFile 2
==> "a2"
getElement pseudoFile 6
==> #("c1", "c2")
getElement pseudoFile 3
==> #("b1", "b2", "(", "c1", "c2", ")", "b3")
There is not yet an recursive logic. But do you agree with the results ?

The script:
fn getElement array position =
(
if position>array.count then false
else
(
if array[position]=="("
then (
position+=1
bloc=1
array2=#()
while ( bloc>0 and position<=array.count ) do
(
append array2 array[position]
position+=1
if array[position]=="(" then bloc+=1
if array[position]==")" then bloc-=1
)
array2
)
else array[position]
)
)

pseudoFile=#("a1", "a2", "(", "b1", "b2", "(", "c1", "c2", ")", "b3", ")", "a3", "(", "b4", ")","a4")
anArray=#()
position=1
count=1
elt=getElement pseudoFile position
while (elt != false) do
(
if classof elt==Array then position+=elt.count else position+=1
append anArray elt
elt=getElement pseudoFile position
)
format "anArray=%\n" anArray
The next step is to add the recursive call.

prettyPixel
09 September 2005, 01:17 PM
I have just found the code recursive :)
fn arrayFromFile array pos =
(
newArray=#()
while pos<=array.count do
(
if ( array[pos]!="(" and array[pos]!=")" )
then (append newArray array[pos])
else (
if array[pos]=="(" do
(
nextPos=pos+1
pos+=1
bloc=1
array2=#()
while ( bloc>0 and pos<=array.count ) do
(
append array2 array[pos]
pos+=1
if array[pos]=="(" then bloc+=1
if array[pos]==")" then bloc-=1
)
append newArray (arrayFromFile array2 1)
)
)
pos+=1
)
newArray
)

clearListener()
pseudoFile=#("a1", "a2", "(", "b1", "b2", "(", "c1", "c2", ")", "b3", ")", "a3", "(", "b4", ")","a4")
structuredArray=arrayFromFile pseudoFile 1
format "structuredArray=%\n" structuredArray

stuh505 thanks for your help. You pointed me in the right direction.

stuh505
09 September 2005, 03:48 PM
Here's a more concise way of doing it. By the way you don't need to use recursion, you could use a list to store temporary lists as a kind of stack. How do you paste code with indentation?

fn sublevelarray list=
(
pos=0
return (getNextSublevel list)
)

fn getNextSublevel list =
(
pos+=1

if (pos > list.count) then
return
if (list[pos]==")") then
return ")"
if (list[pos]=="(") then
(
newArray=#()

while ((ret = getNextSublevel list) != ")") do
append newArray ret

return newArray
)

return list[pos]
)

myarray=#("(","1","2","(","3","4",")",")")
sublevelarray myarray

EDIT: I wrote mine to deal with an original list that has "(" and ")" at the front and back

prettyPixel
09 September 2005, 05:57 PM
How do you paste code with indentation?
Oh easy. I copy/paste from maxscript editor. I select my script and press # in toolmenu of the post editor. That adds the header 'CODE' to keep the indentation.
code with identation (remove the "_")
1
2
3
If you use an another editor, try this to remove the special format: open notepad and press CTRL+V(paste),CTRL+A(select all),CTRL+X(cut)

Could you post your code with indentation ?
Thanks

stuh505
09 September 2005, 09:45 PM
fn sublevelarray list=
(
pos=0
return (getNextSublevel list)
)
fn getNextSublevel list =
(
pos+=1

if (pos > list.count) then
return
if (list[pos]==")") then
return ")"
if (list[pos]=="(") then
(
newArray=#()

while ((ret = getNextSublevel list) != ")") do
append newArray ret

return newArray
)

return list[pos]
)
myarray=#("(","1","2","(","3","4",")",")")
sublevelarray myarray

prettyPixel
09 September 2005, 10:13 PM
What value return this line ?
if (pos > list.count) then
return
the next line ?
I acknowledge that I am lost in the code.

stuh505
09 September 2005, 10:30 PM
getNextSublevel returns the next element to be put into an array. This element could either be an individual or an array of elements.

If the next token is a number, then its obvious that the function should simply return the number. If the next token is a "(", then instead it creates a new array, and then goes into a loop to call itself to find the next element to put into the new array UNTIL it finds a ")". Once it has found ")", it just returns this new array as the element!

There are 4 possible values for the character that must be considered:
1) out of bounds,
2) ")",
3) "(",
4) anything else.

I didn't use "else" statements to separate them because maxscript doesn't seem to like having lots of elses, but it doesn't really make a difference.

Do you get it?

prettyPixel
09 September 2005, 12:53 AM
oh now I understand.
It's so simple ... but That isn't so easy to understand recursive logics.

The only thing that I still don't understand is the out of bounce test. :bounce:
if (pos > list.count) then return

If I remove this line the script don't crash!
Are you sure that this line has an effet ?

stuh505
09 September 2005, 02:42 AM
It doesn't crash because the only thing that makes it keep trying to get another element is the loop that looks for ")". Therefore, if your inintial list has one ")" for every "(", then it will never even CHECK an element beyond the last one.

However, if you do not put ")" for every "(" that you start, then it will try to get an element when there are none left...it will try to return an element thats out of bounds of the array (at the bottom of the function)...so we need to return out of the function before it gets to that point.

prettyPixel
09 September 2005, 09:46 AM
I just tested It with an unfinished array but max crash.
But if I modify the line with:
if (pos > list.count) then return ")"
It works with a bad array.

Now I can use this script for my file reader ;)

The only thing that I would like to improve is that if the array don't begin by "(" the recursive function don't start, and It return a single value.
Are there a way to avoid this ?
Of course I can add a test before the first call and add "(" + ")" around the array...
Is it the best solution ?

Here is the actual script:
global pos

fn getNextSublevel list =
(
pos+=1
if (pos > list.count) then return ")"
if (list[pos]==")") then return ")"
if (list[pos]=="(") then
(
newArray=#()
while ((ret = getNextSublevel list) != ")") do append newArray ret
return newArray
)
return list[pos]
)

fn sublevelarray list=
(
pos=0
return (getNextSublevel list)
)

clearListener()
myarray=#("(","1","2","(","3","4",")",")")
sublevelarray myarray

stuh505
09 September 2005, 12:55 PM
Oh, right...in my previous code it was going to go into an infinite loop looking for a ")" if it tries to step out of bounds! So yeah, your solution there is a good one.

pos does not need to be a global variable, because it is all within the scope of the function sublevelarray.

It is common for recursive functions to behave slightly differently on the very first call which is why is one of the reasons why it is good practice to use what is called a "wrapper" function...one function that basically just calls the other function, while changing a couple input parameters. We already use that to initialize pos to zero. We can also use that function to make it work for arrays that dont start with "(".

fn sublevelarray list=
(
pos=0
newArray=#()
while(pos < list.count)
(
ret = getNextSublevel list
if (ret != ")")
append newArray ret
)
)

Didn't test it but that should work

prettyPixel
09 September 2005, 03:41 PM
pos does not need to be a global variable, because it is all within the scope of the function sublevelarray

Oh I agree that 'pos' does not to be a global variable... But max doesn't accept getNextSublevel because 'pos' is unknown in this fonction. I think the only solution to avoid this is to send the position value (pos) by argument in getNextSublevel.

getNextSublevel list pos

And getNextSublevel should return a double value to update position (because it's an recursive fonction):
ret=getNextSublevel list pos <--- #( theArray , thePosition )
newArray=ret|1]
pos=ret[2]

This solution will increase the length of the code but I do not know how to make it differently. (any idea ?)
It is necessary not to leave pos have has global variable.
My file should be able to contain calls towards other files on the drive.
In fact it is not an array which I must read but code with include functions...
In this case a global variable would not function at all.

stuh505
09 September 2005, 04:45 PM
Oh I agree that 'pos' does not to be a global variable... But max doesn't accept getNextSublevel because 'pos' is unknown in this fonction. I think the only solution to avoid this is to send the position value (pos) by argument in getNextSublevel.

Ah, I think I had accidentally declared it global before...so I was then able to compile code that did not declare it global because it was already global, causing me to overlook this. Whoops.

We only want to have 1 position value that gets used...we don't want each recursive call to be keeping track of a different position value. We could achieve this by passing a reference pointer to the location of memory where this value is stored. This way each recursive call would be dealing with the same value...even though they are storing the address of the value multiple times in memory. I don't know how to do pointers in max script so I'm going to leave it at that, this is more of a c++ concept.

EDIT: apparently MAXScript uses references for everything by default, which means that you could also simply pass "pos" as a parameter to the function, and everything would work. However, I think this would be very confusing because you would expect this to only be modifying a local copy when it in fact would be modifying a shared value...at least according to the documentation.

However, what we can do is make a separate "object" that represents the list and a unique position value...and when we pass this around, we're passing around a reference to the object automatically.

What I've done is made a new structure (same as a class) which defines a listIter, which is something that does this. Then I rewrote the other code to operate with it.

struct listIter
(
list, pos=1,
fn get = list[pos],
fn outOfBounds = (pos > list.count),
fn moreLeft = (pos < list.count)
)
fn sublevelarray list=
(
iter = listIter list

newArray=#()
while (iter.moreLeft() ) do
(
ret = getNextSublevel iter
if (ret != ")") then
append newArray ret
)

return newArray
)

fn getNextSublevel iter =
(

if iter.outOfBounds() then
return ")"
if (iter.get() == ")") then
return ")"
if (iter.get() == "(") then
(
newArray=#()

while ((ret = getNextSublevel iter) != ")") do
append newArray ret

return newArray
)

return iter.get()
)

myarray=#("(","1","2","(","3","4",")",")")
sublevelarray myarray

prettyPixel
09 September 2005, 06:32 PM
Oh man... your code is now clear like a diamond :thumbsup:
I like really the structure definition to execute functions.
WOW many thanks really.

What happens it if I call sublevelarray during the execution of getNextSublevel ?
The position value is different ?
Because if I include path of other files in my main file maybe I will read the new file before closing the main file. You see ?
Is this a problem?

have a good day

stuh505
09 September 2005, 10:57 PM
A structure / class is like a definition of an object. For example, a structure/class would say, "a person is something that has name, height, and weight".

When you instantiate a person, you are creating one of those things (called an object). For instance, you create a person object name=john, height=5, weight=10.

You can create as many people as you want, and those people obviously aren't going to share the same numbers.

Each time sublevelarray is called, it will instantiate a new listIter object that has it's own private variables (such as pos)...so it's not a problem :)

prettyPixel
09 September 2005, 01:33 AM
Thank you again for all explanations.

That will enable me to develop the file reader. When I would be a bit more advanced I will be back ;)

prettyPixel
09 September 2005, 11:24 AM
Hi stuh505, and Hi all

I have an initialisation problem too.
Here is my script:
struct listIter
(
list, pos=0,
fn get = list[pos],
fn outOfBounds = (pos > list.count),
fn moreLeft = (pos < list.count)
)

global sublevelarray

fn getNextSublevel iter =
(
if iter.outOfBounds() do return ")"
retValue=iter.get()
if (retValue == ")") do return ")"
if (retValue == "include") do ( retValue=sublevelarray pseudoFile2 )
if (retValue == "(") do
(
newArray=#()
while ((retValue = getNextSublevel iter) != ")")
do (
if (retValue=="include") do ( retValue=sublevelarray pseudoFile2 )
append newArray retValue
)
return newArray
)
return retValue
)

fn sublevelarray list =
(
iter = listIter list
newArray=#()
while (iter.moreLeft() ) do
(
ret = getNextSublevel iter
if (ret != ")") then append newArray ret
)
return newArray
)

clearListener()
pseudoFile1=#("(","a1","include","a2","a3",")")
pseudoFile1b=#("a1","include","a2","a3")
pseudoFile2=#("b1","b2","b3")

theArray=sublevelarray pseudoFile1
format "theArray=%\n" theArray
When getNextSublevel calls sublevelarray the first time, getNextSublevel does not know it.
note: The error occurs at the first execution of the script only, It is necessary to quit 3DSmax to re-examine the error.
If you run the program a second once, it will work.

I see the problem, but I don't have a solution.
I have tried to add "global sublevelarray" at the beginning of the script but it doesn't work.

Any help? thanks

stuh505
09 September 2005, 04:03 PM
Usually in a programming language, you would declare all your functions/classes at the top (so that it knows which ones exist) and then actually define the functions lower down, where order does not matter. I have been having some trouble with this same problem using maxscript.

Maxscript seems to support this PARTLY. It doesn't always let you declare them, but it does let you re-define them twice...so you can create an empty stub struct and then re-define it later on. This seems to work as long as you don't try to access any of the local variables until after it has been truly defined.

For rollouts:
rollout rolloutName "rolloutTitle" (button none)

For structs:
struct structName(void)

For functions:
function functionName=()

CGTalk Moderation
09 September 2005, 04:03 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.

1