--------------------------------------------------------------------------------
--- Small table utilities
-- @module lua-nucleo.table-utils
-- This file is a part of lua-nucleo library
-- @copyright lua-nucleo authors (see file `COPYRIGHT` for the license)
--------------------------------------------------------------------------------
local setmetatable, error, pairs, ipairs, tostring, select, type, assert
= setmetatable, error, pairs, ipairs, tostring, select, type, assert
local rawget = rawget
local table_insert, table_remove = table.insert, table.remove
local math_min, math_max = math.min, math.max
--------------------------------------------------------------------------------
local arguments,
optional_arguments,
method_arguments
= import 'lua-nucleo/args.lua'
{
'arguments',
'optional_arguments',
'method_arguments'
}
local is_number,
is_table
= import 'lua-nucleo/type.lua'
{
'is_number',
'is_table'
}
local assert_is_table
= import 'lua-nucleo/typeassert.lua'
{
'assert_is_table'
}
--------------------------------------------------------------------------------
-- Warning: it is possible to corrupt this with rawset and debug.setmetatable.
local empty_table = setmetatable(
{ },
{
__newindex = function(t, k, v)
error("attempted to change the empty table", 2)
end;
__metatable = "empty_table";
}
)
local function toverride_many(t, s, ...)
if s then
for k, v in pairs(s) do
t[k] = v
end
-- Recursion is usually faster than calling select()
return toverride_many(t, ...)
end
return t
end
local function tappend_many(t, s, ...)
if s then
for k, v in pairs(s) do
if t[k] == nil then
t[k] = v
else
error("attempted to override table key `" .. tostring(k) .. "'", 2)
end
end
-- Recursion is usually faster than calling select()
return tappend_many(t, ...)
end
return t
end
local function tijoin_many(t, s, ...)
if s then
-- Note: can't use ipairs() since we want to support tijoin_many(t, t)
for i = 1, #s do
t[#t + 1] = s[i]
end
-- Recursion is usually faster than calling select()
return tijoin_many(t, ...)
end
return t
end
-- Keys are ordered in undetermined order
local tkeys = function(t)
local r = { }
for k, v in pairs(t) do
r[#r + 1] = k
end
return r
end
-- Values are ordered in undetermined order
local tvalues = function(t)
local r = { }
for k, v in pairs(t) do
r[#r + 1] = v
end
return r
end
-- Keys and values are ordered in undetermined order
local tkeysvalues = function(t)
local keys, values = { }, { }
for k, v in pairs(t) do
keys[#keys + 1] = k
values[#values + 1] = v
end
return keys, values
end
-- If table contains multiple keys with the same value,
-- only one key is stored in the result, picked in undetermined way.
local tflip = function(t)
local r = { }
for k, v in pairs(t) do
r[v] = k
end
return r
end
-- If table contains multiple keys with the same value,
-- only one key is stored in the result, picked in undetermined way.
local tflip_inplace = function(t)
for k, v in pairs(t) do
t[v] = k
end
return t
end
-- If table contains multiple keys with the same value,
-- only the last such key (highest one) is stored in the result.
local tiflip = function(t)
local r = { }
for i = 1, #t do
r[t[i]] = i
end
return r
end
local tset = function(t)
local r = { }
for k, v in pairs(t) do
r[v] = true
end
return r
end
local tiset = function(t)
local r = { }
for i = 1, #t do
r[t[i]] = true
end
return r
end
local function tiinsert_args(t, a, ...)
if a ~= nil then
t[#t + 1] = a
-- Recursion is usually faster than calling select() in a loop.
return tiinsert_args(t, ...)
end
return t
end
local timap_inplace = function(fn, t, ...)
for i = 1, #t do
t[i] = fn(t[i], ...)
end
return t
end
local timap = function(fn, t, ...)
local r = { }
for i = 1, #t do
r[i] = fn(t[i], ...)
end
return r
end
local timap_sliding = function(fn, t, ...)
local r = {}
for i = 1, #t do
tiinsert_args(r, fn(t[i], ...))
end
return r
end
local tiwalk = function(fn, t, ...)
for i = 1, #t do
fn(t[i], ...)
end
end
local tiwalker = function(fn)
return function(t)
for i = 1, #t do
fn(t[i])
end
end
end
local twalk_pairs = function(fn, t)
for k, v in pairs(t) do
fn(k, v)
end
end
local tequals = function(lhs, rhs)
for k, v in pairs(lhs) do
if v ~= rhs[k] then
return false
end
end
for k, v in pairs(rhs) do
if lhs[k] == nil then
return false
end
end
return true
end
local tiunique = function(t)
return tkeys(tiflip(t))
end
-- Deprecated, use tgenerate_1d_linear instead
local tgenerate_n = function(n, generator, ...)
local r = { }
for i = 1, n do
r[i] = generator(...)
end
return r
end
local tgenerate_1d_linear = function(n, fn, ...)
local r = { }
for i = 1, n do
r[#r + 1] = fn(i, ...)
end
return r
end
local tgenerate_2d_linear = function(w, h, fn, ...)
local r = { }
for y = 1, h do
for x = 1, w do
r[#r + 1] = fn(x, y, ...)
end
end
return r
end
local taccumulate = function(t, init)
local sum = init or 0
for k, v in pairs(t) do
sum = sum + v
end
return sum
end
local tnormalize, tnormalize_inplace
do
local impl = function(t, r, sum)
sum = sum or taccumulate(t)
for k, v in pairs(t) do
r[k] = v / sum
end
return r
end
tnormalize = function(t, sum)
return impl(t, { }, sum)
end
tnormalize_inplace = function(t, sum)
return impl(t, t, sum)
end
end
local tclone
do
local function impl(t, visited)
local t_type = type(t)
if t_type ~= "table" then
return t
end
assert(not visited[t], "recursion detected")
visited[t] = true
local r = { }
for k, v in pairs(t) do
r[impl(k, visited)] = impl(v, visited)
end
visited[t] = nil
return r
end
tclone = function(t)
return impl(t, { })
end
end
-- Slow
local tcount_elements = function(t)
local n = 0
for _ in pairs(t) do
n = n + 1
end
return n
end
local tremap_to_array = function(fn, t)
local r = { }
for k, v in pairs(t) do
r[#r + 1] = fn(k, v)
end
return r
end
local tmap_values = function(fn, t, ...)
local r = { }
for k, v in pairs(t) do
r[k] = fn(v, ...)
end
return r
end
--------------------------------------------------------------------------------
local torderedset = function(t)
local r = { }
for i = 1, #t do
local v = t[i]
-- Have to add this limitation to avoid size ambiquity.
-- If you need ordered set of numbers, use separate storage
-- for set and array parts (write make_ordered_set then).
assert(type(v) ~= "number", "can't insert number into ordered set")
r[v] = i
r[i] = v
end
return r
end
-- Returns false if item already exists
-- Returns true otherwise
local torderedset_insert = function(t, v)
-- See torderedset() for motivation
assert(type(v) ~= "number", "can't insert number into ordered set")
if not t[v] then
local i = #t + 1
t[v] = i
t[i] = v
return true
end
return false
end
-- Returns false if item didn't existed
-- Returns true otherwise
-- Note this operation is really slow
local torderedset_remove = function(t, v)
-- See torderedset() for motivation
assert(type(v) ~= "number", "can't remove number from ordered set")
local pos = t[v]
if pos then
t[v] = nil
-- TODO: Do table.remove manually then to do all in a single loop.
table_remove(t, pos)
for i = pos, #t do
t[t[i]] = i -- Update changed numbers
end
end
return false
end
--------------------------------------------------------------------------------
-- Handles subtables (is "deep").
-- Does not support recursive defaults tables
-- WARNING: Uses tclone()! Do not use on tables with metatables!
local twithdefaults
do
twithdefaults = function(t, defaults)
for k, d in pairs(defaults) do
local v = t[k]
if v == nil then
if type(d) == "table" then
d = tclone(d)
end
t[k] = d
elseif type(v) == "table" and type(d) == "table" then
twithdefaults(v, d)
end
end
return t
end
end
--------------------------------------------------------------------------------
local tifilter = function(pred, t, ...)
local r = { }
for i = 1, #t do
local v = t[i]
if pred(v, ...) then
r[#r + 1] = v
end
end
return r
end
--------------------------------------------------------------------------------
local tsetof = function(value, t)
local r = { }
for k, v in pairs(t) do
r[v] = value
end
return r
end
--------------------------------------------------------------------------------
local tset_many = function(...)
local r = { }
for i = 1, select("#", ...) do
for k, v in pairs((select(i, ...))) do
r[v] = true
end
end
return r
end
-- TODO: Pick a better name?
local tidentityset = function(t)
local r = { }
for k, v in pairs(t) do
r[v] = v
end
return r
end
--------------------------------------------------------------------------------
local timapofrecords = function(t, key)
local r = { }
for i = 1, #t do
local v = t[i]
r[assert(v[key], "missing record key field")] = v
end
return r
end
local tivalues = function(t)
local r = { }
for i = 1, #t do
r[#r + 1] = t[i]
end
return r
end
--------------------------------------------------------------------------------
-- NOTE: Optimized to be fast at simple value indexing.
-- Slower on initialization and on table value fetching.
-- WARNING: This does not protect userdata.
local treadonly, treadonly_ex
do
local newindex = function()
error("attempted to change read-only table")
end
treadonly = function(value, callbacks, tostring_fn, disable_nil)
callbacks = callbacks or empty_table
if disable_nil == nil then
disable_nil = true
end
arguments(
"table", value,
"table", callbacks
)
optional_arguments(
"function", tostring_fn,
"boolean", disable_nil -- TODO: ?! Not exactly optional
)
local mt =
{
__metatable = "treadonly"; -- protect metatable
__index = function(t, k)
local v = rawget(value, k)
if is_table(v) then
-- TODO: Optimize
v = treadonly(v, callbacks, tostring_fn, disable_nil)
end
if v == nil then -- TODO: Try to use metatables
-- Note: __index does not support multiple return values in 5.1,
-- so we can not do call right here.
local fn = callbacks[k]
if fn then
return function(...) return fn(value, ...) end
end
if disable_nil then
error(
"attempted to read inexistant value at key " .. tostring(k),
2
)
end
end
return v
end;
__newindex = newindex;
}
if tostring_fn then
mt.__tostring = function() return tostring_fn(value) end
end
return setmetatable({ }, mt)
end
-- Changes to second return value are guaranteed to affect first one
treadonly_ex = function(value, ...)
local protected = treadonly(value, ...)
return protected, value
end
end
local tmap_kv = function(fn, t)
local r = { }
for k, v in pairs(t) do
k, v = fn(k, v)
r[k] = v
end
return r
end
local tmapofrecordgroups = function(t, key_name)
local r = { }
for k, v in pairs(t) do
local v = t[k]
local key = assert(v[key_name], "missing required key")
local g = r[key]
if not g then
g = { }
r[key] = g
end
g[#g + 1] = v
end
return r
end
local timapofrecordgroups = function(t, key_name)
local r = { }
for i = 1, #t do
local v = t[i]
local key = assert(v[key_name], "missing required key")
local g = r[key]
if not g then
g = { }
r[key] = g
end
g[#g + 1] = v
end
return r
end
local tilistofrecordfields = function(t, k)
local r = { }
for i = 1, #t do
local v = t[i][k]
assert(v ~= nil, "missing required key")
r[#r + 1] = v
end
return r
end
local tipermute_inplace = function(t, n, count, random)
n = n or #t
count = count or n
random = random or math.random
for i = 1, count do
local j = random(i, n)
t[i], t[j] = t[j], t[i]
end
return t
end
local tkvtorecordlist = function(t, key_name, value_name)
local result = { }
for k, v in pairs(t) do
result[#result + 1] = { [key_name] = k, [value_name] = v }
end
return result
end
local function tgetpath(t, k, nextk, ...)
if k == nil then
return nil
end
local v = t[k]
if not is_table(v) or nextk == nil then
return v
end
return tgetpath(v, nextk, ...)
end
-- tsetpath(tabl, "a", "b", "c", d)
-- tabl.a.b.c[d] = val
local tsetpath
do
local function impl(nargs, dest, key, ...)
if nargs == 0 then
return dest
end
if key == nil then
error("tsetpath: nil can't be a table key")
end
dest[key] = assert_is_table(
dest[key] or { },
"key `" .. tostring(key)
.. "' already exists and its value is not a table"
)
return impl(nargs - 1, dest[key], ...)
end
tsetpath = function(dest, ...)
local nargs = select("#", ...)
if nargs == 0 then
return dest
end
return impl(nargs, dest, ...)
end
end
local tsetpathvalue
do
local function impl(nargs, value, dest, key, ...)
assert(nargs > 0)
if key == nil then
error("tsetpathvalue: nil can't be a table key")
end
if nargs == 1 then
dest[key] = value
return dest
end
dest[key] = assert_is_table(
dest[key] or { },
"key `" .. tostring(key)
.. "' already exists and its value is not a table"
)
return impl(nargs - 1, value, dest[key], ...)
end
tsetpathvalue = function(value, dest, ...)
local nargs = select("#", ...)
if nargs == 0 then
return dest
end
return impl(nargs, value, dest, ...)
end
end
-- TODO: rename to tislice
local tslice = function(t, start_i, end_i)
local r = { }
start_i = math_max(start_i, 1)
end_i = math_min(end_i, #t)
for i = start_i, end_i do
r[i - start_i + 1] = t[i]
end
return r
end
local tarraylisttohashlist = function(t, ...)
local r = { }
local nargs = select("#", ...)
for i = 1, #t do
local item = { }
for j = 1, nargs do
local hash = select(j, ...)
if hash ~= nil then -- ignore nil from arguments
item[hash] = t[i][j]
end
end
r[#r + 1] = item
end
return r
end
local tarraytohash = function(t, ...)
local r = { }
local nargs = select("#", ...)
for i = 1, nargs do
local hash = select(i, ...)
if hash ~= nil then -- ignore nil from arguments
r[hash] = t[i]
end
end
return r
end
local tisempty = function(t)
return next(t) == nil
end
local tifindvalue_nonrecursive = function(t, v)
for i = 1, #t do
if t[i] == v then
return true
end
end
return false
end
local tkvlist2kvpairs = function(t)
local r = { }
for i = 1, #t, 2 do
local k, v = t[i], t[i+1]
if k ~= nil then
r[k] = v
end
end
return r
end
local tfilterkeylist = function(t, f, strict)
strict = strict or false
local r = { }
for i = 1, #f do
local k = f[i]
if t[k] ~= nil then
r[k] = t[k]
elseif strict then
return nil, "Field `" .. tostring(k) .. "' is absent"
end
end
return r
end
local tkvmap_unpack = function(fn, t, ...)
local r = { }
for k, v in pairs(t) do
k, v = fn(k, ...), fn(v, ...)
if k ~= nil and v ~= nil then
r[#r + 1] = k
r[#r + 1] = v
end
end
return unpack(r)
end
local tkvlist_to_hash = function(t)
local r = { }
for i = 1, #t, 2 do
r[t[i]] = t[i + 1]
end
return r
end
local tmerge_many = function(...)
return toverride_many({ }, ...)
end
-- Returns true is a table is an array
-- Returns false otherwise
-- Note the empty table is treated as an array
local tisarray = function(t)
for k, _ in pairs(t) do
if
-- Array keys should be numbers...
not is_number(k)
-- ...greater than 1...
or k < 1
-- ...in a continuous sequence...
or (k > 1 and t[k - 1] == nil)
-- ...of integers...
or k % 1 ~= 0
-- ...avoiding floating point overflow
or k == k - 1
then
return false
end
end
return true
end
--------------------------------------------------------------------------------
return
{
empty_table = empty_table;
toverride_many = toverride_many;
tappend_many = tappend_many;
tijoin_many = tijoin_many;
tkeys = tkeys;
tvalues = tvalues;
tkeysvalues = tkeysvalues;
tflip = tflip;
tflip_inplace = tflip_inplace;
tiflip = tiflip;
tset = tset;
tiset = tiset;
tisarray = tisarray;
tiinsert_args = tiinsert_args;
timap_inplace = timap_inplace;
timap = timap;
timap_sliding = timap_sliding;
tiwalk = tiwalk;
tiwalker = tiwalker;
tequals = tequals;
tiunique = tiunique;
tgenerate_n = tgenerate_n; -- deprecated
tgenerate_1d_linear = tgenerate_1d_linear;
tgenerate_2d_linear = tgenerate_2d_linear;
taccumulate = taccumulate;
tnormalize = tnormalize;
tnormalize_inplace = tnormalize_inplace;
tclone = tclone;
tcount_elements = tcount_elements;
tremap_to_array = tremap_to_array;
twalk_pairs = twalk_pairs;
tmap_values = tmap_values;
torderedset = torderedset;
torderedset_insert = torderedset_insert;
torderedset_remove = torderedset_remove;
twithdefaults = twithdefaults;
tifilter = tifilter;
tsetof = tsetof;
tset_many = tset_many;
tidentityset = tidentityset;
timapofrecords = timapofrecords;
tivalues = tivalues;
treadonly = treadonly;
treadonly_ex = treadonly_ex;
tmap_kv = tmap_kv;
tmapofrecordgroups = tmapofrecordgroups;
timapofrecordgroups = timapofrecordgroups;
tilistofrecordfields = tilistofrecordfields;
tipermute_inplace = tipermute_inplace;
tkvtorecordlist = tkvtorecordlist;
tgetpath = tgetpath;
tsetpath = tsetpath;
tsetpathvalue = tsetpathvalue;
tslice = tslice;
tarraylisttohashlist = tarraylisttohashlist;
tarraytohash = tarraytohash;
tkvlist2kvpairs = tkvlist2kvpairs;
tfilterkeylist = tfilterkeylist;
tisempty = tisempty;
tifindvalue_nonrecursive = tifindvalue_nonrecursive;
tkvmap_unpack = tkvmap_unpack;
tkvlist_to_hash = tkvlist_to_hash;
tmerge_many = tmerge_many;
}
test run test run with input download show line numbers
Travelled to 12 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt
No comments. add comment
| Recognizer | Recognition Result | Visualize | Recalc |
|---|---|---|---|
| #308 | 19795 | [visualize] |
| Snippet ID: | #157 |
| Snippet name: | table-utils.lua (luanucleo) |
| Eternal ID of this version: | #157/1 |
| Text MD5: | 886ad3506df943b2b2a59e5315f48d1a |
| Author: | stefan |
| Category: | |
| Type: | Lua code |
| Public (visible to everyone): | Yes |
| Archived (hidden from active list): | No |
| Created/modified: | 2014-01-13 03:45:08 |
| Source code size: | 19795 bytes / 946 lines |
| Pitched / IR pitched: | Yes / Yes |
| Views / Downloads: | 1469 / 417 |
| Referenced in: | #156 - string.lua (luanucleo) #3000382 - Answer for ferdie (>> t = 1, f = 0) #3000383 - Answer for funkoverflow (>> t=1, f=0 okay) |