SCRIPTING:

Chloe Jones
ORIENTATION IN A MULTI-DIMENSIONAL MATRIX
Given a Matrix namely an array of arrays, developed either in mono or bidimensional or tridimensional or x-dimensional tiers, provide the coordinates of one point and find out the full coordinates of all the points that are confining with the given one.
A function useful for orientation in mazes and labyrinths, games, or to describe sequences of dots along a trial and to describe swervings from the trial coherent with the whole path, as when you're defining the boundaries of a polygon.
December 2003
{ @ }
The model above is Chloe Jones
LOADS OF CHLOE JONES ON THE NET


PURPOSE OF THE ALGORITHM
Description of the problem

«And you will match this element with the other, paying great care
[An alchemical treatise]

If you find yourself at a specified location within a field which spans throughout x dimensions, and you have the coordinates of the point you're currently stationing at, a significant problem is to find out all the coordinates of all the positions nearby the current given point; in this way you can, for instance, progressively trace a path out or back to such position; this is like when you're searching for a valid trail within a labyrinth (and if you are interested in labyrinths conceived as representations of data structures you can find a long and rather interesting essay titled labyrinths and data structures).

nicholas regnier
Nicholas Regnier, Young Lady, 1626
Most functions that deal with this issue have a significant limit: they hard code the position detection, thus basically yielding results that are not flexible but that are strictly tailored upon and concerned with a fixed amount of dimensions: the good thing with my approach is that you can find out the positions on both bi and tri dimensional matrixes, and even x-dimensional ones, without any setback on the speed of the calculation.

For instance an implementation of this task is provided in a good book titled "Algorithms and data structures in Java" by Adam Drozdek, yet the limitation that that script has is exactly that it hard codes the confining position instead than promoting generality and flexibility in the calculation: you cannot lend it to any other use but that involving a strictly bidimensional matrix.
Its code was (I reproduce only the core):
pushUnvisited(row-1, col);
pushUnvisited(row+1, col);
pushUnvisited(row, col-1);
pushUnvisited(row, col+1);


So while reading it it came to my mind the idea of building a different algorithm capable of dealing with x dimensions and not with just two (1 set of columns and 1 set of rows) as that implementation did. The name of this algorithm of mine is: orientator.
That is the way I produce scripts in this website, challenges pop to my mind while I read and think.

Also, the code above doesn't consider as viable routes the diagonals, it only goes either northward or southward and rightward or leftward, can't go say South-East: my script doesn't take in only the cardinal points but also the diagonals, namely doesn't envision objects only allowed to proceed just as a tower on a chessboard but also as a bishop.

The second specific feature my script has, is that it doesn't deal with the calculation calling in the matrix itself but investing only the numerical coordinates: this basically means that my implementation can be used also as a mere computation pattern or combination-maker like the many powerful ones I presented in full combinations of items from a given input set of elements, or ranged full combinations from a given input set of elements, or the DNA algorithm.
Thus you can use this orientation algorithm also like a mere combination maker if you think you may need combinations of the input items after the schemes that I will describe shortly.


ANALYSIS OF THE PROBLEM
My approach and the description of the inner workings I'm relying upon

