--- 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 ipairs ({...}) do for _, v in ipairs (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 ipairs (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 ipairs (l) do if type (v) == "table" then m = concat (m, flatten (v)) else table.insert (m, v) end 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 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