485 lines
15 KiB
Lua
485 lines
15 KiB
Lua
-- ------------------------------------------------------------------------------ --
|
|
-- TradeSkillMaster --
|
|
-- https://tradeskillmaster.com --
|
|
-- All Rights Reserved - Detailed license information included with addon. --
|
|
-- ------------------------------------------------------------------------------ --
|
|
|
|
--- Table Functions
|
|
-- @module Table
|
|
|
|
local _, TSM = ...
|
|
local Table = TSM.Init("Util.Table")
|
|
local TempTable = TSM.Include("Util.TempTable")
|
|
local private = {
|
|
filterTemp = {},
|
|
sortValueLookup = nil,
|
|
sortValueReverse = false,
|
|
sortValueUnstable = false,
|
|
iterContext = { arg = {}, index = {}, helperFunc = {}, cleanupFunc = {} },
|
|
inwardIteratorContext = {},
|
|
}
|
|
setmetatable(private.iterContext.arg, { __mode = "k" })
|
|
setmetatable(private.iterContext.index, { __mode = "k" })
|
|
setmetatable(private.iterContext.helperFunc, { __mode = "k" })
|
|
setmetatable(private.iterContext.cleanupFunc, { __mode = "k" })
|
|
local READ_ONLY_TABLE_MT = {
|
|
__index = function(_, key) error(format("Key (%s) does not exist in read-only table", tostring(key)), 2) end,
|
|
__newindex = function(_, key) error(format("Writing (%s) to read-only table", tostring(key)), 2) end,
|
|
__metatable = false,
|
|
}
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Module Functions
|
|
-- ============================================================================
|
|
|
|
--- Creates an iterator from a table.
|
|
-- NOTE: This iterator must be run to completion and not interrupted (i.e. with a `break` or `return`).
|
|
-- @tparam table tbl The table (numerically-indexed) to iterate over
|
|
-- @tparam[opt] function helperFunc A helper function which gets passed the current index, value, and user-specified arg
|
|
-- and returns nothing if an entry in the table should be skipped or the result of an iteration loop
|
|
-- @param[opt] arg A value to be passed to the helper function
|
|
-- @tparam[opt] function cleanupFunc A function to be called (passed `tbl`) to cleanup at the end of iterator
|
|
-- @return An iterator with fields: `index, value` or the return of `helperFunc`
|
|
function Table.Iterator(tbl, helperFunc, arg, cleanupFunc)
|
|
local iterContext = TempTable.Acquire()
|
|
iterContext.data = tbl
|
|
iterContext.arg = arg
|
|
iterContext.index = 0
|
|
iterContext.helperFunc = helperFunc
|
|
iterContext.cleanupFunc = cleanupFunc
|
|
return private.TableIterator, iterContext
|
|
end
|
|
|
|
--- Creates an iterator from the keys of a table.
|
|
-- @tparam table tbl The table to iterate over the keys of
|
|
-- @return An iterator with fields: `key`
|
|
function Table.KeyIterator(tbl)
|
|
return private.TableKeyIterator, tbl, nil
|
|
end
|
|
|
|
--- Uses a function to filter the entries in a table.
|
|
-- @tparam table tbl The table to be filtered
|
|
-- @tparam function func The filter function which gets passed `key, value, ...` and returns true if that entry should
|
|
-- be removed from the table
|
|
-- @param[opt] ... Optional arguments to be passed to the filter function
|
|
function Table.Filter(tbl, func, ...)
|
|
assert(not next(private.filterTemp))
|
|
for k, v in pairs(tbl) do
|
|
if func(k, v, ...) then
|
|
tinsert(private.filterTemp, k)
|
|
end
|
|
end
|
|
for _, k in ipairs(private.filterTemp) do
|
|
tbl[k] = nil
|
|
end
|
|
wipe(private.filterTemp)
|
|
end
|
|
|
|
--- Removes all occurences of the value in the table.
|
|
-- Only the numerically-indexed entries are checked.
|
|
-- @tparam table tbl The table to remove the value from
|
|
-- @param value The value to remove
|
|
-- @treturn number The number of values removed
|
|
function Table.RemoveByValue(tbl, value)
|
|
local numRemoved = 0
|
|
for i = #tbl, 1, -1 do
|
|
if tbl[i] == value then
|
|
tremove(tbl, i)
|
|
numRemoved = numRemoved + 1
|
|
end
|
|
end
|
|
return numRemoved
|
|
end
|
|
|
|
--- Gets the table key by value.
|
|
-- @tparam table tbl The table to look through
|
|
-- @param value The value to get the key of
|
|
-- @return The key for the specified value or `nil`
|
|
function Table.KeyByValue(tbl, value)
|
|
for k, v in pairs(tbl) do
|
|
if v == value then
|
|
return k
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Gets the number of entries in the table.
|
|
-- This can be used when the count of a non-numerically-indexed table is desired (i.e. `#tbl` wouldn't work).
|
|
-- @tparam table tbl The table to get the number of entries in
|
|
-- @treturn number The number of entries
|
|
function Table.Count(tbl)
|
|
local count = 0
|
|
for _ in pairs(tbl) do
|
|
count = count + 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
--- Gets the distinct table key by value.
|
|
-- This function will assert if the value is not found in the table or if more than one key is found.
|
|
-- @tparam table tbl The table to look through
|
|
-- @param value The value to get the key of
|
|
-- @return The key for the specified value
|
|
function Table.GetDistinctKey(tbl, value)
|
|
local key = nil
|
|
for k, v in pairs(tbl) do
|
|
if v == value then
|
|
assert(not key)
|
|
key = k
|
|
end
|
|
end
|
|
assert(key)
|
|
return key
|
|
end
|
|
|
|
--- Checks if two tables have the same entries (non-recursively).
|
|
-- @tparam table tbl1 The first table to check
|
|
-- @tparam table tbl2 The second table to check
|
|
-- @treturn boolean Whether or not the tables are equal
|
|
function Table.Equal(tbl1, tbl2)
|
|
if Table.Count(tbl1) ~= Table.Count(tbl2) then
|
|
return false
|
|
end
|
|
for k, v in pairs(tbl1) do
|
|
if tbl2[k] ~= v then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- Returns whether or not the table is currently sorted.
|
|
-- @tparam table tbl The table to check
|
|
-- @tparam[opt] function sortFunc The helper function to use to determine sort order (same prototype as is used for `sort()`)
|
|
-- @tparam[opt=1] number firstIndex The first index to check
|
|
-- @tparam[opt=#tbl] number lastIndex The last index to check
|
|
-- @treturn boolean Whether or not the table is sorted
|
|
function Table.IsSorted(tbl, sortFunc, firstIndex, lastIndex)
|
|
sortFunc = sortFunc or private.DefaultSortFunc
|
|
firstIndex = firstIndex or 1
|
|
lastIndex = lastIndex or #tbl
|
|
local prevValue = tbl[firstIndex]
|
|
for i = firstIndex + 1, lastIndex do
|
|
local value = tbl[i]
|
|
if sortFunc(value, prevValue) then
|
|
return false
|
|
end
|
|
prevValue = value
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- Sorts a table with some optimizations over lua's sort().
|
|
-- @tparam table tbl The table to sort
|
|
-- @tparam[opt] function sortFunc The helper function to use to determine sort order (same prototype as is used for `sort()`)
|
|
function Table.Sort(tbl, sortFunc)
|
|
if Table.IsSorted(tbl, sortFunc) then
|
|
return
|
|
end
|
|
sort(tbl, sortFunc)
|
|
end
|
|
|
|
--- Returns whether or not the table is currently sorted with a value lookup table.
|
|
-- @tparam table tbl The table to sort
|
|
-- @tparam table valueLookup The sort value lookup table
|
|
-- @tparam[opt=1] number firstIndex The first index to check
|
|
-- @tparam[opt=#tbl] number lastIndex The last index to check
|
|
function Table.IsSortedWithValueLookup(tbl, valueLookup, firstIndex, lastIndex)
|
|
assert(not private.sortValueLookup and valueLookup)
|
|
private.sortValueLookup = valueLookup
|
|
private.sortValueReverse = false
|
|
private.sortValueUnstable = false
|
|
local result = Table.IsSorted(tbl, private.TableSortWithValueLookupHelper, firstIndex, lastIndex)
|
|
private.sortValueLookup = nil
|
|
return result
|
|
end
|
|
|
|
--- Merges two sorted tables with a value lookup table.
|
|
-- @tparam table tbl1 The first table to merge
|
|
-- @tparam table tbl2 The second table to merge
|
|
-- @tparam table result The result table
|
|
-- @tparam table valueLookup The sort value lookup table
|
|
function Table.MergeSortedWithValueLookup(tbl1, tbl2, result, valueLookup)
|
|
assert(not private.sortValueLookup and valueLookup)
|
|
private.sortValueLookup = valueLookup
|
|
private.sortValueReverse = false
|
|
private.sortValueUnstable = false
|
|
|
|
local index1, index2, resultIndex = 1, 1, 1
|
|
while true do
|
|
local value1 = tbl1[index1]
|
|
local value2 = tbl2[index2]
|
|
if value1 == nil and value2 == nil then
|
|
-- we're done
|
|
break
|
|
elseif value1 == nil then
|
|
result[resultIndex] = value2
|
|
index2 = index2 + 1
|
|
elseif value2 == nil then
|
|
result[resultIndex] = value1
|
|
index1 = index1 + 1
|
|
elseif private.TableSortWithValueLookupHelper(value1, value2) then
|
|
result[resultIndex] = value1
|
|
index1 = index1 + 1
|
|
else
|
|
result[resultIndex] = value2
|
|
index2 = index2 + 1
|
|
end
|
|
resultIndex = resultIndex + 1
|
|
end
|
|
private.sortValueLookup = nil
|
|
end
|
|
|
|
--- Does a table sort with an extra value lookup step.
|
|
-- @tparam table tbl The table to sort
|
|
-- @tparam table valueLookup The sort value lookup table
|
|
-- @tparam[opt=false] boolean reverse Reverse the sort order
|
|
-- @tparam[opt=false] boolean unstable Don't try to make the sort stable
|
|
function Table.SortWithValueLookup(tbl, valueLookup, reverse, unstable)
|
|
assert(not private.sortValueLookup and valueLookup)
|
|
private.sortValueLookup = valueLookup
|
|
private.sortValueReverse = reverse
|
|
private.sortValueUnstable = unstable
|
|
Table.Sort(tbl, private.TableSortWithValueLookupHelper)
|
|
private.sortValueLookup = nil
|
|
end
|
|
|
|
--- Creates an iterator which iterates through a numerically-indexed table (list) from the ends inward.
|
|
-- @tparam table tbl The table to iterate over
|
|
-- @return An iterator with fields: `index`, `value`, `isAscending`
|
|
function Table.InwardIterator(tbl)
|
|
assert(not private.inwardIteratorContext[tbl])
|
|
local context = TempTable.Acquire()
|
|
private.inwardIteratorContext[tbl] = context
|
|
context.inUse = true
|
|
context.tbl = tbl
|
|
context.leftIndex = 1
|
|
context.rightIndex = #tbl
|
|
context.isAscending = true
|
|
return private.InwardIteratorHelper, context, 0
|
|
end
|
|
|
|
--- Reverses the direction of the current inward iterator.
|
|
-- @tparam table tbl The table being iterated over
|
|
function Table.InwardIteratorReverse(tbl)
|
|
local context = private.inwardIteratorContext[tbl]
|
|
assert(context and context.tbl == tbl)
|
|
context.isAscending = not context.isAscending
|
|
end
|
|
|
|
--- Sets a table as read-only (modifications aren't checked).
|
|
-- @tparam table tbl The table to make read-only
|
|
function Table.SetReadOnly(tbl)
|
|
setmetatable(tbl, READ_ONLY_TABLE_MT)
|
|
end
|
|
|
|
--- Appends all values passed in to the end of the table.
|
|
-- @tparam table tbl The table to insert the data into
|
|
-- @param ... The values to insert
|
|
function Table.Append(tbl, ...)
|
|
local len = #tbl
|
|
for i = 1, select("#", ...) do
|
|
tbl[len + i] = select(i, ...)
|
|
end
|
|
end
|
|
|
|
--- Performs a binary search on a sorted table and returns the index of the search value.
|
|
-- @tparam table tbl The table to search
|
|
-- @tparam number|string searchValue The value to search for
|
|
-- @tparam[opt=nil] function valueFunc A function to call to get the value to compare
|
|
-- @param ... Extra values to pass to valueFunc
|
|
-- @treturn ?number The index of the value or nil if it wasn't found
|
|
-- @treturn ?number The insert index
|
|
function Table.BinarySearch(tbl, searchValue, valueFunc, ...)
|
|
if valueFunc then
|
|
searchValue = valueFunc(searchValue, ...)
|
|
end
|
|
local insertIndex = 1
|
|
local low, mid, high = 1, 0, #tbl
|
|
while low <= high do
|
|
mid = floor((low + high) / 2)
|
|
local value = tbl[mid]
|
|
if valueFunc then
|
|
value = valueFunc(tbl[mid], ...)
|
|
end
|
|
if value == searchValue then
|
|
return mid, mid
|
|
elseif value < searchValue then
|
|
-- we're too low
|
|
low = mid + 1
|
|
else
|
|
-- we're too high
|
|
high = mid - 1
|
|
end
|
|
insertIndex = low
|
|
end
|
|
return nil, insertIndex
|
|
end
|
|
|
|
--- Inserts a value into a sorted table by using the insertIndex returned by Table.BinarySearch().
|
|
-- @see Table.BinarySearch
|
|
-- @tparam table tbl The table
|
|
-- @tparam number|string value The value to insert
|
|
-- @tparam[opt=nil] function valueFunc A function to call to get the value to compare
|
|
-- @param ... Extra values to pass to valueFunc
|
|
function Table.InsertSorted(tbl, value, valueFunc, ...)
|
|
local _, insertIndex = Table.BinarySearch(tbl, value, valueFunc, ...)
|
|
tinsert(tbl, insertIndex, value)
|
|
end
|
|
|
|
--- Gets the common values from two or more sorted tables.
|
|
-- @tparam table tbls The tables to compare
|
|
-- @tparam table result The result table
|
|
-- @tparam[opt=nil] function valueFunc A function to call to get the value to compare
|
|
-- @param ... Extra values to pass to valueFunc
|
|
function Table.GetCommonValuesSorted(tbls, result, valueFunc, ...)
|
|
local numTbls = #tbls
|
|
if numTbls == 0 then
|
|
return
|
|
elseif numTbls == 1 then
|
|
for i = 1, #tbls[1] do
|
|
result[i] = tbls[1][i]
|
|
end
|
|
return
|
|
end
|
|
|
|
-- initialize our iterator indexes
|
|
for i = 1, numTbls do
|
|
local t = tbls[i]
|
|
assert(t._index == nil)
|
|
t._index = 1
|
|
end
|
|
|
|
while true do
|
|
-- go through each list and check if the current values are equal and get the max value
|
|
local isDone, isEqual = false, true
|
|
local equalValue, maxValue = nil, nil
|
|
for i = 1, numTbls do
|
|
local t = tbls[i]
|
|
local value = t[t._index]
|
|
value = value and valueFunc and valueFunc(value, ...)
|
|
if not value then
|
|
isDone = true
|
|
break
|
|
elseif i == 1 then
|
|
equalValue = value
|
|
maxValue = value
|
|
else
|
|
if value ~= equalValue then
|
|
isEqual = false
|
|
end
|
|
if value > maxValue then
|
|
maxValue = value
|
|
end
|
|
end
|
|
end
|
|
if isDone then
|
|
break
|
|
end
|
|
if isEqual then
|
|
-- all lists contained the same value, so insert it into our result and advance all the indexes
|
|
tinsert(result, tbls[1][tbls[1]._index])
|
|
for i = 1, numTbls do
|
|
local t = tbls[i]
|
|
t._index = t._index + 1
|
|
end
|
|
else
|
|
-- all lists aren't on the same value, so advanced each one to at least the current max value
|
|
for i = 1, numTbls do
|
|
local t = tbls[i]
|
|
local value = t[t._index]
|
|
value = value and valueFunc and valueFunc(value, ...)
|
|
while value and value < maxValue do
|
|
t._index = t._index + 1
|
|
value = t[t._index]
|
|
value = value and valueFunc and valueFunc(value, ...)
|
|
end
|
|
end
|
|
if isDone then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- clear all our iterator indexes
|
|
for i = 1, numTbls do
|
|
tbls[i]._index = nil
|
|
end
|
|
end
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
-- Private Helper Functions
|
|
-- ============================================================================
|
|
|
|
function private.TableKeyIterator(tbl, prevKey)
|
|
local key = next(tbl, prevKey)
|
|
return key
|
|
end
|
|
|
|
function private.TableIterator(iterContext)
|
|
iterContext.index = iterContext.index + 1
|
|
if iterContext.index > #iterContext.data then
|
|
local data = iterContext.data
|
|
local cleanupFunc = iterContext.cleanupFunc
|
|
TempTable.Release(iterContext)
|
|
if cleanupFunc then
|
|
cleanupFunc(data)
|
|
end
|
|
return
|
|
end
|
|
if iterContext.helperFunc then
|
|
local result = TempTable.Acquire(iterContext.helperFunc(iterContext.index, iterContext.data[iterContext.index], iterContext.arg))
|
|
if #result == 0 then
|
|
TempTable.Release(result)
|
|
return private.TableIterator(iterContext)
|
|
end
|
|
return TempTable.UnpackAndRelease(result)
|
|
else
|
|
return iterContext.index, iterContext.data[iterContext.index]
|
|
end
|
|
end
|
|
|
|
function private.TableSortWithValueLookupHelper(a, b)
|
|
local aValue = private.sortValueLookup[a]
|
|
local bValue = private.sortValueLookup[b]
|
|
if aValue == bValue then
|
|
if private.sortValueUnstable then
|
|
return false
|
|
else
|
|
return a > b
|
|
end
|
|
end
|
|
if private.sortValueReverse then
|
|
return aValue > bValue
|
|
else
|
|
return aValue < bValue
|
|
end
|
|
end
|
|
|
|
function private.InwardIteratorHelper(context)
|
|
if context.leftIndex > context.rightIndex then
|
|
private.inwardIteratorContext[context.tbl] = nil
|
|
TempTable.Release(context)
|
|
return
|
|
end
|
|
local index = nil
|
|
if context.isAscending then
|
|
-- iterating in ascending order
|
|
index = context.leftIndex
|
|
context.leftIndex = context.leftIndex + 1
|
|
else
|
|
-- iterating in descending order
|
|
index = context.rightIndex
|
|
context.rightIndex = context.rightIndex - 1
|
|
end
|
|
return index, context.tbl[index], context.isAscending
|
|
end
|
|
|
|
function private.DefaultSortFunc(a, b)
|
|
return a < b
|
|
end
|