tab
Table utilities.
sections
control
Syntax | Description |
---|---|
tab.print(t) | Prints the contents of table t . |
tab.sort(t) | Return a lexigraphically-sorted array of keys for a table. |
tab.count(t) | Count the number of entries in a table; unlike table.getn() or #table , nil entries won’t break the loop. |
tab.contains (t, e) | Search table t for element e . Returns boolean. |
tab.invert(t) | Given a simple table of primitives t , “invert” it so that values become keys and vice versa. Returns inverted table. |
tab.key(t, e) | Search table t for element e , return its key. |
tab.lines(str) | Split multi-line string str (with line breaks) into table of strings. Returns table of strings. |
tab.save(t, filename) | Save table t to disk as filename . |
tab.load(saved_file) | Load tab.save()’d table from file saved_file . |
tab.readonly{params} | Create a read-only proxy for a given table, following the provided params . Notice the use of curly braces here!* params.table is the table to proxy* params.except is a list of writable keys* params.expose limits which keys from params.table are exposedReturns read-only table. |
tab.gather(default_values, custom_values) | Returns a new table, gathering values first from default_values , then from custom_values (nils are ignored). |
tab.update(table_to_mutate, updated_values) | Mutate table table_to_mutate , updating values from another table updated_values . |
tab.select_values (t, condition) | Create a new table with all values from table t that pass the test implemented by the provided function condition . |
description
Tables are data structures which contain arrays, dictionaries, collections of symbols, etc. If you’re familiar with other programming languages, Lua treats tables like associative arrays and they’re the only “container” in the language. See this section of Programming In Lua for more information.
simple table query and manipulation
In this example, we’ll demonstrate how to use this library’s basic functions to query tables in Lua, as well as how to use actions like sort
and invert
.
-- tabutil example: simple table query and manipulation
values = {
400,
391,
202,
12039,
}
mana = {
white = {"order", "peace", "light"},
blue = {"intellect", "logic", "manipulation"},
black = {"power", "death", "corruption"},
red = {"freedom", "chaos", "fury"},
green = {"life", "nature", "evolution"}
}
steps = {
[1] = {midi_notes = {42,49,35}},
[4] = {midi_notes = {37,30}},
[9] = {midi_notes = {48,41,53}}
}
simple = {
"apple",
"banana",
"cherry"
}
multiline_string = [[hello!!
this is a big old chunk of text.
it's something which i'd like to break up
into a few table entries.]]
function init()
print(" ")
print("each entry in table 'values':")
tab.print(values)
print(" ")
print('alpha-sorted mana:')
local sorted_mana = tab.sort(mana)
tab.print(sorted_mana)
print(" ")
print("~~~ how many 'steps'? ~~~")
print("tab.count counts all non-nil entries: " .. tab.count(steps))
print("#steps doesn't breaks with nil entries: " .. #steps)
print(" ")
print("is intellect associated with blue mana?")
print(tab.contains(mana.blue, 'intellect'))
print("is 331 in the 'values' table?")
print(tab.contains(values, 331))
print("does step 9's midi notes contain 53?")
print(tab.contains(steps[9].midi_notes, 53))
print(" ")
print("which position is banana in?")
local inverted_simple = tab.invert(simple)
print(inverted_simple.banana)
print("...testing another way to find banana's position...")
print("banana is in position: "..tab.key(simple, 'banana'))
print(" ")
print("breaking up the big multiline_string...")
local linebroken = tab.lines(multiline_string)
for i = 1, tab.count(linebroken) do
print("line "..i..": "..linebroken[i])
end
end
table save and load
In this example, we’ll demonstrate how to save a table to the norns disk (under dust/data/<scriptname>/test-table.txt
) and load it again in future sessions.
-- tabutil example: table save and load
function init()
random_file_exists = util.file_exists(norns.state.data.."test-table.txt")
random_values = {
math.random(100),
math.random(100),
math.random(100)
}
end
function key(n,z)
if n == 3 and z == 1 then
local filename = norns.state.data.."test-table.txt"
tab.save(random_values, filename)
print("saved some random values to "..filename)
random_file_exists = true
elseif random_file_exists and n == 2 and z == 1 then
local filename = norns.state.data.."test-table.txt"
random_values = tab.load(filename)
end
redraw()
end
function enc(n,d)
random_values[n] = util.clamp(random_values[n] + d, 0, 100)
redraw()
end
function redraw()
screen.clear()
for i = 1,#random_values do
screen.move(0,10*i)
screen.text("value "..i..": "..random_values[i])
end
screen.move(120,50)
screen.text_right(random_file_exists and "K2: load saved values" or "")
screen.move(120,60)
screen.text_right("K3: save current values")
screen.update()
end
advanced table manipulation
Often, newcomers to Lua will be surprised by the results of this code:
notes = {30,50,72}
function octave_up()
local new_notes = notes
for i = 1,3 do
new_notes[i] = new_notes[i]+12
end
print("new notes:")
tab.print(new_notes)
print("old notes:")
tab.print(notes)
end
function init()
print("starting notes:")
tab.print(notes)
clock.run(
function()
clock.sleep(1)
octave_up()
end
)
end
A common assumption is that new_notes = notes
goes through and assigns all the values of notes
to new_notes
, resulting in two unique tables. However, upon running the script, we see:
# script init
starting notes:
1 30
2 50
3 72
new notes:
1 42
2 62
3 84
old notes:
1 42
2 62
3 84
# these ID's are unique each run, but they're always the same as each other:
table: 0x4aced0 table: 0x4aced0
This is because new_notes = notes
simply aliases or links notes
with new_notes
– they refer to the same piece of data, as we can see with print(notes, new_notes)
at the end of octave_up()
. So even though new_notes
is local to our octave_up
function, any change to it will also propagate to notes
.
In order to make an independent copy of a table, we can use tab.select_values
, which allows us to specify a table from which to conditionally select values for a new table to be made:
notes = {30,50,72}
function octave_up()
local new_notes = tab.select_values(notes,function() return true end)
for i = 1,3 do
new_notes[i] = new_notes[i]+12
end
print("new notes:")
tab.print(new_notes)
print("old notes:")
tab.print(notes)
print(notes, new_notes)
end
function init()
print("starting notes:")
tab.print(notes)
clock.run(
function()
clock.sleep(1)
octave_up()
end
)
end
Upon running the script, we see:
# script init
starting notes:
1 30
2 50
3 72
new notes:
1 42
2 62
3 84
old notes:
1 30
2 50
3 72
table: 0x5ae3d8 table: 0x448998
This even works for nested tables, by re-building the top-most part of the data structure:
mana = {
white = {"order", "peace", "light"},
blue = {"intellect", "logic", "manipulation"},
black = {"power", "death", "corruption"},
red = {"freedom", "chaos", "fury"},
green = {"life", "nature", "evolution"}
}
function init()
new_mana = tab.select_values(mana, function() return true end)
print(mana, new_mana, mana == new_mana and "| tables are the same :(" or "| tables are different!")
new_mana.green = {"death", "factory", "stagnation"}
print("old green mana")
tab.print(mana.green)
print("new green mana")
tab.print(new_mana.green)
end
We can also use tab.select_values
to selectively gather values which meet certain conditions without having to worry about building complex functions:
sequence = {60,42,54,55,57,39,70}
function init()
hi_notes = tab.select_values(sequence, function(value,key) return value >= 55 end)
lo_notes = tab.select_values(sequence, function(value,key) return value < 55 end)
print('high notes')
tab.print(hi_notes)
print('low notes')
tab.print(lo_notes)
end
gather and update
The tab
library also provides two functions for merging and weaving tables together.
gather
If tables t1
and t2
share keys / indices, tab.gather(t1,t2)
will use t2
to overwrite as much of t1
as it can. For example, this code:
t1 = {60, 55, 57, 70, 59}
t2 = {39, 42, 54}
function init()
local t3 = tab.gather(t1,t2)
tab.print(t3)
end
Will result in:
1 39
2 42
3 54
4 70
5 59
Any time t2
’s indices cross over with t1
, the value in t1
will be replaced with the value from t2
. Note that the resulting table is always as many entries deep as the first table provided.
If tables t1
and t2
do not share indices, the resulting table changes in response. As gather
iterates through each of t1
’s indices, it checks to see if t2
holds a corresponding entry at that index – if yes then it will choose t2
’s value for the new table, otherwise it will migrate t1
’s value:
t1 = {
[1] = 60,
[5] = 55,
[7] = 57,
[10] = 70,
[15] = 59
}
t2 = {
[5] = 39,
[6] = 42,
[8] = 16,
[10] = 54,
[12] = 60
}
function init()
t3 = tab.gather(t1,t2)
end
Will result in table t3
with these indices and values:
t3[1] = 60 # from t1
t3[5] = 39 # from t2
t3[7] = 57 # from t1
t3[10] = 54 # from t2
t3[15] = 59 # from t1
update
Rather than using t1
’s indices to generate a new table, update
will include all index/value pairs from t2
as well as overwrite any values for indices shared with t1
. Unlike tab.gather
, tab.update
can result in tables with more entries than the first table provides.
For example:
t1 = {
[1] = 60,
[5] = 55,
[7] = 57,
[10] = 70,
[15] = 59
}
t2 = {
[5] = 39,
[6] = 42,
[8] = 16,
[10] = 54,
[12] = 60
}
function init()
t3 = tab.update(t1,t2)
end
Will result in table t3
with these indices and values:
t3[1] = 60 # from t1
t3[5] = 39 # from t2: value overwrite
t3[6] = 42 # from t2: new index and value added
t3[7] = 57 # from t1
t3[8] = 16 # from t2: new index
t3[10] = 54 # from t2: value overwrite
t3[12] = 60 # from t2: new index and value added
t3[15] = 59 # from t1