--- Tables as lists.
module ("list", package.seeall)
require "base"
require "table_ext"
--- An iterator over the elements of a list.
-- @param l list to iterate over
-- @return iterator function which returns successive elements of the list
-- @return the list l
as above
-- @return true
function elems (l)
local n = 0
return function (l)
n = n + 1
if n <= #l then
return l[n]
end
end,
l, true
end
--- An iterator over the elements of a list, in reverse.
-- @param l list to iterate over
-- @return iterator function which returns precessive elements of the list
-- @return the list l
as above
-- @return true
function relems (l)
local n = #l + 1
return function (l)
n = n - 1
if n > 0 then
return l[n]
end
end,
l, true
end
--- Map a function over a list.
-- @param f function
-- @param l list
-- @return result list {f (l[1]), ..., f (l[#l])}
function map (f, l)
return _G.map (f, elems, l)
end
--- Map a function over a list of lists.
-- @param f function
-- @param ls list of lists
-- @return result list {f (unpack (ls[1]))), ..., f (unpack (ls[#ls]))}
function mapWith (f, l)
return _G.map (compose (f, unpack), elems, l)
end
--- Filter a list according to a predicate.
-- @param p predicate (function of one argument returning a boolean)
-- @param l list of lists
-- @return result list containing elements e
of
-- l
for which p (e)
is true
function filter (p, l)
return _G.filter (p, elems, l)
end
--- Return a slice of a list.
-- (Negative list indices count from the end of the list.)
-- @param l list
-- @param from start of slice (default: 1)
-- @param to end of slice (default: #l
)
-- @return {l[from], ..., l[to]}
function slice (l, from, to)
local m = {}
local len = #l
from = from or 1
to = to or len
if from < 0 then
from = from + len + 1
end
if to < 0 then
to = to + len + 1
end
for i = from, to do
table.insert (m, l[i])
end
return m
end
--- Return a list with its first element removed.
-- @param l list
-- @return {l[2], ..., l[#l]}
function tail (l)
return slice (l, 2)
end
--- Fold a binary function through a list left associatively.
-- @param f function
-- @param e element to place in left-most position
-- @param l list
-- @return result
function foldl (f, e, l)
return _G.fold (f, e, elems, l)
end
--- Fold a binary function through a list right associatively.
-- @param f function
-- @param e element to place in right-most position
-- @param l list
-- @return result
function foldr (f, e, l)
return _G.fold (function (x, y) return f (y, x) end,
e, relems, l)
end
--- Prepend an item to a list.
-- @param l list
-- @param x item
-- @return {x, unpack (l)}
function cons (l, x)
return {x, unpack (l)}
end
--- Append an item to a list.
-- @param l list
-- @param x item
-- @return {l[1], ..., l[#l], x}
function append (l, x)
local r = {unpack (l)}
table.insert (r, x)
return r
end
--- Concatenate lists.
-- @param ... lists
-- @return {l1[1], ...,
-- l1[#l1], ..., ln[1], ...,
-- ln[#ln]}
function concat (...)
local r = {}
for l in elems ({...}) do
for v in elems (l) do
table.insert (r, v)
end
end
return r
end
--- Repeat a list.
-- @param l list
-- @param n number of times to repeat
-- @return n
copies of l
appended together
function rep (l, n)
local r = {}
for i = 1, n do
r = list.concat (r, l)
end
return r
end
--- Reverse a list.
-- @param l list
-- @return list {l[#l], ..., l[1]}
function reverse (l)
local m = {}
for i = #l, 1, -1 do
table.insert (m, l[i])
end
return m
end
--- Transpose a list of lists.
-- This function in Lua is equivalent to zip and unzip in more
-- strongly typed languages.
-- @param ls {{l1,1, ..., l1,c}, ...,
-- {lr,1, ..., lr,c}}
-- @return {{l1,1, ..., lr,1}, ...,
-- {l1,c, ..., lr,c}}
function transpose (ls)
local ms, len = {}, #ls
for i = 1, math.max (unpack (map (function (l) return #l end, ls))) do
ms[i] = {}
for j = 1, len do
ms[i][j] = ls[j][i]
end
end
return ms
end
--- Zip lists together with a function.
-- @param f function
-- @param ls list of lists
-- @return {f (ls[1][1], ..., ls[#ls][1]), ..., f (ls[1][N], ..., ls[#ls][N])
-- where N = max {map (function (l) return #l end, ls)}
function zipWith (f, ls)
return mapWith (f, transpose (ls))
end
--- Project a list of fields from a list of tables.
-- @param f field to project
-- @param l list of tables
-- @return list of f
fields
function project (f, l)
return map (function (t) return t[f] end, l)
end
--- Turn a table into a list of pairs.
--
FIXME: Find a better name.
-- @param t table {i1=v1, ...,
-- in=vn}
-- @return list {{i1, v1}, ...,
-- {in, vn}}
function enpair (t)
local ls = {}
for i, v in pairs (t) do
table.insert (ls, {i, v})
end
return ls
end
--- Turn a list of pairs into a table.
--
FIXME: Find a better name.
-- @param ls list {{i1, v1}, ...,
-- {in, vn}}
-- @return table {i1=v1, ...,
-- in=vn}
function depair (ls)
local t = {}
for v in elems (ls) do
t[v[1]] = v[2]
end
return t
end
--- Flatten a list.
-- @param l list to flatten
-- @return flattened list
function flatten (l)
local m = {}
for v in ileaves (l) do
table.insert (m, v)
end
return m
end
--- Shape a list according to a list of dimensions.
--
-- Dimensions are given outermost first and items from the original
-- list are distributed breadth first; there may be one 0 indicating
-- an indefinite number. Hence, {0}
is a flat list,
-- {1}
is a singleton, {2, 0}
is a list of
-- two lists, and {0, 2}
is a list of pairs.
--
-- Algorithm: turn shape into all positive numbers, calculating
-- the zero if necessary and making sure there is at most one;
-- recursively walk the shape, adding empty tables until the bottom
-- level is reached at which point add table items instead, using a
-- counter to walk the flattened original list.
--
-- @param s {d1, ..., dn}
-- @param l list to reshape
-- @return reshaped list
-- FIXME: Use ileaves instead of flatten (needs a while instead of a
-- for in fill function)
function shape (s, l)
l = flatten (l)
-- Check the shape and calculate the size of the zero, if any
local size = 1
local zero
for i, v in ipairs (s) do
if v == 0 then
if zero then -- bad shape: two zeros
return nil
else
zero = i
end
else
size = size * v
end
end
if zero then
s[zero] = math.ceil (#l / size)
end
local function fill (i, d)
if d > #s then
return l[i], i + 1
else
local t = {}
for j = 1, s[d] do
local e
e, i = fill (i, d + 1)
table.insert (t, e)
end
return t, i
end
end
return (fill (1, 1))
end
--- Make an index of a list of tables on a given field
-- @param f field
-- @param l list of tables {t1, ...,
-- tn}
-- @return index {t1[f]=1, ...,
-- tn[f]=n}
function indexKey (f, l)
local m = {}
for i, v in ipairs (l) do
local k = v[f]
if k then
m[k] = i
end
end
return m
end
--- Copy a list of tables, indexed on a given field
-- @param f field whose value should be used as index
-- @param l list of tables {i1=t1, ...,
-- in=tn}
-- @return index {t1[f]=t1, ...,
-- tn[f]=tn}
function indexValue (f, l)
local m = {}
for i, v in ipairs (l) do
local k = v[f]
if k then
m[k] = v
end
end
return m
end
permuteOn = indexValue
--- Compare two lists element by element left-to-right
-- @param l first list
-- @param m second list
-- @return -1 if l
is less than m
, 0 if they
-- are the same, and 1 if l
is greater than m
function compare (l, m)
for i = 1, math.min (#l, #m) do
if l[i] < m[i] then
return -1
elseif l[i] > m[i] then
return 1
end
end
if #l < #m then
return -1
elseif #l > #m then
return 1
end
return 0
end
-- Metamethods for lists
metatable = {
-- list .. table = list.concat
__concat = list.concat,
-- list == list retains its referential meaning
-- list < list = list.compare returns < 0
__lt = function (l, m) return compare (l, m) < 0 end,
-- list <= list = list.compare returns <= 0
__le = function (l, m) return compare (l, m) <= 0 end,
__append = list.append,
}
--- List constructor.
-- Needed in order to use metamethods.
-- @param t list (as a table)
-- @return list (with list metamethods)
function new (l)
return setmetatable (l, metatable)
end
-- Function forms of operators
_G.op[".."] = list.concat