--[[ Counts table entries when #tableObject will not work. (...or would also fail on tables using non-numeric keys.) --]] function NonContiguousCount(t) local count = 0 for _, _ in pairs(t) do count = count + 1 end return count end --[[ Description This is the only thing that should be used in table.sort's "comp" function. It allows a data description of how to sort tables to be written, instead of writing a custom sort function over and over again, you just create a single table that defines the sort ordering for a general table type. Arguments: entry1 (table) An entry in the table being sorted entry2 (table) Another entry in the table being sorted sortKey (non nil) A key in the entry arguments (tableX[sortKey]) to be used for sorting. sortKeys (table) A table whose keys are all keys in entryX and whose values are all tables. sortKeys options: isNumeric - used if a string field should be converted to a number for comparison isId64 - used for id64 fields which need special comparison functions caseInsensitive - used for case insensitive string comparison tiebreaker - the next key to be used if this one is tied tieBreakerSortOrder - the sort order to be used with the tie breaker key reverseTiebreakerSortOrder - a boolean which if set to true causes the tie breaker to use the opposite of the current sort order sortOrder (number) Must be ZO_SORT_ORDER_UP or ZO_SORT_ORDER_DOWN Return: When sortOrder is ZO_SORT_ORDER_UP: entry1[sortKey] < entry2[sortKey] When sortOrder is ZO_SORT_ORDER_DOWN: entry1[sortKey] > entry2[sortKey] --]] local validOrderingTypes = { ["number"] = true, ["string"] = true, ["boolean"] = true } local function NumberFromBoolean(boolean) if(boolean == true) then return 1 end return 0 end local type = type local tonumber = tonumber --[[ Common constants and types to make sorting a little easier. --]] -- Sort from A - Z ZO_SORT_ORDER_UP = true -- Sort from Z - A ZO_SORT_ORDER_DOWN = false -- Sort by the name field of your entry. Assumes the table being sorted is full of entries which are -- also tables; those entries having a key named "name". ZO_SORT_BY_NAME = { ["name"] = {} } ZO_SORT_BY_NAME_NUMERIC = { ["name"] = { isNumeric = true } } local IS_LESS_THAN = -1 local IS_EQUAL_TO = 0 local IS_GREATER_THAN = 1 function ZO_TableOrderingFunction(entry1, entry2, sortKey, sortKeys, sortOrder) local value1 = entry1[sortKey] if type(value1) == "function" then value1 = value1(entry1) end local value2 = entry2[sortKey] if type(value2) == "function" then value2 = value2(entry2) end local value1Type = type(value1) if value1Type ~= type(value2) or not validOrderingTypes[value1Type] then local value1Text if value1 == nil then value1Text = "nil" else value1Text = tostring(value1) end local value2Text if value2 == nil then value2Text = "nil" else value2Text = tostring(value2) end internalassert(false, string.format("%s is not a valid sort key for this data. value1 = %s. value2 = %s.", sortKey or "[nil key]", value1Text, value2Text)) return false end if value1Type == "boolean" then value1 = NumberFromBoolean(value1) value2 = NumberFromBoolean(value2) end local compareResult if sortKeys[sortKey].isId64 then compareResult = CompareId64s(value1, value2) else if sortKeys[sortKey].isNumeric then value1 = tonumber(value1) value2 = tonumber(value2) elseif value1Type == "string" then if sortKeys[sortKey].caseInsensitive then value1 = zo_strlower(value1) value2 = zo_strlower(value2) end end if value1 < value2 then compareResult = IS_LESS_THAN elseif value1 > value2 then compareResult = IS_GREATER_THAN else compareResult = IS_EQUAL_TO end end -- The two pieces of data are equal, now this needs to tiebreaker to a different key and recurse. -- This is so that in a list sorted by something like AllianceType, where there are only three -- alliances, the tiebreaker would sort within the "name" key of the table entry. if compareResult == IS_EQUAL_TO then local tiebreaker = sortKeys[sortKey].tiebreaker if tiebreaker then local nextSortOrder if sortKeys[sortKey].tieBreakerSortOrder ~= nil then nextSortOrder = sortKeys[sortKey].tieBreakerSortOrder else nextSortOrder = sortOrder end if sortKeys[sortKey].reverseTiebreakerSortOrder then nextSortOrder = not nextSortOrder end return ZO_TableOrderingFunction(entry1, entry2, tiebreaker, sortKeys, nextSortOrder) end else if sortOrder == ZO_SORT_ORDER_UP then return compareResult == IS_LESS_THAN end return compareResult == IS_GREATER_THAN end return false end function ZO_ClearNumericallyIndexedTable(t) for i = #t, 1, -1 do t[i] = nil end end function ZO_ClearTable(t) for k in pairs(t) do t[k] = nil end end --Want to keep ZO_ClearTable(t) snappy, so don't bog it down with an optional callback param to if check function ZO_ClearTableWithCallback(t, c) for k, v in pairs(t) do c(v) t[k] = nil end end function ZO_ShallowTableCopy(source, dest) dest = dest or {} for k, v in pairs(source) do dest[k] = v end return dest end function ZO_DeepTableCopy(source, dest) dest = dest or {} setmetatable(dest, getmetatable(source)) for k, v in pairs(source) do if type(v) == "table" then dest[k] = ZO_DeepTableCopy(v) else dest[k] = v end end return dest end -- Returns true if table is nil or empty function ZO_IsTableEmpty(t) return not t or next(t) == nil end -- Returns true if running the iterator once did not return a value. this modifies the iterator. function ZO_IsIteratorEmpty(iteratorFunction, invariantState, controlValue) local nextControlValue = iteratorFunction(invariantState, controlValue) if nextControlValue == nil then return true end return false end -- The dest table is mutable and will take in the values of all subsequent tables. It must be initialized. function ZO_CombineNumericallyIndexedTables(dest, ...) local counter = #dest for sourceTableIndex = 1, select("#", ...) do local sourceTable = select(sourceTableIndex, ...) for _, data in ipairs(sourceTable) do counter = counter + 1 dest[counter] = data end end end -- The dest table is mutable and will take in the values of all subsequent tables. It must be initialized. function ZO_CombineNonContiguousTables(dest, ...) for sourceTableIndex = 1, select("#", ...) do local sourceTable = select(sourceTableIndex, ...) for key, data in pairs(sourceTable) do assert(dest[key] == nil, "Cannot combine tables that share keys") dest[key] = data end end end function ZO_IsElementInNumericallyIndexedTable(t, element) for index, value in ipairs(t) do if value == element then return true end end return false end function ZO_IndexOfElementInNumericallyIndexedTable(t, element) for index, value in ipairs(t) do if value == element then return index end end return nil end function ZO_IsElementInNonContiguousTable(t, element) for key, value in pairs(t) do if value == element then return true end end return false end function ZO_KeyOfFirstElementInNonContiguousTable(t, element) for key, value in pairs(t) do if value == element then return key end end return nil end function ZO_RemoveFirstElementFromNumericallyIndexedTable(t, element) for index, value in ipairs(t) do if value == element then table.remove(t, index) return true end end return false end function ZO_TableRandomInsert(t, element) table.insert(t, zo_random(#t + 1), element) end function ZO_NumericallyIndexedTableIterator(t) return ipairs(t) end do local function NumericallyIndexedTableReverseIterator(t, i) if i > 1 then i = i - 1 return i, t[i] end end function ZO_NumericallyIndexedTableReverseIterator(t) return NumericallyIndexedTableReverseIterator, t, #t + 1 end end function ZO_FilteredNumericallyIndexedTableIterator(t, filterFunctions) local numFilters = filterFunctions and #filterFunctions or 0 if numFilters > 0 then local index = 0 local count = #t return function() index = index + 1 while index <= count do local passesFilter = true local data = t[index] for filterIndex = 1, numFilters do if not filterFunctions[filterIndex](data) then passesFilter = false break end end if passesFilter then return index, data else index = index + 1 end end end else return ipairs(t) end end function ZO_FilteredNonContiguousTableIterator(t, filterFunctions) local numFilters = filterFunctions and #filterFunctions or 0 if numFilters > 0 then local nextKey, nextData = next(t) return function() while nextKey do local currentKey, currentData = nextKey, nextData nextKey, nextData = next(t, nextKey) local passesFilter = true for filterIndex = 1, numFilters do if not filterFunctions[filterIndex](currentData) then passesFilter = false break end end if passesFilter then return currentKey, currentData end end end else return pairs(t) end end function ZO_DeepAcyclicTableCompare(t1, t2, maxTablesVisited) local visitedLeft, visitedRight = {}, {} local numVisited = 0 local function visit(left, right) if type(left) == "table" and type(right) == "table" then if visitedLeft[left] or visitedRight[right] then internalassert(false, "Comparing tables with cycles.") return false end visitedLeft[left], visitedRight[right] = true, true numVisited = numVisited + 2 if numVisited > maxTablesVisited then internalassert(false, "Max table limit reached") return false end for k, v in pairs(left) do if not visit(v, right[k]) then return false end end for k, v in pairs(right) do if left[k] == nil then return false end end if getmetatable(left) ~= getmetatable(right) then return false end return true else return left == right end end return visit(t1, t2) end function ZO_CreateSetFromArguments(...) local set = {} for i = 1, select('#', ...) do set[select(i, ...)] = true end return set end function ZO_AreNumericallyIndexedTablesEqual(left, right) if #left == #right then for index, value in ipairs(left) do if right[index] ~= value then return false end end return true end return false end -- Creates a non-contiguous table, the keys of which are the unique values -- in the numerically indexed table t or, if t is scalar, the value of t. function ZO_CreateSet(t) local s = {} local tType = type(t) if tType == "table" then for _, v in ipairs(t) do s[v] = true end elseif tType ~= "nil" then s[t] = true end return s end -- Returns a non-contiguous table, the keys of which are the intersecting keys -- shared between the non-contiguous sets s1 and s2. function ZO_IntersectSets(s1, s2) local s = {} for k in pairs(s1) do if s2[k] then s[k] = true end end return s end -- Returns true if the non-contiguous sets s1 and s2 share one or more keys. function ZO_AreIntersectingSets(s1, s2) return next(ZO_IntersectSets(s1, s2)) ~= nil end -- Returns a non-contiguous table, the keys of which are the intersecting values -- shared between the numerically indexed tables t1 and t2. function ZO_IntersectNumericallyIndexedTables(t1, t2) local s1 = ZO_CreateSet(t1) local s2 = ZO_CreateSet(t2) return ZO_IntersectSets(s1, s2) end -- Returns true if the numerically indexed tables t1 and t2 share one or more values. function ZO_AreIntersectingNumericallyIndexedTables(t1, t2) local s = ZO_IntersectNumericallyIndexedTables(t1, t2) return next(s) ~= nil end -- Returns true if the non-contiguous sets s1 and s2 contain the same keys. function ZO_AreEqualSets(s1, s2) local size1 = NonContiguousCount(s1) local size2 = NonContiguousCount(s2) if size1 ~= size2 then return false end for k in pairs(s1) do if not s2[k] then return false end end return true end