How do I Display an Array in Human Readable Form?
This lesson describes how to implement a short recursive algorithm to display arbitrarily nested arrays that contain printable data. Sample code is provided.
Introduction
It is often convenient to represent complex data sets as nested array structures. This allows information to be grouped in a logical form that is easily accessible from within a LiveCode application. As the data of such an array structure grows, it can quickly become difficult for a human reader to interpret the data and determine the array structure. Fortunately, it is very easy to write a generic function that decomposes complex array structures into an easily readable form.
Creating the Array Data
The structure and depth of the array can be chosen freely to meet the requirements of the data that is to be stored.
In the following example, the data represents an arrangement that may be present in an application that is used to process purchases of online book orders.
The data stored contains: The first name, the last name, the country in which the customer lives, the books a customer purchased, the currency in which the books were paid for and the payments that were made for each book. In this example, the book seller allows for books to be paid for in instalments.
# declare the variable that stores the array
local tCustomerDataArray
# populate the customer names
put "John" into tCustomerDataArray["1"]["address"]["first_name"]
put "Smith" into tCustomerDataArray["1"]["address"]["last_name"]
put "Peter" into tCustomerDataArray["2"]["address"]["first_name"]
put "Brown" into tCustomerDataArray["2"]["address"]["last_name"]
put "Martin" into tCustomerDataArray["3"]["address"]["first_name"]
put "Adamson" into tCustomerDataArray["3"]["address"]["last_name"]
put "Julia" into tCustomerDataArray["4"]["address"]["first_name"]
put "King" into tCustomerDataArray["4"]["address"]["last_name"]
# populate the customer country details
put "Scotland" into tCustomerDataArray["1"]["address"]["country"]
put "Germany" into tCustomerDataArray["2"]["address"]["country"]
put "England" into tCustomerDataArray["3"]["address"]["country"]
put "Sweden" into tCustomerDataArray["4"]["address"]["country"]
# populate the customer order details
put "Walking in Scotland" into tCustomerDataArray["1"]["orders"]["1"]["book"]
put "The Great Outdoors" into tCustomerDataArray["1"]["orders"]["2"]["book"]
put "Kilts and Stuff" into tCustomerDataArray["1"]["orders"]["3"]["book"]
put "1001 Jokes" into tCustomerDataArray["2"]["orders"]["1"]["book"]
put "Makeup and Fashion" into tCustomerDataArray["3"]["orders"]["1"]["book"]
put "Meat Ball Recipes" into tCustomerDataArray["4"]["orders"]["1"]["book"]
put "Ice Hotels DIY" into tCustomerDataArray["4"]["orders"]["2"]["book"]
# populate the customer payments
put "pounds" into tCustomerDataArray["1"]["orders"]["1"]["currency"]
put "10.00" into tCustomerDataArray["1"]["orders"]["1"]["payments"]["1"]
put "pounds" into tCustomerDataArray["1"]["orders"]["2"]["currency"]
put "15.00" into tCustomerDataArray["1"]["orders"]["2"]["payments"]["1"]
put "pounds" into tCustomerDataArray["1"]["orders"]["3"]["currency"]
put "7.00" into tCustomerDataArray["1"]["orders"]["3"]["payments"]["1"]
put "7.00" into tCustomerDataArray["1"]["orders"]["3"]["payments"]["2"]
put "euro" into tCustomerDataArray["2"]["orders"]["1"]["currency"]
put "5.00" into tCustomerDataArray["2"]["orders"]["1"]["payments"]["1"]
put "5.00" into tCustomerDataArray["2"]["orders"]["1"]["payments"]["2"]
put "pounds" into tCustomerDataArray["3"]["orders"]["1"]["currency"]
put "20.00" into tCustomerDataArray["3"]["orders"]["1"]["payments"]["1"]
put "krona" into tCustomerDataArray["4"]["orders"]["1"]["currency"]
put "12.00" into tCustomerDataArray["4"]["orders"]["1"]["payments"]["1"]
put "krona" into tCustomerDataArray["4"]["orders"]["2"]["currency"]
put "17.00" into tCustomerDataArray["4"]["orders"]["2"]["payments"]["1"]
This array represents the information of four customers and their purchases.
Decomposing the Array Data
There are potentially many ways of decomposing array data into a human readable form, and the representation chosen depends on personal preference and the data structure that is to be displayed.
The code in this lesson is generic and can be used on a large range of arrays. Data is processed in two ways:
1. Indent the data from left to right, depending on the depth at which the data is stored in the array.
2. Group information together depending on how close it lies together in the array structure.
function displayArrayData pArray, pIndent
# create the variable that loops through the keys in the array
local tKey
if pArray is an array then
# print information to indicate that we are entering a new nested level of the array
get "Array" & return
# print full stops that allow the reader to track the depth of an element
put "." after pIndent
# create the indentation
put tab after pIndent
repeat for each key tKey in pArray
# call displayArrayData with a nested array
put format("%s[%s] => %s\n", pIndent, tKey, displayArrayData (pArray[tKey], pIndent)) after it
end repeat
delete the last char of it
return it
else
return pArray
end if
end displayArrayData
The level of indentation and its formatting is explicitly controlled by output lines in the function. Information grouping is implicitly generated as a result of depth-first search exploration of the array data structure.
Executing the Function and Displaying the Data
To execute the function and display the result in the Message Box you can execute
put displayArrayData(tCustomerDataArray,".")
To execute the function from a button and display the result in a field you can call the function from a mouseUp handler.
on mouseUp
// Construct the array data in tCustomerDataArray
put displayArrayData(tCustomerDataArray,".") into field "readableDisplay"
end mouseUp
The Formatted Output
The output from displayArrayData is written to the LiveCode Message Box and is structured as follows:
Array
. [1] => Array
. . [address] => Array
. . . [first_name] => John
. . . [last_name] => Smith
. . . [country] => Scotland
. . [orders] => Array
. . . [1] => Array
. . . . [payments] => Array
. . . . . [1] => 10.00
. . . . [book] => Walking in Scotland
. . . . [currency] => pounds
. . . [2] => Array
. . . . [payments] => Array
. . . . . [1] => 15.00
. . . . [book] => The Great Outdoors
. . . . [currency] => pounds
. . . [3] => Array
. . . . [payments] => Array
. . . . . [1] => 7.00
. . . . . [2] => 7.00
. . . . [book] => Kilts and Stuff
. . . . [currency] => pounds
. [2] => Array
. . [address] => Array
. . . [first_name] => Peter
. . . [last_name] => Brown
. . . [country] => Germany
. . [orders] => Array
. . . [1] => Array
. . . . [payments] => Array
. . . . . [1] => 5.00
. . . . . [2] => 5.00
. . . . [book] => 1001 Jokes
. . . . [currency] => euro
. [3] => Array
. . [address] => Array
. . . [first_name] => Martin
. . . [last_name] => Adamson
. . . [country] => England
. . [orders] => Array
. . . [1] => Array
. . . . [payments] => Array
. . . . . [1] => 20.00
. . . . [book] => Makeup and Fashion
. . . . [currency] => pounds
. [4] => Array
. . [address] => Array
. . . [first_name] => Julia
. . . [last_name] => King
. . . [country] => Sweden
. . [orders] => Array
. . . [1] => Array
. . . . [payments] => Array
. . . . . [1] => 12.00
. . . . [book] => Meat Ball Recipes
. . . . [currency] => krona
. . . [2] => Array
. . . . [payments] => Array
. . . . . [1] => 17.00
. . . . [book] => Ice Hotels DIY
. . . . [currency] => krona
The data is logically grouped, and the data depth is indicated by the number of full stops in front of each entry. For example, address data contains the first_name, the last_name and the country.
Note: If you do not know how the grouping was created or mapped to the array, please refer to the source array structure and compare it to the output that is shown here.
Dennis Wurster
Can you provide the syntax of your function call?
In the case of the article, I'm expecting it to be something like
displayArrayData tCustomerDataArray, "."
and I'd expect the full line of code to be something like
put displayArrayData tCustomerDataArray, "." into field "results"
Elanor Buchanan
Hi Dennis
You call a function in LiveCode like this:
displayArrayData(tCustomerArrayData,".")
the full line of code would be
put displayArrayData(tCustomerArrayData,".") into field "results"
In this case you don't actually need to pass pIndent when you first call the function, pIndent is used when the function is called recursively but can be left empty in the initial call.
I hope that helps.
Elanor
Rachel
Hi Elanor,
This lesson needs a little TLC on your end. It gave me quite a bit of trouble before I finally realized what was happening.
As a beginner to LC, I didn't understand that in order to call the code from a button or message box, I would have to use the "put" command in front of the function call. I also attempted to use your function call in the code. It took me quite a while to notice that your call was actually different from the code provided. I believe the correct function call should be as follows:
put displayArrayData (tCustomerDataArray), "." into field "results"
I know that you are very busy, but I wonder if other beginners might benefit from a little more code in the example. Then, when they run it from an object, they wouldn't have to troubleshoot quite as much.
I'm enjoying LiveCode. Thank you for all of your hard work.
-Rachel
Elanor Buchanan
Dear Rachel,
Thank you for your kind and helpful comments. I have added a section that explains more about how to execute the code, and I have also added an example stack that can be downloaded and run. I hope that will make things a bit clearer and easier for less experienced LiveCode users.
I'm glad you hear you are enjoying LiveCode!
Kind regards
Elanor
Gil
hello Elanor,
Thank you for this example.
However, the order in the output is not chronological.
For example, instead of 1,2,3, you see 1,3,2
How can I solve this?
Thank you and best regards
Gil
Elanor Buchanan
Hi Gil
The reason for this is that the keys of an array are not returned ordered. If the order is important you can get the keys, sort them then loop across the ordered keys.
Something like this
...
put the keys of pArray into tKeys
if line 1 of tKeys is a number then
sort lines of tKeys ascending numeric
else
sort lines of tKeys ascending
end if
repeat for each line tKey in tKeys
# call displayArrayData with a nested array
put format("%s[%s] => %s\n", pIndent, tKey, displayArrayData (pArray[tKey], pIndent)) after it
end repeat
...
I hope that helps.
Elanor
Gil
hi Elanor,
thanks a lot for your quick help!
It helped me alot!
Thank you and best regards
Gil
Torsten
Can somebody explain how this line works?
put format("%s[%s] => %s\n", pIndent, tKey, displayArrayData (pArray[tKey], pIndent)) after it
How are the different variables work together and what is "%s[%s] => %s\n" doing?
Thanks a lot!
Torsten
Elanor Buchanan
Hi Torsten
The base string is "%s[%s] => %s\n". This contains three special formatting incantations, in this case 3 occurrences of "%s". When the format command is called each of these incantations is replaced by the corresponding variable in the values list. The "%s" incantation will leave the corresponding value unchanged.
- The first "%s" is replaced by the value of pIndent
- The second "%s" is replace by the value of tKey
- The third "%s" is replaced by the value returned by the recursive call to displayArrayData()
- The other characters "[, ], =, >" are unchanged
- \n becomes a return character
Example:
- pIndent = ".. . . . "
- tKey = "currency"
- displayArrayData (pArray["currency"], ".. . . . ") = "pounds"
Would end up being
.. . . . [currency] => pound
You can find more details in the format entry in the Dictionary.
I hope that helps.
Kind regards
Elanor
Roberto
I could I have a return similar to the one shown on the LC script editor?
That is, all the nested keys completely displayed and the values aligned?
Elanor Buchanan
Hi Roberto
The LC Script Editor uses a Tree View Widget to display the contents of arrays so you should be able to use that in your stack.
I hope that helps.
Elanor
Roberto
Sorry: what I mean is to create a text file of the global nested array vars, formatted as in the script editor
Elanor Buchanan
Hi Roberto
You can tweak the displayArrayData function if the display is not exactly what you want. All the keys are values should already be returned by the function and it uses tabs and full stops to separate them. You can then write the result of the function out to file.
The Script Editor uses a widget to display the array contents rather than displaying it as text so you will need to experiment to get a similar display you are happy with.
If you are just looking for a readable form on an array you could also try ArrayToJSON with the pPretty parameter set to true. This gives a nicely laid out text representation.
I hope that helps. My apologies if I have not quite understood what you are looking for.
Elanor