|
|
|
|
|
|
|
|
|
HOW TO UNFOLD A MATRIX WHICH HOLDS NO STRUCTURED DATA
|
|
An interesting achievement: do you have a matrix (that is: an array of arrays) whose branches can hold an unlimited amount of other branches, without having neither a binary nor another regular structure, but an highly unpredictable one? Do you want to scan such a matrix and extract all data from the end of each branch?
|
|
November 2001 |
|
{ @ }
|
|
|
|
|
A NOTE |
For the first time in over one year I find a script is aged.
I keep it here for something in it is interesting but if you want unfolders of an Array that are faster and that unfold better and that can unfold also associative arrays and moreover even map them:
CLICK HERE
|
The code of this recursive function
|
|
It took me a month, but it is a fairly convincing algorithm for people dealing with a real world, which might often be a confused or messed up one: a recursive function that can inspect a tree (arrays of arrays) regardless of its branching and expecting no pattern regularity
|
They often assume your data structure should be regular. Well, what if you have to inspect an array of arrays which has no structure at all, that possesses no regularity or patterns? I do not deem this algorithm the best possible one, but I do deem it a fairly convincing one. It may seem long and confused, but after all if you inspect it, it is just made up of two major concise conditional checks with two more equally square checks nested in each of them, performing repetitive tasks summed up in 4 or 5 lines: in other words, whenever this algorithm is run, it has just to make two triflesome checks to immediately grab what block must be executed! Of course, if you should flatten an array of arrays, javascript would apparently do this automatically: in fact, if you include in within an alert() method an object which is an array of an array without regularity, the alert would show to you all its entries in a flattened row as if they were a single array with no branching But that is deceptive, actually it has not been flattened. So what we wanted to achieve was a subroutine that could yield out of an unpatterned matrix a single linear array whose each entry were an entry of the matrix, meaning by the latter each entry of the matrix which held no other arrays but a final data (number, string, character, whatever but not another composed object like an array is). So, here's the code, and after it its documentation and explanations:
function unfoldMatrix(array,output,downfall,TREE,branch){
if(!output){
if(!array.length){return array};
output=new Array(0);
downfall=new Array()
downfall.length++
downfall[downfall.length-1]=array.length
TREE=new Array()
TREE.length++;
TREE[TREE.length-1]=downfall[downfall.length-1]
branch=array[TREE[TREE.length-1]-downfall[downfall.length-1]]
}
if(typeof(branch)=="object"){
if(!branch.length){
if(downfall[downfall.length-1]==0){
if(downfall.length==1){return output};
var pop1=new Array();
var pop2=new Array();
for(var i=0;i<downfall.length;i++){
pop1.length++;pop2.length++;
pop1[pop1.length-1]=downfall[i]
pop2[pop2.length-1]=TREE[i]
}
TREE=pop2;
downfall=pop1;
downfall[downfall.length-1];
branch=array[TREE[0]-downfall[0]]
for(var k=;k<downfall.length;k++){
branch=branch[TREE[k]-downfall[k]]
}
}
else{
downfall[downfall.length-1];
branch=array[TREE[0]-downfall[0]]
for(var k=;k<downfall.length;k++){
branch=branch[TREE[k]-downfall[k]]
}
}
}
else{
downfall.length++
downfall[downfall.length-1]=branch.length;
TREE.length++
TREE[TREE.length-1]=branch.length;
branch=array[TREE[0]-downfall[0]]
for(var k=;k<downfall.length;k++){
branch=branch[TREE[k]-downfall[k]]
}
}
return unfoldMatrix(array,output,downfall,TREE,branch);
}
else{
if(downfall[downfall.length-1]==0){
if(downfall.length==1){return output};
var pop1=new Array();
var pop2=new Array();
for(var i=0;i<downfall.length;i++){
pop1.length++;pop2.length++;
pop1[pop1.length-1]=downfall[i]
pop2[pop2.length-1]=TREE[i]
}
TREE=pop2;
downfall=pop1;
downfall[downfall.length-1];
branch=array[TREE[0]-downfall[0]]
for(var k=;k<downfall.length;k++){
branch=branch[TREE[k]-downfall[k]]
}
}
else{
output.length++;
output[output.length-1]=branch;
downfall[downfall.length-1];
branch=array[TREE[0]-downfall[0]]
for(var k=;k<downfall.length;k++){
branch=branch[TREE[k]-downfall[k]]
}
}
return unfoldMatrix(array,output,downfall,TREE,branch);
/*keep this comment to reuse freely
http://www.unitedscripters.com */}
}
Explanations and implementation
|
|
Block by block, how it works
|
We have included in the function some comments referred to as BLOCKS, so now we explain every block workings. Some of these explanations might appear obscure at first, but while you go on they'd get clearer and clearer:
- : since this function requires, upon being invoked, only one argument to be passed (the matrix which has to be unfolded), we check if the function has no furhter argument passed, which is accomplished verifying if the output argument appears defined. If not, the subroutine understands it is the first time it runs and makes the preparatory steps, undertaken only the first run:
- The output argument is initialized as an empty array, since it will carry in a linear array each ultimative entry found in the branched matrix.
- The TREE argument will hold the length found in each branch (being an array of arrays, it is possible that each entry holds another array, which has a length of its own)
- The downfall argument is the same as the TREE argument, but the difference is that, when we find a final entry (an entry which holds no longer another array but a conclusive data, we decrease by one the downfall argument. You'll see later why.
- The branch argument will carry a reference to the current position we are inspecting. To initialize it, it will obviously be the array[0] entry, which is what the branch=array[TREE[TREE.length-1]-downfall[downfall.length-1]]
is for: in fact since TREE entries carry a number which is the length of the current array branch, when we start it has one single entry which is the length of the matrix itself: for instance 4; if you subtract to the entry of TREE the entry of downfall (which is only one at the first run), you yield zero in the very first run, which is tantamount to saying: array[0]. You'll understand better soon.
- WE now check if the typeof of branch is an object or not. If it is an object, it means that it is not a final data but another object, so it is an array (a Matrix is an array of arrays).
Since it is an array, we proceed as follows (beware, we anticipate, you'r better jump to point 7 and read from there what happens in case the end of a branch is found, and then get back here at point 2):
- If it has no length () we must go back since it has no meaning to scan an array which holds zero entries anyway, thus we have to decide, in order to go back and to inspect the subsequent entry, whether we can decrease downfall by one () for the same reasons explained in points 10 , 11 (see them; anyway this is: we can decrease it only if it is not equal to zero yet, which would imply it has exhausted a whole branch) or we can't () with the remarkable exception that since this branch has no length, would call for no updating of output (we do not execute step 9, that is). So we proceed as we would at step 13 (see it) since an array with no length can be considered under a practical point of view like an array which has already been fully scanned!
- If it has a length we add an entry to TREE and downfall both and then we set this latest entries to the length of this branch: that is, if branch is at the first run equals to array[0] and type of array[0] turns out being an object, it means that within a matrix setting it must be another array, so it has a length of its own.
- We now update the branch object: we provide a loop on the TREE array (TREE and downfall have been already updated to carry an adjunctive entry with the length of this found branch): we then build up branch reference by adding to it a reference to this latest branch: pay attention: if branch is equals to array[0] at the first start, and it is found that array[0] is an array entry which holds another array, then this latest array has another [0] entry of its own, so the new reference should be array[0][0]. Consequently, the procedure says: branch=branch (...), which can be translated into array[0]=array[0] (...), so that we have now to add it the subsequent index. We deduce this index with the same procedure seen in point 1 sub point 4: we subtract downfall[X] from TREE[X] and assign its result as the index that has to been added to branch (which is array[0], and awaits to know what index has to be added).
- We return the function itself, to see if also this latest branch object holds another array.
- If not, it means that we have reached the end of a branch, we have a final data!. We're thus at which deals with the case the branch is no more an object (an array, that is) but a final data.
- If it is such, we have two possibilities: let's start with the second one which is
- : we have found a final data, we increase output, the array meant to include all the final data straightened in a simple linear array, and we add to it branch that at this stage is no more a reference to an array, but to a conclusive data, string number or whatever it might ever be (such as, let's imagine, some array[1][0][0][1][3]="hallo world" would be! branch simply expresses the same entity in a more straightforward way whereas all the indexes are concealed into a single variable name which conceals nothing short of a reference, pointer- if you don't know what a pointer is, I'm not surprised as it is said JavaScript or Java have no pointers like C++ or Perl: quite false, and this branch variable demonstrates it; but to explain here what a pointer is would well exceed the range of a mere documentation like this file is meant to be).
- Once added it to output, we decrease the downfall latest entry by one.
- Why the hack we do this? This is the core trick of this algorithm!
In fact since all the detections of the stage we're at are determined by subtracting from the latest TREE entry the latest downfall entry, if we decrease downfall by one every time a final data is reached and picked, this means that every subsequent subtraction of downfall's latest entry from TREE latest entry will yield lower and lower numbers: think of it: if TREE[X]-downfall[X] equals zero when both of the are equals, if we decrease downfall by one when one data is finally picked, a difference between the TREE[X] (the unaltered length of an array included in the matrix) and a diminished downfall[X] would be no longer zero, but one: we can this way let the function argue that 1, and no longer 0, is the subsequent index which has to be passed to branch (which is exactly what we do soon after the --downfall[downfall.length-1] statement in BLOCK 2B) for further inspection: and since branch is the same as array[0] in the simplest case, to pass to branch the difference between those variables would be like eventually yielding array[1], array[2], array[3] and so on until...
- Until the TREE[X]-downfall[X] difference would yield a number which is the length of the branch or (which is the same as this condition) when downfall[X] equals to ZERO. When this happen, we're at...
- chance: downfall[X] is equal to zero: we have inspected the whole of this array!. This means that we can eliminate from TREE and downfall both their last entries: they are useless by now. WE could have used the javaScript pop() method to cut out the last entries, but this would have caused the function to cut off also the backbone, the first array which holds all the matrix eventually; thus we chose to rewrite manually the TREE and downfall arrays excluding their latest entry by parsing in a loop all their length except the last one, which is what the expression downfall.length is for.
We build two local variables on the fly and then we assign their results to TREE and downfall.
- At this stage the expression --downfall[downfall.length-1] is absolutely necessary: having shortened the TREE and downfall arrays, we're back to the previous branch, but if we don't decrease once again, we would start once again reinspecting (having gone back to the future...) the already inspected branch. So, after decreased, we rebuild the reference to the correct branch following the procedure identical to those described for branch at point 3.
- The condition is when downfall equals to zero and also downfall length equals 1: this means that we have inspected all, we're on the backbone which holds the whole matrix, and we're at the last entry, nothing else is left
To invoke this function just do, assuming MATRIX is a matrix:
unfoldMatrix(MATRIX)
For your convenience I include here a foo-foo matrix with no pattern, so you can see that this function works also on a highly branched matrix:
var testArray=new Array()
testArray[0]=new Array("one")
testArray[1]=new Array("two","three")
testArray[2]=new Array()
testArray[2][0]="four"
testArray[2][1]="five"
testArray[2][2]=new Array()
testArray[2][2][0]="six"
testArray[2][2][1]="seven"
testArray[2][2][2]=new Array()
testArray[2][2][2][0]=new Array()
testArray[2][2][2][0][0]=new Array()
testArray[2][2][2][0][0][0]="seven again!!!"
testArray[2][2][2][0][0][1]=new Array("Joker 1","Joker 2")
testArray[3]="eight"
testArray[4]=new Array()
testArray[4][0]=new Array()
testArray[4][0][0]=new Array()
testArray[4][0][0][0]="nine"
testArray[5]=new Array(0)
testArray[6]=10
So, wanna test it right on the example above?
|
|