--- Unique Array class -- For working with unique string array values. Creates an array with hash/dictionary indexing. -- @classmod unique_array -- Adding or removeing values without using the provided methods can break the functionality of this class. -- Additional methods exported by requering unique_array are .set and .make_dictionary -- @usage local Unique_Array = require('__stdlib__/stdlib/utils/classes/unique_array') -- local my_array = Unique_Array('first') -- my_array:add('second') -- if my_array['second'] then -- print('its truthy') -- end' -- @set all=true local M = { __class = 'unique_array' } local type, ipairs = type, ipairs local getmetatable, setmetatable, rawset = getmetatable, setmetatable, rawset local t_remove, t_sort = table.remove, table.sort local function add(self, class, param) local index = #self + 1 rawset(self, index, param) class.dictionary[param] = index class.len = class.len + 1 return index end local function dictionary_sort(self, class) class.len = 0 for i, v in ipairs(self) do class.len = class.len + 1 class.dictionary[v] = i end end local function remove(self, class, param) t_remove(self, class.dictionary[param]) class.dictionary[param] = nil dictionary_sort(self, class) end local wrappers = {} local function create_class(tab) if type(tab) == 'table' then local class = { __concat = M.concat, __tostring = M.tostring, __eq = M.same, __lt = wrappers.__lt, __add = wrappers.__add, __sub = wrappers.__sub, __lte = wrappers.__lt, len = 0 } class.dictionary = M.make_dictionary(tab) class.__index = function(_, k) return class.dictionary[k] or M[k] end return setmetatable(tab, class) end end local function unique_or_new(tab) if type(tab) == table and tab.__class == 'unique_array' then return tab else return M.new(tab) end end wrappers.__lt = function(lhs, rhs) lhs, rhs = unique_or_new(lhs), unique_or_new(rhs) return rhs:all(lhs) end wrappers.__add = function(lhs, rhs) return M.new(lhs):add(rhs) end wrappers.__sub = function(lhs, rhs) return M.new(lhs):remove(rhs) end wrappers.__pairs = function(self) local dictionary = getmetatable(self).dictionary return next, dictionary, nil end --- Methods -- @section Methods --- Create a new unique array. -- @tparam unique_array|string|{string,...} ... strings to initialize the unique array with -- @treturn @{unique_array} new function M.new(...) return create_class {}:add(type((...)) == 'table' and (...) or { ... }) end --- Add a string to the array if it doesn't exist in the array. -- @tparam unique_array|string|{string,...} other -- @treturn @{unique_array} self function M:add(other) local class = getmetatable(self) for _, param in ipairs(type(other) == 'table' and other or { other }) do if not self[param] then add(self, class, param) end end return self end --- Remove the strings from the array if they exist. -- @tparam unique_array|string|{string,...} other -- @treturn @{unique_array} self function M:remove(other) local class = getmetatable(self) for _, param in ipairs(type(other) == 'table' and other or { other }) do if self[param] then remove(self, class, param) end end return self end --- Toggles the passed name in the array by adding it if not present or removing it if it is. -- @tparam unique_array|string|{string,...} other -- @treturn @{unique_array} self function M:toggle(other) local class = getmetatable(self) for _, param in ipairs(type(other) == 'table' and other or { other }) do if self[param] then remove(self, class, param) else add(self, class, param) end end return self end --- Get all items that are NOT in both arrays. -- @tparam unique_array|string|{string,...} other -- @treturn @{unique_array} new function M:diff(other) other = unique_or_new(other) local diff = M.new() for _, v in ipairs(self) do if not other[v] then diff:add(v) end end for _, v in ipairs(other) do if not self[v] then diff:add(v) end end return diff end --- Get all items that are in both arrays. -- @tparam unique_array|string|{string,...} other -- @treturn @{unique_array} new function M:intersects(other) other = unique_or_new(other) local intersection = M.new() for _, v in ipairs(self) do if other[v] then intersection:add(v) end end for _, v in ipairs(other) do if self[v] and not intersection[v] then intersection:add(v) end end return intersection end --- Sort the unique_array in place. -- @tparam[opt] function cmp Comparator @{sort} function to use -- @treturn @{unique_array} self function M:sort(cmp) local class = getmetatable(self) t_sort(self, cmp) dictionary_sort(self, class) return self end --- Create a new unique_array by concatenating together. -- @tparam unique_array|string|{string,...} other -- @treturn @{unique_array} new function M:concat(other) return M.new(self):add(other) end --- Find all members in a unique array that match the pattern. -- @tparam string pattern Lua @{pattern} -- @treturn @{unique_array} new unique array containing all elements that match. function M:find(pattern) local matches = M.new() for _, value in ipairs(self) do if value:find(pattern) then matches:add(value) end end return matches end --- Clear the array returning an empty array object -- @treturn @{unique_array} self function M:clear() local class = getmetatable(self) for i = #self, 1, -1 do self[i] = nil end class.dictionary = {} class.len = 0 return self end --- Functions -- @section Functions --- Does this array have all of the passed strings. -- @tparam unique_array|string|{string,...} other -- @treturn boolean every passed string is in the array function M:all(other) local params = type(other) == 'table' and other or { other } local count = 0 for _, param in ipairs(params) do if self[param] then count = count + 1 end end return count == #params end M.has = M.all --- Does this array have any of the passed strings. -- @tparam unique_array|string|{string,...} other -- @treturn boolean any passed string is in the array function M:any(other) for _, param in ipairs(type(other) == 'table' and other or { other }) do if self[param] then return true end end return false end --- Does this array have none of the passed strings. -- @tparam unique_array|string|{string,...} other -- @treturn boolean no passed strings are in the array function M:none(other) for _, param in ipairs(type(other) == 'table' and other or { other }) do if self[param] then return false end end return true end --- Do both arrays contain the same items -- @tparam unique_array|string|{string,...} other -- @treturn boolean function M:same(other) other = unique_or_new(other) if #self == #other then for _, value in ipairs(self) do if not other[value] then return false end end return true end return false end --- Do the unique arrays have no items in common -- @tparam unique_array|string|{string,...} other -- @treturn boolean function M:disjointed(other) return #self:intersects(other) == 0 end --- Convert the array to a string. -- @treturn string function M:tostring() return table.concat(self, ', ') end --- Return a dictionary mapping of the array. -- can be passed a function to set the value of the field. -- @tparam[opt] function func value, index are passed as the first two paramaters -- @tparam[opt] any ... additional values to pass to the function -- @treturn dictionary function M:make_dictionary(func, ...) local dict = {} for index, value in ipairs(self) do dict[value] = func and func(value, index, ...) or index end return dict end --- Exports -- @section Exports local function from_dictionary(dict) local array = {} local i = 0 for k in pairs(dict) do i = i + 1 array[i] = k end return create_class(array) end --- These functions are available when requiring this class. -- @table exports local exports = { new = M.new, -- @{unique_array.new} set = create_class, -- set the class on an existing table dictionary = M.make_dictionary, -- @{unique_array.make_dictionary} from_dictionary = from_dictionary } setmetatable( exports, { __call = function(_, ...) return M.new(...) end } ) return exports