batteries/set.lua

181 lines
4.1 KiB
Lua
Raw Permalink Normal View History

--[[
set type with appropriate operations
Add comments for things that surprised me As a new user, there were things I was skeptical about and after digging in, these were my conclusions. Compared to the simple and obvious lua wiki solutions, batteries' string functions are slightly faster. GC is the same. Test local str = "hello world" local fn = function() local x = 0 if stringx.ends_with(str, "h") then x = x + 1 end if stringx.ends_with(str, "helll") then x = x + 1 end if stringx.ends_with(str, "helicopter") then x = x + 1 end end local pretty = require "inspect" print("stringx =", pretty({ time_taken = {measure.time_taken(fn, 10000)}, memory_taken = {measure.memory_taken(fn, 10000)} })) local function starts_with(str, prefix) return str:find(prefix, 1, true) == 1 end local function ends_with(str, ending) return ending == "" or str:sub(-#ending) == ending end local fn = function() local x = 0 if ends_with(str, "h") then x = x + 1 end if ends_with(str, "helll") then x = x + 1 end if ends_with(str, "helicopter") then x = x + 1 end end print("find =", pretty({ time_taken = {measure.time_taken(fn, 10000)}, memory_taken = {measure.memory_taken(fn, 10000)} })) starts_with =========== stringx = { memory_taken = { 0, 0, 0 }, time_taken = { 1.5098012518138e-007, 9.988434612751e-008, 2.1699932403862e-005 } } find = { memory_taken = { 0, 0, 0 }, time_taken = { 2.7349997544661e-007, 1.9988510757685e-007, 9.1999536380172e-006 } } ends_with ========= stringx = { memory_taken = { 0, 0, 0 }, time_taken = { 9.0479978825897e-008, 0, 2.5199959054589e-005 } } find = { memory_taken = { 0, 0, 0 }, time_taken = { 2.1833006758243e-007, 1.9988510757685e-007, 6.1000464484096e-006 } }
2022-03-02 18:30:52 +00:00
NOTE: This is actually a unique list (ordered set). So it's more than just
a table with keys for values.
]]
local path = (...):gsub("set", "")
local class = require(path .. "class")
local table = require(path .. "tablex") --shadow global table module
local set = class({
name = "set",
})
--construct a new set
--elements is an optional ordered table of elements to be added to the set
function set:new(elements)
self._keyed = {}
self._ordered = {}
if elements then
for _, v in ipairs(elements) do
self:add(v)
end
end
end
--check if an element is present in the set
function set:has(v)
return self._keyed[v] or false
end
--add a value to the set, if it's not already present
function set:add(v)
if not self:has(v) then
self._keyed[v] = true
table.insert(self._ordered, v)
end
return self
end
--remove a value from the set, if it's present
function set:remove(v)
if self:has(v) then
self._keyed[v] = nil
table.remove_value(self._ordered, v)
end
return self
end
--remove all elements from the set
function set:clear()
if table.clear then
table.clear(self._keyed)
table.clear(self._ordered)
else
self._keyed = {}
self._ordered = {}
end
end
2020-08-29 11:12:56 +00:00
--get the number of distinct values in the set
function set:size()
return #self._ordered
end
--return a value from the set
--index must be between 1 and size() inclusive
--adding/removing invalidates indices
function set:get(index)
return self._ordered[index]
end
--iterate the values in the set, along with their index
--the index is useless but harmless, and adding a custom iterator seems
--like a really easy way to encourage people to use slower-than-optimal code
function set:ipairs()
return ipairs(self._ordered)
end
--get a copy of the values in the set, as a simple table
function set:values()
return table.shallow_copy(self._ordered)
end
--get a direct reference to the internal list of values in the set
--do NOT modify the result, or you'll break the set!
--for read-only access it avoids a needless table copy
--(eg this is sensible to pass to functional apis)
function set:values_readonly()
return self._ordered
end
--convert to an ordered table, destroying set-like properties
--and deliberately disabling the initial set object
function set:to_table()
local r = self._ordered
self._ordered = nil
self._keyed = nil
return r
end
--modifying operations
--add all the elements present in the other set
function set:add_set(other)
for i, v in other:ipairs() do
self:add(v)
end
return self
end
--remove all the elements present in the other set
function set:subtract_set(other)
for i, v in other:ipairs() do
self:remove(v)
end
return self
end
--new collection operations
--copy a set
function set:copy()
return set():add_set(self)
end
--create a new set containing the complement of the other set contained in this one
--the elements present in this set but not present in the other set will remain in the result
function set:complement(other)
return self:copy():subtract_set(other)
end
--alias
set.difference = set.complement
--create a new set containing the union of this set with another
--an element present in either set will be present in the result
function set:union(other)
return self:copy():add_set(other)
end
--create a new set containing the intersection of this set with another
--only the elements present in both sets will remain in the result
function set:intersection(other)
local r = set()
for i, v in self:ipairs() do
if other:has(v) then
r:add(v)
end
end
return r
end
--create a new set containing the symmetric difference of this set with another
--only the elements not present in both sets will remain in the result
--similiar to a logical XOR operation
--
--equal to self:union(other):subtract_set(self:intersection(other))
-- but with much less wasted effort
function set:symmetric_difference(other)
local r = set()
for i, v in self:ipairs() do
if not other:has(v) then
r:add(v)
end
end
for i, v in other:ipairs() do
if not self:has(v) then
r:add(v)
end
end
return r
end
--alias
set.xor = set.symmetric_difference
--
return set