Re: Convert any list to a table?
Re: Convert any list to a table?
- Subject: Re: Convert any list to a table?
- From: Ric Phillips <email@hidden>
- Date: Thu, 20 Sep 2001 13:41:34 +1000
On 19/9/01 12:53 PM, "Jason Bourque" <email@hidden> wrote:
>
>
set anyList to {"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh"}
>
>
my createFalseTable(anyList, 2)
First - this is a ridiculously long post. But this one is just so
'archetypal', and so much fun, I couldn't resist. And I hope this is
informative and useful to someone out there.
I am not going to 'fix' Jason's code. Rather I thought it might be fun to
examine a possible approach to the problem in general terms....
To state the problem...
For any list of text items like,
{"aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg"}
Given a number of columns, say 2,
Transform it into a tab and return delimited string that will take the form,
"aaa eee
bbb fff
ccc ggg
ddd"
That is the 'table' cells are loaded by column first.
(I haven't built the complete program, just a solution to the core problem
of re-ordering arrays (lists))
Method
----------------------------------------------------------------------
1) Ignore obvious exceptions. Eg, empty input lists, situations where the
number of columns exceeds the number of items in the list ({"a","b","c","d"}
into 5 columns => "a b c d"). These are trivial and can easily be
pre-handled before the real work is done.
2) Ignore the formatting issue: Any white space padding to make the columns
line up neatly is a separate coding problem and can be handled later. More
importantly, even the returns needed to break the final string into rows are
really just a formatting problem and can be ignored.
3) Disregard the type conversion (list to string) issue, this is also not
really the main problem.
4) Having done this we are left with a fairly clean problem: How to
transform a one dimensional array into a two dimensional array of a specific
order. Say a 10 item list into a 3 column table:
________________________________________
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|___|___|___|___|___|___|___|___|___|____|
Into:
____________
| 1 | 5 | 9 |
|___|___|____|
| 2 | 6 | 10 |
|___|___|____|
| 3 | 7 | x |
|___|___|____|
| 4 | 8 | x |
|___|___|____|
5) This immediately suggests the next step: Further simplify the problem by
assuming that the number of columns is always a factor of the number of
items in the list, (that is a perfect 'fit') by imagining as many null
elements at the end of the list as you require, (Here I am using "x" to
indicate a null item.)
Now there is counter-intuitive element to this transformation, and it arises
from the fact that our approach to looping through the input list and
loading the output matrix is going to cause less brain strain if it conforms
to the left->right, top->bottom conventions of (western) writing.
So doing a nested loop sequence that looks like this (note the null
elements),
[ loop 1 ] [ loop 2 ] [ loop 3 ]
| | | | | | | | | | | |
V V V V V V V V V V V V
________________________________________________
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | x | x |
|___|___|___|___|___|___|___|___|___|____|___|___|
To load a matrix that looks like this,
________________
| 1 | 2 | 3 | 4 |
|___|____|___|___|
| 5 | 6 | 7 | 8 |
|___|____|___|___|
| 9 | 10 | x | x |
|___|____|___|___|
Feels a lot more 'natural' to our habitual thinking than the transformation
we actually want. So we will simply use that as the first step towards our
solution.
6) Look for the relationship between the two matrices;
________________ ____________
| 1 | 2 | 3 | 4 | | 1 | 5 | 9 |
|___|____|___|___| |___|___|____|
| 5 | 6 | 7 | 8 | and | 2 | 6 | 10 |
|___|____|___|___| |___|___|____|
| 9 | 10 | x | x | | 3 | 7 | x |
|___|____|___|___| |___|___|____|
| 4 | 8 | x |
|___|___|____|
Now its much easier:
Go down the columns in the first matrix from the left column to the right
column to get each value, and put those values in the second matrix by going
across the rows in the second matrix from the top row to the bottom row.
Both are familiar and reasonably intuitive ways of reading a matrix (table).
7) Now you have what you were really after - a re-ordered list, which back
in a one dimensional array will look like this:
________________________________________________
| 1 | 5 | 9 | 2 | 6 | 10 | 3 | 7 | x | 4 | 8 | x |
|___|___|___|___|___|____|___|___|___|___|___|___|
Given that AppleScript handles null strings ("") this will be easy to build.
8) Now that it is easy to 'see' what has to be done, we can quickly sketch
the nested iteration that will be needed to achieve the transformation. The
outside loop will repeat the same number of times as there are columns in
the (null padded) output matrix, and the inside loop will repeat the same
number of times as there are rows.....for the example we have been using;
* Outside loop (iteration 1)
[inside loops ]
| | | |
V V V V
________________________________________________
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | x | x |
|___|___|___|___|___|___|___|___|___|____|___|___|
| | | |_______________________
| | |_______________ |
| |_______ | |
| | | |
V V V V
________________________________________________
| 1 | 5 | 9 | 2 | 6 | 10 | 3 | 7 | x | 4 | 8 | x |
|___|___|___|___|___|____|___|___|___|___|___|___|
* Outside loop (iteration 2)
[inside loops ]
| | | |
V V V V
________________________________________________
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | x | x |
|___|___|___|___|___|___|___|___|___|____|___|___|
___________| | | |____________
| ___| | |
| | |____ |
| | | |
V V V V
________________________________________________
| 1 | 5 | 9 | 2 | 6 | 10 | 3 | 7 | x | 4 | 8 | x |
|___|___|___|___|___|____|___|___|___|___|___|___|
* Outside loop (iteration 3)
[inside loops ]
| | | |
V V V V
________________________________________________
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | x | x |
|___|___|___|___|___|___|___|___|___|____|___|___|
_______________________| | | |
| _______________| | |
| | _______| |
| | | |
V V V V
________________________________________________
| 1 | 5 | 9 | 2 | 6 | 10 | 3 | 7 | x | 4 | 8 | x |
|___|___|___|___|___|____|___|___|___|___|___|___|
9) Work out your coding needs.
You will need to work out the length the two lists need to be to reflect the
dimensions of the output matrix (table), and how many cells (items) at the
end of your input list must be padded with nulls.
So divide the number of cells by the given number of columns.
If the list fits perfectly the remainder will be zero. If not you will need
one extra row.
If you had to add an extra row then the number of empty cells will be the
number of columns minus the number of non-null items in the last row.
Apple Script's div and mod functions will provide the values you need.
Hoping the variable names are self explanatory, the following code will do
the trick...
Set leftOvers to listCount mod columnNum
If leftOvers = 0 then
Set rowNum to listCount div columnNum -- Perfect fit
Else
Set rowNum to (listCount div columnNum) + 1 -- add a row.
repeat (columnNum - leftOvers) times
Set inList to inList & "" -- pad the input list to make it
end repeat -- a perfect fit
End if
Now we are going to read from one list and write to another. So to keep this
easy to follow use to variables, one that will always point to the next cell
to be read and one that will always point to the next cell to be written to.
(say readPointer and writePointer).
Our transformation diagrams show that we will need to read and write in
groups of three (that is in groups of columns, or columnNum) of four each
(that is in groups of rows, or rowNum). So the outside loop will be a simple
'repeat columnNum times' and the inside loop will be a simple 'repeat rowNum
times'. The code outline is now obvious...
Repeat columnNum times
Repeat rowNum times
set item writePointer of outList to item readPointer of inList
End repeat
End repeat
(life is good...) So now we need to not worry about where we are writing and
just look at making sure that we are reading form the correct cell each
time. Well we always have to start at the first cell so we will initialise
that to 1 outside both loops. The we just read from write to left, so we
completely ignore the loops and notice that we just have to make sure the
read pointer is incremented by one after each read operation.
Set readPointer to 1
Repeat columnNum times
Repeat rowNum times
set item writePointer of outList to item readPointer of inList
set readPointer to readPointer + 1
End repeat
End repeat
Writing is a little tricker. But taking it one step at a time...
Like the read we will always start at the first cell.
Then we see that starting position for the rows moves forward only one cell
for each group (row) of read / write operations. So we make a variable
called rowStart, set it to 1 before we start and make sure that after each
row (inner loop) is done we increment it by 1
Set readPointer to 1
Set rowStart to 1
Repeat columnNum times
Repeat rowNum times
set item writePointer of outList to item readPointer of inList
set readPointer to readPointer + 1
End repeat
set rowStart to rowStart + 1
End repeat
Now we can see that each time we go into the inner loop rowStart is going to
point to the correct place for the first read, so we set the readPointer to
the value of rowStart just before we go in. (Not after!)
Set readPointer to 1
Set rowStart to 1
Repeat columnNum times
set writePointer to rowStart
Repeat rowNum times
set item writePointer of outList to item readPointer of inList
set readPointer to readPointer + 1
End repeat
set rowStart to rowStart + 1
End repeat
That means we are at the right position for the first write of every row
(inner loop). Now we just have to make sure that while we are writing that
group we move to the correct cell after each individual write. Looking at
the diagram we can see that the write position inside the inner (row) loops,
jumps forward the number of columns. So all we have to do is make sure that
after each write we increment the writePointer by that amount.
Set readPointer to 1
Set rowStart to 1
Repeat columnNum times
set writePointer to rowStart
Repeat rowNum times
set item writePointer of outList to item readPointer of inList
set readPointer to readPointer + 1
set writePointer to writePointer + columnNum
End repeat
set rowStart to rowStart + 1
End repeat
11) Now there is a little catch here but it is easy to overcome. The writing
is using list indexes to build the output list, which means whatever the
value of writePointer is, the position it is trying to reference in the list
has to already exist. So if you start with,
Set outList to {}
The code wall fall over. We need to add one line of code to our
initailisation routine above..
Set leftOvers to listCount mod columnNum
If leftOvers = 0 then
Set rowNum to listCount div columnNum -- Perfect fit
Else
Set rowNum to (listCount div columnNum) + 1 -- add a row.
repeat (columnNum - leftOvers) times
Set inList to inList & "" -- pad the input list to make it
end repeat -- a perfect fit
End if
copy inList to outList
It does not matter at all that outLIst will be initially filled with all the
items from inList because every one of them will be replaced by the
transformation (re-ordering).
11) That's your solution. Now you have to convert the transformed list into
the required string, but it should be pretty simple. You can fold that into
the transformation code. That is a matter of philosophy and aesthetics and
performance requirements Unless forced to seriously optimise, I will always
break the process up so that functionality is a separate as possible and the
code is as easy as possible to understand when I come back to it later.
Hope someone stayed with this to the end, and if you did I hope you enjoyed
it.
Ric Phillips
Computer Laboratory Support Officer
Faculty of Humanities and Social Sciences
La Trobe University
9479 2792