Modulo:Wikilib/tables

da Pokémon Central Wiki, l'enciclopedia Pokémon in italiano.
Vai alla navigazione Vai alla ricerca
Questo modulo non ha ancora un manuale. Creane uno!
-- luacheck: new_globals table
-- Library table functions

local t = {}

-- Check whether a given value is an integer
local function isint(val)
    return type(val) == "number" and val == math.floor(val)
end

-- Returns true only if a is lexigographically greater than b
local function minor(a, b)
    if not b then
        return true
    end

    -- Converting to number for better comparison (eg. '01' and 1)
    local nA, nB = tonumber(a), tonumber(b)
    if nA and nB then
        return nA < nB
    end

    return tostring(a) < tostring(b)
end

--[[

Since lua5.2 unpack has been moved to table.unpack, this allows migration with
older versions on PCW.

--]]
table.unpack = table.unpack or unpack

--[[

Returns true if all of the elements in the list satisties a predicate.

The predicate function takes the value as the first argument, then the key;
usual key-value order is reversed because the key is often unnecessary, and
having it last leads to nicer syntax in the call site.

TODO: refactoring to use just built-ins

--]]
table.all = function(tab, funct, iter)
    return table.fold(tab, true, function(acc, value, key)
        return funct(value, key) and acc
    end, iter)
end

t.all = table.all

--[[

Returns true if at least one of the elements in the list satisties a predicate.

The predicate function takes the value as the first argument, then the key;
usual key-value order is reversed because the key is often unnecessary, and
having it last leads to nicer syntax in the call site.

TODO: refactoring to use just built-ins

--]]
table.any = function(tab, funct, iter)
    return table.fold(tab, false, function(acc, value, key)
        return funct(value, key) or acc
    end, iter)
end
t.any = table.any

--[[

Deep clones a table. It intentionally doesn't clone the metatables, as that's
the job of mw.clone.

--]]
table.copy = function(value)
    local dest = {}
    for k, v in pairs(value) do
        dest[k] = type(v) == "table" and table.copy(v) or v
    end
    return dest
end
t.copy = table.copy

--[[

Merges two tables into a new one. Integer indices of the second table will
follow the ones of the first. Other indices overwrite the ones of the first
in case of equality, unless the values are tables in both the source tables:
in this case they are merged recursively.

--]]
table.deepMerge = function(tab1, tab2)
    -- stylua: ignore
        -- Better to use mw.clone, so as to keep metatable of items.
    local dest = mw.clone(tab1)

    -- Double loop to keep integer keys sorted
    for _, value in ipairs(tab2) do
        table.insert(dest, value)
    end
    for key, value in pairs(tab2) do
        if not isint(key) then
            if
                dest[key]
                and type(dest[key]) == "table"
                and type(value) == "table"
            then
                dest[key] = table.deepMerge(dest[key], value)
            else
                dest[key] = value
            end
        end
    end
    return dest
end
table.deep_merge = table.deepMerge
t.deep_merge, t.deepMerge = table.deepMerge, table.deepMerge