If you have a mono dimensional matrix you have basically something that looks like:
012345
If in such over simplified matrix you want to find the confining positions of position 3 you immediately realize they are 2 and 4, namely 3-1 and 3+1 (fully coherent with Adam Drozdek's idea).
If you then imagine a bidimensional matrix you can have the following:
012345
1xxxxx
2xxxxx
3xxxxx
4xxxxx
5xxxxx
       etc...

You may want, for instance, to find out all the positions confining with the point whose coordinates are [3,2], which is highlighted in blue in our example.

The first thing I want you to consider, is that it is immaterial whether you provide as first coordinate the coordinate of the row or rather that of the column: as long as each level is correct in its own coordinate, then it is of no real concern for this script the order in which you pass such coordinates: the only thing which will vary as a consequence of such subjective implication, is the clockwise or counterclockwise procedure, or the tier level that will be tackled first when reporting the coordinates of all the confining points in a tridimensional simulation.

For instance our point can be located not only as [3,2] but also as [2,3] if and only if you keep in mind that by the first coordinate you now meant the row and not the column coordinate (in dhtml you first rank, for instance, the ordinate position of a pixel on the screen, and not its abscissa).

Also, remember that computationally speaking, for this script the first set of coordinates to be tackled with within a dimension, also means starting with the higher number of such set; thus if the first coordinate you pass is meant to be the 3rd dimension, the computations will return the results dealing first with the 3rd dimension, and will start reporting it from the highest index it finds. For instance if the tri-dimensional coordinates are 4,1,1 the first lots reported would look like:
5,2,2
5,2,1
5,2,0
5,1,2
etc...
As you see, it starts from calculating the topmost layer (being your input 3rd dimension number 4, it starts with layer 5: it is topmost as a number, though if you consider it as an array index, which it truly is, well then number 5 is likely to represent a more bottom-level: because 5 comes after 1... On the whole, do not get confused by implications which are entirely subjective). If such 5th entry in the 3rd dimension doesn't exist, please go on reading: this function can skip non existent coordinates, but always assumes by default they all probably exist and thus reports them all by default (that is, let's imagine a layer that stretches in the third dimension but where a number 5 indexed item doesn't exist: all items in the 3rd dimensions are, say, arrays up to index 4 alone: but the function calculates also, so to say, "ghost" boundaries, and takes in also slot 5, imagining a ghost floor -or ceiling- laying below -or above- the current tier).
Thus as long as you keep yourself consequential with the personal preference you start listing and passing your coordinates with, the script will keep returning the confining coordinates always in the same order: namely in a bidimensional example either row by row and then column by column, or first column by column and then row by row, just depending on how you meant your passed coordinates.
Please to have a more consistent view of how these returned coordinates are listed, do try to use the test form in this file and see its outputs with a variety of inputs of your choice.

In our bidimensional example you may have noticed that [3,2] confines as follows:
[2,1] [2,2] [2,3]
[3,1] [3,2] [3,3]
[4,1] [4,2] [4,3]

Now, have you noticed what happens to [3,2]? Once again like in the case of the mono dimensional (pseudo) matrix, both 3 and 2 describe the former a set of sibling numbers such as 3-1, 3, 3+1 in the columns (green numbers), whereas the latter (number 2) has described a set of siblings 2-1, 2, 2+1 in the rows (black numbers).

What if you add a third dimension? Obviously a third coordinate is called in to represent also this new layer, but what visually happens is that two analogous tiers of coordinates get the one superimposed (above) and the other sub-posed (beneath) to the current one.
Allow me to ease my task while drawing these examples, let's imagine the point whose confining coordinates we want to locate is a point located at a visually simple [1,1,1] location:
[2,2,2] [2,2,1] [2,2,0]
[2,1,2] [2,1,1] [2,1,0]
[2,0,2] [2,0,1] [2,0,0]

[1,2,2] [1,2,1] [1,2,0]
[1,1,2] [1,1,1] [1,1,0]
[1,0,2] [1,0,1] [1,0,0]

[0,2,2] [0,2,1] [0,2,0]
[0,1,2] [0,1,1] [0,1,0]
[0,0,2] [0,0,1] [0,0,0]



You may notice that the scheme num-1, num, num+1 repeats itself also for the third coordinate.
Thus you basically have three sets of num+1, num, num-1:
0,1,2
0,1,2
0,1,2
which aren't but sets of numbers that must be now computationally combined together (thence my previous reference to a mere combinatorial computation instead than meddling with the actual matrix). For instance, a first set of coordinates is the one given by the first 0 plus the second zero plus all the numbers in the third row: 000, 001,002.

Upon accomplished this first set of recombinations, you switch on by a number in the second row (not unlike in a clock the hours keep steady while the minutes roll on one by one as long as the seconds are dealt with; when also the minutes are all dealt with, we update the hour and we start back again from the seconds and minutes); thus 00 becomes 01 and you go on combining this with the last row: 010, 011, 012. You proceed like that: 01 becomes 02 and gets combined with 0 and 1 and 2, then 02 becomes 10 and yields 100, 101, 102. Then 10 becomes 11 and yields 110, 111, 112, then 11 becomes 12 and yields 120,121,122, then 12 becomes 20 and so on...

If by the first coordinate you meant the tridimensional layer, the eventually relinquished combination will firstly report the family of the topmost layer confining coordinates, then the middle layer family, then the bottom layer family. Let's say in a clock these would be the hours.
If the second coordinate represents the column coordinate, within each layer the progression in the computation will proceed yielding column by column, and let's say these in a clock would be the minutes, matching them with the rows that in this case would represent the seconds - if conversely the second coordinate was meant as representing the row, within each layer the computation will proceed yielding row by row as if they were the "minutes" and with the columns now impersonating the "seconds".
More exactly, let's assume L=layer, R=row, C= column:
0L, 1L, 2L
0R, 1R, 2R,
0C, 1C, 2C

would yield:
2L 2R 2C, 2L 2R 1C, 2L 2R 0C,
2L 1R 2C, 2L 1R 1C, 2L 1R 0C,
2L 0R 2C, 2L 0R 1C, 2L 0R 0C

1L 2R,2C, 1L 2R 1C, 1L 2R 0C,
1L 1R 2C, 1L 1R 1C, 1L 1R 0C,
1L 0R 2C, 1L 0R 1C, 1L 0R 0C

0L 2R 2C, 0L 2R 1C, 0L 2R 0C,
0L 1R 2C, 0L 1R 1C, 0L 1R 0C,
0L 0R 2C, 0L 0R 1C, 0L 0R 0C


So, keep in mind that for each dimension the default behaviour is to build a set of 3 numbers for each dimension:
num_dimX+1, num_dimX, num_dimX-1
All the returned results would be listed reflecting such order: first num+1, then num, then num-1.
In our example [1,1,1] first will be dealt 1+1 namely 2, then 1 itself, then 1-1 namely 0.


PARTICULARITIES OF THE SCRIPT
Minor cautions while using the script

Note that the current position is reported within the computations. This means you have basically two issues with my algorithm: once relinquished the eventual confining positions:
  • You can rebuild for each set of returned numerical coordinates the pinpointed actual matrix element (see further on how to pass arguments to the script): that is, if the coordinates of one point are [1,0,1], what it is meant and beckoned at is, obviously, yourInputArray[1][0][1]
  • Once you have done this, you have to check whether:
    • The position does not coincide with the current one (say the self), because the self is reported with the result: the function is computation oriented, remember? It just speeds headlong to make the combinations, and this necessarily includes the current position: it would have been very dysfunctional to remove this reference to the self arranging the function to do so, for the valid reason that within a, say, 8 dimension matrix you'd have as many as, if I am not mistaken, 6561 confining sets of coordinates: it would have been truly absurd to check at every iteration of these 6561 combinations whether the current combination is that isolated one of that one single occurrence, namely the self, with the purpose to exclude it once located.
    • Check whether each pinpointed position doesn't overflow the matrix boundaries: it will surely be the case if one number among the ones relinquished as coordinates is a negative number or if it exceeds the amount of slots available in a matrix dimensions (note: remember that index positions are counted starting from zero, not from one).
      Of course I could have instructed the script to detect it on its own, yet it would have made it much slower. Thus a way to avoid this type of out of array boundaries exception, can be the one suggested by Adam Drozdek namely to provide only coordinates of an array which is framed by a set of surrounding columns and rows meant as its walls, and to pass coordinates only within such boundaries and not right on the boundary. That with java.
      In any case by JavaScript all you have to do is even simpler: just check whether the typeof of each reported confining entry is not equal to string "undefined" - this provided you have dutifully populated your matrix with something, like for instances numbers as zeroes or one or whatever you prefer.
paul rubens
Pieter Paul Rubens, The Straw Hat, 1630
At this point my residual problem was to imagine further dimensions, as a 4th one.
As, if I am not mistaken, Roger Penrose stresses in his book "The Emperor's New Mind" the reason it is so difficult to imagine a fourth dimensions we're clearly unfamiliar with in our daily empirical experience, is that, in order to imagine it, we should not try to imagine it too hard. Even better, Roger Penrose suggests not to try imagining it at all: just deal with it.
I stick to this suggestion, for I confess I have problems envisioning a fourth and you can imagine a fifth or sixth or seventh dimension. Thus I, slightly encouraged by Roger Penrose, drew a somewhat not corroborated conclusion: that what applied to 1 and 2 and 3 dimensions would apply also to the 4 or 5 or 6th dimensions I'm unable to envision: thus for each newly added coordinate I produce firstly as many sets of num+1, num, num-1 as many as the passed dimensions are, and then I proceed to recombine all of them in the fore mentioned way.
Anyway I added in the script (see next section) a range argument that will allow you to produce for each dimensions an x amount of numbers before and after it: if such argument is not passed or is passed as zero, it will default to 3, which is what you want. I allowed for this range feature only to add flexibility since it was not detrimental to the performance.

Last but not least, keep in mind that since you have to pass the coordinates as an array, such array must have as coordinates only numbers: that is, if your matrix is not a numerically indexed one but allows for mixed data type (not only numbers, that is; and it would be legitimate in itself) you cannot locate the confining positions for the function cannot perform calculations but on numbers. In fact if your indexes are, say, strings (which I repeat would be legitimate: matrix can be indexed also by other data type than just numbers), there is no way for no function (not only mine) to mathematically deduce that the confining with layer indexed by an arbitrary string such as, say, "layerOne" was a layer indexed by another entirely arbitrary string such as, say, "layerMiddle": this procedure can be meaningful to your human eyes because you know your own elected, devised taxonomy; but computers are only cognizant of the standard worldwide default mathematical taxonomies, and thus can make rational guesses (=calculations) only through numbers.
If you want to convert a mixed data type matrix of x dimensions into a matrix numerically indexed, a powerful and somewhat interesting function to do that is the one named The Hash Scan.

Known Issue: technical note
This function is tailored to work with matrixes that are at least bidimensional.
Though it would work also with simple arrays, I strongly suggest to you to resort to this function only when you have an object more complex than a mere unilinear array (which would truly make little sense: for in order to know what's before and what's after number, say, 8: that's 7 and 9 without further computations!).
In fact in such case the returned result would be a bit different and less rational than what you'd expect: an array of one entry which is an array which holds numbers, but each of these numbers is still encapsulated within an array again, thus yielding three arrays just to return a number,. The reason this happens is that I had, upon feeding the algorithm internal stack (there is a comment in the codex that shows the point where such operation is performed, though only Internet Explorer would show it in the codex field), to provide the first numbers as arrays even if they were single numbers in order to be able to resort since the beginning of the runtime to the javascript array management built in methods, such as concat, which would have not worked if invoked upon mere number data types.


ARGUMENTS OVERVIEW
Description of the function arguments and how to pass them

The function returns an array of arrays: that is, an array whose each entry is an array holding all the numbers representing the coordinates of the position of a confining point with the given one.
So, for instance:
var foo=orientator( new Array(3,2) )
would store in the placeholder foo an object as follows:
foo[0]=array(4,3)
foo[1]=array(4,2)
foo[2]=array(4,1)
foo[3]=array(3,3)
foo[4]=array(3,2)
foo[5]=array(3,1)
foo[6]=array(2,3)
foo[7]=array(2,2)
foo[8]=array(2,1)

Of course if there are more dimensions, each entry will have x numbers accordingly to the x dimensions, for each entry represents the full set of coordinates fit to identify and pinpoint one confining entry.
Thus you can retrieve the number of one of such entries like, for instance:
foo[1][0] //this would return: 4
If some inputs are highly inconsistent, such as an input array of no length, the function can return boolean false.

ARGUMENTS:
Paul Gaugin
Paul Gaugin, Fatata te miti, 1892
array
This is mandatory. It must be an array whose each entry is a number representing one of the coordinate of the point whose confining coordinates must be found.
range
This is the second argument and is optional. It defaults to 3, also if passed as zero. The function will arrange the scope of the numbers to be listed before and after each number passed in the previous array argument. That is, if you have read section 2 of this file, the function has, in the default case, to arrange for each number passed in the first argument a subset that features
num-1, num, num+1.
This argument is not necessary and I made it possible to set it to something else than 3 just because it was possible without affecting the performance, and thus I thought it was better to provide the option. If a function has to be flexible and the performance is not affected and you can give one more option instead than one less without complexifying too much the script, my policy is to provide the option without hesitation.
Thus, for instance, if you were to pass such argument as, say, 5, this would produce for each number a set of as many as 5 numbers like:
num-2, num-1, num, num+1, num+2
Perhaps you have guessed that the symmetry is preserved as long as the number is odd. Were it passed as an even number, say 6, the set would look like:
num+3, num+2, num+1, num, num-1, num-2
That is, the asymmetric number would be added among the upper ones
matrix
It is optional, and it must be the matrix passed as a whole object.
If this argument is set as a matrix, the function after having done the main calculation, will reincarnate the numerical indexes into its equivalent matrix entry, and would return such elements as the entries listed into the returned array.
skip

aria giovanni
The model above and below is Aria Giovanni
[Suze Randall photos, in a Paul Gaugin style!]

ARIA GIOVANNI ONLINE
aria giovanni
aria giovanni
This is optional, and works only if also the matrix argument is passed as a matrix indeed. Conversely, if you pass the argument matrix don't forget to pass skip too.

Passing skip too is arguably what you want to do, if you pass matrix: in fact if you pass the matrix argument you mean you want this function to take care of reincarnating the actual matrix entries after the yielded coordinates, and thereferore you might be interested as well into checking whether some of these "reincarnations" coincide with computational realities but with no actual entry in the matrix.

The function can be instructed, by the agency of this last argument, to exclude from the returned values values of a reincarnated matrix, those values that are either undefined or only those that carry a specified values, or those that are undefined and/or carry some specified value as well. That is:
  • Pass skip argument as string: "undefined" (yes, just like that: and no leading or trailing whitespaces):
    The function will skip all those matrix entries that have no value assigned or that do not exist, namely whose typeof reports precisely "undefined", reporting instead of them boolean false.
  • Pass skip argument as whatever except the string saying "undefined":
    The function will skip all those matrix entries whose value matches what has been passed as skip argument, reporting instead of them boolean false.
    For instance if you pass skip as 5, the returned output will carry false in those entries whose pinpointed matrix element was equal to number 5.
  • Pass skip argument as Array:
    The function will skip both those matrix entries whose typeof is "undefined" and those that are equal to the first item of the array, reporting instead of them boolean false.

    Remember that in javascript a set of square brackets isolated mean an array whose first entry is the listed element, whereas if you would use the new Array constructor, be warned that a statement like new Array(5) does not mean that the first element of the array is 5, but that is an array whose length is five, namely of 5 unassigned, undefined elements!
    Conversely if you initialize an array as a set of square brackets, that would imply that the only element listed (in case like in our example we list only one element in the array, which is legitimate) does not represent the length of the array but just the first element in the lot.

    So if argument skip is, say: [5] the function will report as false both those entries that are "undefined" and those whose eventual held item was number 5.
    Sorry, no further specifications are allowed: if you want to exclude more values, you have to do it by yourself scanning the whole reported matrix and sifting what you want and you don't want by yourself: I could not account for this in the function because the chances are endless and would have made little sense allowing for that in a function whose likely purpose is, at most, of skipping undefined or a specified value which the user assigned a particular meaning to (instance: number ones might mean: coordinates of a wall, and thus not truly viable ones).


THE CODEX AND THE TEST FORM
Copy the codex or test the functionalities

THE CODEX & THE TEST FORM
claude monet
Claude Monet, Garden Path At Giverny
lines:
Paul Gaugin
Paul Gaugin, Aha oe feii, 1892
input array coordinates (separated by whitespace):
          [Optional: range: ]
   
John Frederick Lewis
John Frederick Lewis, Life in the Harem, 1857


HINTS ON THE INNER WORKINGS
A very technical section. Skip if uninterested

The core runtime engine of the orientator is pretty short; I add here a few comments:
ranges.pop();
//RUNTIME:
while(ranges.length){/*it reiterates for as many dimensions there are less 1. If 3, only 2 times!*/
var prepend=ranges[ranges.length-1];
var newOutput=new Array(0);
for(var p=0; p<prepend.length; p++){/*if range=3, is a loop of only 3 entries!*/
for(var s=0; s<output.length; s++){/*this is the truly long loop for it increases over time by a pow of d dimensions*/
newOutput[++newOutput.length-1]=[ prepend[p] ].concat(output[s]);
}
}
output=newOutput; /*newOutput was bigger than the previous output: thus output is increasing*/
ranges.pop();
}
//OVER.

The variable named ranges, is the array that hosts the sets of [num+1, num, num-1] for each dimension (dimensions=input array length).
That is, ranges is an array of arrays, an array whose each entry is an array of a set of numbers like [num+1, num, num-1], 3 numbers as you see: at least if the argument range is the default number 3 (by the way, do not confound the argument named range with the internal variable named ranges with an ending s).

We start reiterating this set of ranges (which anyway at inception has already been deprived of the 1st dimension lot which has been assigned to the variable named output as a feeding decoy to get started; the variable named output would therefore merely consist, at the beginning, of one set of [num+1, num, num-1] items. You'll soon see why the last ranges set of num+1, num, num-1 has been assigned to output and deleted from ranges: for now let's imagine such numbers are [2,1,0]).

At the first reiteration we assign to a local variable named prepend the last entry in ranges, thus getting for instance another isolated set like [num+1, num, num-1] which could be for instance [6,5,4] if the coordinate num was 5. We must now combine all these prepend items with all the items in output.

So we scan this prepend [6,5,4], item by item.
For each of its items we scan the output array which at inception carries, as I said, the first dimemsions [num+1, num, num-1]: let's imagine as I said that they are [2,1,0].
In this way I can now build the combinations. In fact, by prepending to each of the entries in output each of the prepend entries we get:
[6,2] [6,1] [6,0] //this is the first iteration of prepend
[5,2] [5,1] [5,0] //this is the second iteration of prepend
[4,2] [4,1] [4,0] //this is the third iteration of prepend
we also have saved each of these newly produced arrays in a temporary array named newOutput, and upon completing the population for this round of prepend, we entirely rewrite the output with this newly generated newOutput.
We now pop the variable named ranges. Ready to tackle with a step higher in the ladder: and if none, of course the function understands it has accomplished its tasks.

Let's imagine it has not finished yet for the dimensions are three and there is one last set of [num+1, num, num-1] stored in ranges. Now, at the next iteration, we have in the last entry of ranges this new set of entries. This is the new prepend.
Each of these entries must be matched (prepended, more specifically) with all the items in output, exactly like we did before: but now output, which at beginning was just a set of 3 entries, grew to a set of as many as 9 entries!
Don't make me list all of them now, say sort of:
[x, 6,2] [x, 6,1] [x, 6,0] [x, 5,2] [x, 5,1] [x, 5,0] [x, 4,2] [x, 4,1] [x, 4,0]
[y, 6,2] [y, 6,1] [y, 6,0] [y, 5,2] etc.

Thus each new element in prepend will now produce not just 3 combinations but each of them will yield as many as 9 combinations, thus yeilding an overall 3*9=27 combinations for this ranges set: these newly produced combinations will now be assigned as the new whole lot of elements -or say of the already processed combinations- to be stored in output.
In other words the trick is: every newly produced combination is stored into output so to be bequeathed to the next elements waiting up the ladder as a whole set of fully valid matching partners already processed and completed by the previous processes: so no need to redo them, you just have to recombine the all of them with each new entry waiting in the stack.
Thus these combinations are stored in output.
The eventual output, being the matching of the topmost ranges array with all the previously produced combinations, also relinquishes/coincides with the full set of searched for combinations, and this is precisely the output that is returned.

You thus have your coordinates, calculated as a combinatorial game. Cool, isn't it?