2020-05-02 09:59:02 +00:00
|
|
|
--[[
|
|
|
|
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.
|
2020-05-02 09:59:02 +00:00
|
|
|
]]
|
|
|
|
|
|
|
|
local path = (...):gsub("set", "")
|
|
|
|
local class = require(path .. "class")
|
|
|
|
local table = require(path .. "tablex") --shadow global table module
|
|
|
|
|
2021-07-15 06:09:08 +00:00
|
|
|
local set = class({
|
|
|
|
name = "set",
|
|
|
|
})
|
2020-05-02 09:59:02 +00:00
|
|
|
|
|
|
|
--construct a new set
|
|
|
|
--elements is an optional ordered table of elements to be added to the set
|
|
|
|
function set:new(elements)
|
2021-07-15 06:09:08 +00:00
|
|
|
self._keyed = {}
|
|
|
|
self._ordered = {}
|
2020-05-02 09:59:02 +00:00
|
|
|
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
|
|
|
|
|
2021-05-04 00:38:07 +00:00
|
|
|
--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
|
|
|
|
|
2020-05-02 09:59:02 +00:00
|
|
|
--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()
|
2022-03-07 00:59:23 +00:00
|
|
|
return table.shallow_copy(self._ordered)
|
2020-05-02 09:59:02 +00:00
|
|
|
end
|
|
|
|
|
2020-05-02 10:12:59 +00:00
|
|
|
--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
|
|
|
|
|
2020-08-24 10:54:24 +00:00
|
|
|
--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
|
|
|
|
|
2020-05-02 09:59:02 +00:00
|
|
|
--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()
|
2021-07-15 06:09:08 +00:00
|
|
|
return set():add_set(self)
|
2020-05-02 09:59:02 +00:00
|
|
|
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)
|
2021-07-15 06:09:08 +00:00
|
|
|
local r = set()
|
2020-05-02 09:59:02 +00:00
|
|
|
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)
|
2021-07-15 06:09:08 +00:00
|
|
|
local r = set()
|
2020-05-02 09:59:02 +00:00
|
|
|
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
|