--------------------------------------------------------------------------- --- Various functions to worj with tables. -- -- Examples -- ======== -- -- Using `cycle_value`, you can cycle through values in a table. -- When the end of the table is reached, `cycle_value` loops around to the beginning. -- @DOC_text_gears_table_cycle_value_EXAMPLE@ -- -- @utillib gears.table --------------------------------------------------------------------------- local rtable = table local gmath = require("gears.math") local gtable = {} --- Join all tables given as arguments. -- This will iterate over all tables and insert their entries into a new table. -- @tparam table ... Tables to join. -- @treturn table A new table containing all entries from the arguments. -- @staticfct gears.table.join function gtable.join(...) local ret = {} for i = 1, select("#", ...) do local t = select(i, ...) if t then for k, v in pairs(t) do if type(k) == "number" then rtable.insert(ret, v) else ret[k] = v end end end end return ret end --- Override elements in the target table with values from the source table. -- -- Note that this method doesn't copy entries found in `__index`. -- Nested tables are copied by reference and not recursed into. -- -- @tparam table target The target table. Values from `source` will be copied -- into this table. -- @tparam table source The source table. Its values will be copied into -- `target`. -- @tparam[opt=false] bool raw If `true`, values will be assigned with `rawset`. -- This will bypass metamethods on `target`. -- @treturn table The target table. -- @staticfct gears.table.crush function gtable.crush(target, source, raw) if raw then for k, v in pairs(source) do rawset(target, k, v) end else for k, v in pairs(source) do target[k] = v end end return target end --- Pack all elements with an integer key into a new table. -- While both lua and luajit implement __len over sparse -- tables, the standard defines it as an implementation -- detail. -- -- This function removes any entries with non-numeric keys. -- -- @tparam table t A potentially sparse table. -- @treturn table A packed table with only numeric keys. -- @staticfct gears.table.from_sparse function gtable.from_sparse(t) local keys= {} for k in pairs(t) do if type(k) == "number" then keys[#keys+1] = k end end table.sort(keys) local ret = {} for _,v in ipairs(keys) do ret[#ret+1] = t[v] end return ret end --- Check if a table has an item and return its key. -- -- @tparam table t The table. -- @param item The item to look for in values of the table. -- @treturn[1] string|number The key of the item. -- @treturn[2] nil -- @staticfct gears.table.hasitem function gtable.hasitem(t, item) for k, v in pairs(t) do if v == item then return k end end end --- Get all matching table keys for a `matcher` function. -- -- @tparam table t The table. -- @tparam function matcher A function taking the key and value as arguments and -- returning a boolean. -- @tparam[opt=false] boolean ordered If true, only look for continuous -- numeric keys. -- @tparam[opt=nil] number max The maximum number of entries to find. -- @treturn table|nil An ordered table with all the keys or `nil` if none were -- found. -- @staticfct gears.table.find_keys function gtable.find_keys(t, matcher, ordered, max) if max == 0 then return nil end ordered, max = ordered or false, 0 local ret, it = {}, ordered and ipairs or pairs for k, v in it(t) do if matcher(k,v) then table.insert(ret, k) if #ret == max then break end end end return #ret > 0 and ret or nil end --- Find the first key that matches a function. -- -- @tparam table t The table. -- @tparam function matcher A function taking the key and value as arguments and -- returning a boolean. -- @tparam[opt=false] boolean ordered If true, only look for continuous -- numeric keys. -- @return The table key or nil. -- @staticfct gears.table.find_first_key function gtable.find_first_key(t, matcher, ordered) local ret = gtable.find_keys(t, matcher, ordered, 1) return ret and ret[1] or nil end --- Get a sorted table with all keys from a table. -- -- @tparam table t The table for which the keys to get. -- @treturn table A table with keys. -- @staticfct gears.table.keys function gtable.keys(t) local keys = { } for k, _ in pairs(t) do rtable.insert(keys, k) end rtable.sort(keys, function (a, b) return type(a) == type(b) and a < b or false end) return keys end --- Get the number of keys in a table, both integer and string indicies. -- -- This is functionally equivalent, but faster than `#gears.table.keys(t)`. -- -- @DOC_text_gears_table_count_keys_EXAMPLE@ -- -- @tparam table t The table for which to count the keys. -- @treturn number The number of keys in the table. -- @staticfct gears.table.count_keys function gtable.count_keys(t) local count = 0 for _ in pairs(t) do count = count + 1 end return count end --- Filter a table's keys for certain content type. -- -- @tparam table t The table to retrieve the keys for. -- @tparam string ... The types to look for. -- @treturn table A filtered table. -- @staticfct gears.table.keys_filter function gtable.keys_filter(t, ...) local keys = gtable.keys(t) local keys_filtered = { } for _, k in pairs(keys) do for _, et in pairs({...}) do if type(t[k]) == et then rtable.insert(keys_filtered, k) break end end end return keys_filtered end --- Reverse a table. -- -- @tparam table t The table to reverse. -- @treturn table A reversed table. -- @staticfct gears.table.reverse function gtable.reverse(t) local tr = { } -- Reverse all elements with integer keys. for _, v in ipairs(t) do rtable.insert(tr, 1, v) end -- Add the remaining elements. for k, v in pairs(t) do if type(k) ~= "number" then tr[k] = v end end return tr end --- Clone a table. -- -- @tparam table t The table to clone. -- @tparam[opt=true] bool deep If `true`, recurse into nested tables to create -- a deep clone. -- @treturn table A clone of `t`. -- @staticfct gears.table.clone function gtable.clone(t, deep) deep = deep == nil and true or deep local c = { } for k, v in pairs(t) do if deep and type(v) == "table" then c[k] = gtable.clone(v) else c[k] = v end end return c end --- Get the next (or previous) value from a table and cycle if necessary. -- -- If the table contains the same value multiple type (aka, is not a set), the -- `first_index` has to be specified. -- -- @tparam table t The input table. -- @param value The start value. Must be an element of the input table `t`. -- @tparam[opt=1] number step_size The amount to increment the index by. -- When this is negative, the function will cycle through the table backwards. -- @tparam[opt=nil] function filter An optional filter function. It receives a -- value from the table as parameter and should return a boolean. If it -- returns `false`, the value is skipped and `cycle_value` tries the next one. -- @tparam[opt=1] number start_at Where to start the lookup from. -- @return The next eligible value. If no value matches, `nil` is returned. -- @treturn number|nil If a value is found, this is its index within the input -- table. -- @staticfct gears.table.cycle_value function gtable.cycle_value(t, value, step_size, filter, start_at) local k = gtable.hasitem(t, value, true, start_at) if not k then return end step_size = step_size or 1 local new_key = gmath.cycle(#t, k + step_size) if filter and not filter(t[new_key]) then for i=1, #t, step_size do local k2 = gmath.cycle(#t, new_key + i) if filter(t[k2]) then return t[k2], k2 end end return end return t[new_key], new_key end --- Iterate over a table. -- -- Returns an iterator to cycle through all elements of a table that match a -- given criteria, starting from the first element or the given index. -- -- @tparam table t The table to iterate. -- @tparam func filter A function that returns true to indicate a positive -- match. -- @param func.item The item to filter. -- @tparam[opt=1] int start Index to start iterating from. -- Default is 1 (=> start of the table). -- @treturn func -- @staticfct gears.table.iterate function gtable.iterate(t, filter, start) local count = 0 local index = start or 1 local length = #t return function () while count < length do local item = t[index] index = gmath.cycle(#t, index + 1) count = count + 1 if filter(item) then return item end end end end --- Merge items from the source table into the target table. -- -- Note that this only considers the array part of `source` (same semantics -- as `ipairs`). -- Nested tables are copied by reference and not recursed into. -- -- @tparam table target The target table. Values from `source` will be copied -- into this table. -- @tparam table source The source table. Its values will be copied into -- `target`. -- @treturn table The target table. -- @staticfct gears.table.merge function gtable.merge(target, source) for _, v in ipairs(source) do table.insert(target, v) end return target end --- Update the `target` table with entries from the `new` table. -- -- Compared to `gears.table.merge`, this version is intended to work using both -- an `identifier` function and a `merger` function. This works only for -- indexed tables. -- -- The main use case is when changing the table reference is not possible or -- when the `target` contains additional content that must be kept. -- -- Note that calling this function involve a lot of looping and should not be -- done often. -- -- @tparam table target The table to modify. -- @tparam table new The table which contains the new content. -- @tparam function identifier A function which take the table entry (either -- from the `target` or `new` table) and return an unique identifier. The -- identifier type isn't important as long as `==` works to compare them. -- @tparam[opt] function merger A function takes the entry to modify as first -- parameter and the new entry as second. The function must return the merged -- value. If none is provided, there is no attempt to merge the content. -- @treturn table The target table (for daisy chaining). -- @treturn table The new entries. -- @treturn table The removed entries. -- @treturn table The updated entries. -- @staticfct gears.table.diff_merge -- @usage local output, added, removed, updated = gears.table.diff_merge( -- output, input, function(v) return v.id end, gears.table.crush, -- ) function gtable.diff_merge(target, new, identifier, merger) local n_id, o_id, up = {}, {}, {} local add, rem = gtable.clone(new, false), gtable.clone(target, false) for _, v in ipairs(target) do o_id[identifier(v)] = v end for _, v in ipairs(new) do n_id[identifier(v)] = v end for k, v in ipairs(rem) do if n_id[identifier(v)] then rem[k] = nil end end for k, v in ipairs(add) do local id = identifier(v) local old = o_id[id] if old then add[k] = nil if merger then o_id[id] = merger(old, v) table.insert(up, old) end else table.insert(target, v) end end for k, v in ipairs(target) do local id = identifier(v) if o_id[id] then target[k] = o_id[id] end end -- Compact. rem, add = gtable.from_sparse(rem), gtable.from_sparse(add) for _, v in ipairs(rem) do for k, v2 in ipairs(target) do if v == v2 then table.remove(target, k) break end end end return target, add, rem, up end --- Map a function to a table. -- -- The function is applied to each value in the table, returning a modified -- table. -- -- @tparam function f The function to be applied to each value in the table. -- @tparam table tbl The container table whose values will be operated on. -- @treturn table -- @staticfct gears.table.map function gtable.map(f, tbl) local t = {} for k,v in pairs(tbl) do t[k] = f(v) end return t end return gtable -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80