--[[

Recursive search: looks for a value in a table and possible subtables. Returns
a list of indices, mapping the nesting of tables. This means that the last
index is the one of the element, the second last is the subtable it is found
in, the third last the subtable that contains such table an so on. If the
element is not found, returns nil.

Example:
table.deep_search({'a', {'b', 'c'}}, 'b') --> 2, 1

--]]
table.deepSearch = function(tab, value)
    for k, v in pairs(tab) do
        if v == value then
            return k
        end
        if type(v) == "table" then
            --[[
                If the last list element is not nil, value
                has been found, thus returning
            --]]
            local valueKeys = { table.deepSearch(v, value) }
            if valueKeys[#valueKeys] then
                return k, table.unpack(valueKeys)
            end
        end
    end
    -- No explicit return, thus nil
end
table.deep_search = table.deepSearch
t.deepSearch, t.deep_search = table.deepSearch, table.deepSearch

--[[

Returns true if table is empty, that is it has no values.

--]]
table.empty = function(tab)
    --[[
        next() is a stateless iterator, it doesn't modify the table;
        need to compare to nil as false is also a value
    --]]
    return next(tab) == nil
end
t.empty, t.isEmpty, t.is_empty = table.empty, table.empty, table.empty
table.isEmpty, table.is_empty = table.empty, table.empty

--[[

Returns whether two tables are equal: this means same keys mapped to same
values, where same is the == operator. For the sake of conveniente, it returns
true also for non-table arguments, as long as they computer equals.

--]]
table.equal = function(tab1, tab2)
    --[[
        Arguments are compared directly if:
            - One of them has the __eq metamethod
            - One of them is not a table
    --]]
    local mt1 = getmetatable(tab1)
    local mt2 = getmetatable(tab2)
    if
        mt1 and mt1.__eq
        or mt2 and mt2.__eq
        or type(tab1) ~= "table"
        or type(tab2) ~= "table"
    then
        return tab1 == tab2
    end

    -- Optimization: references to the same table can be assessed equal here
    if tab1 == tab2 then
        return true
    end

    --[[
        If the two tables have different number of items, they aren't equal.

        Furthermore, it covers the cases in which:
            - tab1 is empty, and the upcoming loop doesn't run at all.
            - tab1 is a subset of tab2, not covered by the loop, since only
                tab1 indices are used.
    --]]
    if table.getn(tab1) ~= table.getn(tab2) then
        return false
    end

    for key, value in pairs(tab1) do
        -- Easerie to compare this way, being table.equal very type-safe
        if not table.equal(value, tab2[key]) then
            return false
        end
    end

    return true
end
table.eq = table.equal
t.equal, t.eq = table.equal, table.equal

--[[

Returns a table containing only the elements that satisfy the passed predicate.
Integer keys are compacted, so that standard library functions will still work:
in fact, they don't get along well with "holes" in integer keys. Other keys are
kept unchanged.

The predicate takes as input an element and its key, in this order: in fact,
the key is often unnecessary, and would just clutter the code in these cases.
It returns a boolean: if this is true, the element is kept, else is discarded.

--]]
table.filter = function(tab, cond)
    local dest = {}

    -- Double loop to keep integer keys sorted and compact
    for key, value in ipairs(tab) do
        if cond(value, key) then
            table.insert(dest, value)
        end
    end
    for key, value in pairs(tab) do
        if not isint(key) and cond(value, key) then
            dest[key] = value
        end
    end
    return dest
end
t.filter = table.filter

--[[

Applies a function over all of the elements of a table returned by the passed
iterator. The function is expected to return a list. The lists produced from
the elements are merged together in the same way as table.merge would do. The
iterator defaults to pairs.

The function takes as input an element and its key, in this order: in fact, the
key is often unnecessary, and would just clutter the code in these cases.

--]]
table.flatMap = function(tab, funct, iter)
    iter = iter or pairs

    local dest = {}
    for k, v in iter(tab) do
        dest = table.merge(dest, funct(v, k))
    end
    return dest
end
table.flat_map = table.flatMap
t.flatMap, t.fat_map = table.flatMap, table.flatMap

--[[

Applies a function over all of the elements of a table returned by the passed
iterator. The function is expected to return a list. The lists produced from
the elements are converted to numeric lists: the order of the elements is
determined by the given iterator. These lists are then concatenated together,
in the order the elements of the initial list are returned by the iterator.

The function takes as input an element and its key, in this order: in fact, the
key is often unnecessary, and would just clutter the code in these cases.

--]]
table.flatMapToNum = function(tab, funct, iter)
    iter = iter or pairs

    local dest = {}
    for k, v in iter(tab) do
        for _, value in iter(funct(v, k)) do
            table.insert(dest, value)
        end
    end
    return dest
end
table.flat_map_to_num = table.flatMapToNum
t.flatMapToNum, t.flat_map_to_num = table.flatMapToNum, table.flatMapToNum

-- Returns the input table, but with keys and values swapped.
table.flip = function(tab)
    local flipped = {}
    for key, value in pairs(tab) do
        flipped[value] = key
    end
    return flipped
end
t.flip = table.flip

--[[

Removes one level of nesting in a table-of-tables. Returns a new table.
Non-table elements are left untouched, while subtables are merged in the
returned as they are given by the iterator. The iterator defaults to pairs.

As items are processed in the order they are returned by the iterator, care
should be taken regarding non-integer indices, since their value are overridden
in assignments. For example,

table.flatten({{1, 2, key1 = 5}, {key1 = 9}}, ipairs) --> {1, 2, key1 = 9}
table.flatten({{key1 = 9}, {1, 2, key1 = 5}}, ipairs) --> {1, 2, key1 = 5}

--]]
table.flatten = function(tab, iter)
    iter = iter or pairs

    local dest = {}
    for k, v in iter(tab) do
        if type(v) == "table" then
            dest = table.merge(dest, v)
        else
            dest[k] = v
        end
    end
    return dest
end
t.flatten = table.flatten

--[[

Classic fold on lists: applies a binary function to an accumulator and a
value-key pair taken from the list and returns the final result. The iterator
determines the order elements are scanned, and therefore the final result.

The function takes the accumulator as the first argument, then the value and
finally the key; usual key-value order is reversed because the key is often
unnecessary, and having it last leads to nicer syntax in callers.

--]]
table.fold = function(tab, zero, func, iter)
    iter = iter or pairs

    for key, value in iter(tab) do
        zero = func(zero, value, key)
    end

    return zero
end
t.fold = table.fold

--[[

Returns the number of elements in a table. It should only be used when
Scribunto overrides the # operator so that it doesn't work propertly.
If the second argument is 'num' it only counts items having a integer index,
otherwise it takes all elements in account.

--]]
---@diagnostic disable-next-line: duplicate-set-field
table.getn = function(self, count)
    count = count or "all"

    local n = 0
    local iterator = (count == true or tostring(count):lower() == "num")
            and ipairs
        or pairs

    for _ in iterator(self) do
        n = n + 1
    end

    return n
end
t.getn = table.getn

--[[

Returns a numeric table whose elements are the keys of the passed one, in an
unspecified order.

--]]
table.keys = function(tab)
    local keys = {}
    for key in pairs(tab) do
        table.insert(keys, key)
    end
    return keys
end
t.keys = table.keys

--[[

Applies a function to the elements of a table scanned by the passed iterator.
It returls a table holding the results of the function application, bound to
the same keys as the original items. If no iterator is passed, all elements
are scanned.

The function takes as input an element and its key, in this order: in fact, the
key is often unnecessary, and would just clutter the code in these cases.

--]]
table.map = function(tab, funct, iter)
    iter = iter or pairs

    local dest = {}
    for key, value in iter(tab) do
        dest[key] = funct(value, key)
    end
    return dest
end
t.map = table.map

--[[

Applies a function to the elements of a table scanned by the passed iterator.
It returls a table holding the results of the function application, bound to
integer keys in the order the original elements are returned by the iterator.
If no iterator is passed, all elements are scanned.

The function takes as input an element and its key, in this order: in fact, the
key is often unnecessary, and would just clutter the code in these cases.

--]]
table.mapToNum = function(tab, funct, iter)
    iter = iter or pairs

    local dest = {}
    for key, value in iter(tab) do
        table.insert(dest, funct(value, key))
    end
    return dest
end
table.map_to_num = table.mapToNum
t.mapToNum, t.map_to_num = table.mapToNum, table.mapToNum

--[[

Merges two tables into a new one. Integer indices of the second table will
follow the ones of the first; other indices overwrite the ones of the first
in case of equlity.

--]]
table.merge = function(tab1, tab2)
    -- stylua: ignore
        -- Better to use mw.clone, so as to keep metatable of items.
    local dest = mw.clone(tab1)

    -- Double loop to keep integer keys sorted
    for _, value in ipairs(tab2) do
        table.insert(dest, value)
    end
    for key, value in pairs(tab2) do
        if not isint(key) then
            dest[key] = value
        end
    end
    return dest
end
t.merge = table.merge

-- Predicate search. Returns the index of any value satisfying the predicate,
-- nil if no such values exists in the table. The last parameter iter is
-- optional and specifies the iterator to use (pairs is the default). The
-- predicate takes as arguments the value and the key (the order is reversed
-- because often the key isn't needed)
table.find = function(tab, pred, iter)
    iter = iter or pairs
    for k, v in iter(tab) do
        if pred(v, k) then
            return k
        end
    end
    return nil
end
t.find = table.find

-- Linear search. Returns the index of the passed value if found, else nil.
table.search = function(tab, value)
    for k, v in pairs(tab) do
        if v == value then
            return k
        end
    end
    return nil
end
t.search = table.search

--[[

Returns the elements of a numeric table whose indices are within the specified
range. The end of the range defaults to the end of the source table.

Negative values count from the end of the table. For example, -1 is the last
element, -2 is the second last and so on.

--]]
table.slice = function(tab, from, to)
    local length = #tab

    to = to or length
    if from < 0 then
        from = length + from + 1 -- + because from is negative
    end
    if to < 0 then
        to = length + to + 1 -- + because to is negative
    end

    local dest = {}
    for k = from, to do
        table.insert(dest, tab[k])
    end
    return dest
end
t.slice = table.slice

--[[

Aliases keys in a single table. Arguments:
    - tab: the source table
    - source: list of keys to be aliased
    - dest: list of tables, in which every element contains the aliases
        to its corresponding one in source.

Example:
table.tableKeysAlias(t, {'key', 'key1'}, {{'alias', alias1'},
    {'alias2', 'alias3'}})
Equivalent with assignments:
t.alias, t.alias1 = t.key, t.key
t.alias2, t.alias3 = t.key1, t.key1

--]]
table.tableKeysAlias = function(tab, source, dest)
    for destGroup, sourceKey in ipairs(source) do
        for _, destKey in ipairs(dest[destGroup]) do
            tab[destKey] = tab[sourceKey]
        end
    end
end

table.table_keys_alias, table.keysAlias, table.keys_alias =
    table.tableKeysAlias, table.tableKeysAlias, table.tableKeysAlias
t.tableKeysAlias, t.table_keys_alias, t.keysAlias, t.keys_alias =
    table.tableKeysAlias,
    table.tableKeysAlias,
    table.tableKeysAlias,
    table.tableKeysAlias

--[[

Returns a table with the same items of the input one, but without duplicates.
The keys of repeated elements that are kept are the first in lexicographic
order, unless they are integer: in this case they are compacted to allow
compatibility with standard library functions.

--]]
table.unique = function(tab)
    --[[
        Check is meant to be used to test uniqueness of values: the most
        efficient implementation is to keep values as indices, so that
        uniqueness can be checked with simple table indexing. The values are
        the indices in the final table, so that they can easily be flipped.

        n is used as incremental integer index, to avoid "holes" in the final
        table.
    --]]
    local check, n = {}, 1

    -- Minor is not used as ipairs returns integer keys in ascending order.
    for _, value in ipairs(tab) do
        if not check[value] then
            check[value] = n
            n = n + 1
        end
    end

    for key, value in pairs(tab) do
        if not isint(key) then
            -- If value is not in check, minor returns true, as the second argument
            -- is nil
            if minor(key, check[value]) then
                check[value] = key
            end
        end
    end

    return table.flip(check)
end
t.unique = table.unique

--[[

Returns a numeric table containing the values of the passed table, in the
order they are returned in by the iterator. Such iterator defaults to pairs.

--]]
table.values = function(tab, iter)
    iter = iter or pairs

    local values = {}
    for _, value in iter(tab) do
        table.insert(values, value)
    end
    return values
end
t.values = table.values

--[[

Returns a table from two sources. The items of the new table are obtained by
combining the elements with the same index in the two sources. The default
combinator creates a pair, as a table with two elements.

The used indices are those of the first table returned by the iterator. No
other indices will be considered, especially the extra indices of the second
table. The iterator defaults to pairs.

--]]
table.zip = function(tab1, tab2, combinator, iter)
    combinator = combinator or function(a, b)
        return { a, b }
    end
    iter = iter or pairs

    local res = {}
    for k, v in iter(tab1) do
        res[k] = combinator(v, tab2[k])
    end
    return res
end
t.zip = table.zip

--[[

Returns a table from a source, grouping elements by a function value. The items
of the new table are tables, each corresponding to a group. Groups are
determined by the return value of a function: the result is used as a key for
the group table, that contains all the elements of the passed table that
returned that value.

The getGroup function takes an element and its key, in this order: in fact, the
key is often unnecessary, and would just clutter the code in these cases.

--]]
table.groupBy = function(tab, getGroup, iter)
    iter = iter or pairs

    local groups = {}
    for _, v in iter(tab) do
        local groupID = getGroup(v)
        if not groups[groupID] then
            groups[groupID] = {}
        end
        table.insert(groups[groupID], v)
    end

    return groups
end
t.groupBy = table.groupBy

--[[

Insert into a list all elements from another list. The third parameter is an
optional iterator to specify how to get elements from the second list (default
to ipairs).

--]]
table.append = function(list, elems, iter)
    iter = iter or ipairs
    for _, v in iter(elems) do
        table.insert(list, v)
    end
    return list
end
t.append = table.append

return t