Charakterbewegungen hinzugefügt, Deko hinzugefügt, Kochrezepte angepasst
This commit is contained in:
parent
95945c0306
commit
a0c893ca0b
1124 changed files with 64294 additions and 763 deletions
125
mods/futil/data_structures/bitarray.lua
Normal file
125
mods/futil/data_structures/bitarray.lua
Normal file
|
@ -0,0 +1,125 @@
|
|||
-- pack bits into doubles
|
||||
-- this is quite slow compared to how you'd do this in c or something.
|
||||
-- there's https://bitop.luajit.org/api.html but that seems limited to 32 bits. we can use the full 53 bit mantissa.
|
||||
|
||||
local BITS_PER_NUMBER = 53
|
||||
|
||||
local f = string.format
|
||||
|
||||
local m_floor = math.floor
|
||||
local s_byte = string.byte
|
||||
local s_char = string.char
|
||||
|
||||
local BitArray = futil.class1()
|
||||
|
||||
function BitArray:_init(size_or_bitmap)
|
||||
if type(size_or_bitmap) == "number" then
|
||||
local data = {}
|
||||
self._size = size_or_bitmap
|
||||
for i = 1, math.ceil(size_or_bitmap / BITS_PER_NUMBER) do
|
||||
data[i] = 0
|
||||
end
|
||||
self._data = data
|
||||
elseif type(size_or_bitmap) == "table" then
|
||||
if size_or_bitmap.is_a and size_or_bitmap:is_a(BitArray) then
|
||||
self._size = size_or_bitmap._size
|
||||
self._data = table.copy(size_or_bitmap._data)
|
||||
end
|
||||
end
|
||||
if not self._data then
|
||||
error("bitmap must be initialized w/ a size or another bitmap")
|
||||
end
|
||||
end
|
||||
|
||||
function BitArray:__eq(other)
|
||||
if self._size ~= other._size then
|
||||
return false
|
||||
end
|
||||
for i = 1, self._size do
|
||||
if self._data[i] ~= other._data[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function get_bit(n, j)
|
||||
n = n % (2 ^ j)
|
||||
return m_floor(n / (2 ^ (j - 1))) == 1
|
||||
end
|
||||
|
||||
function BitArray:get(k)
|
||||
if type(k) ~= "number" then
|
||||
return nil
|
||||
elseif k <= 0 or k > self._size then
|
||||
return nil
|
||||
end
|
||||
local i = math.ceil(k / BITS_PER_NUMBER)
|
||||
local n = self._data[i]
|
||||
local j = ((k - 1) % BITS_PER_NUMBER) + 1
|
||||
return get_bit(n, j)
|
||||
end
|
||||
|
||||
local function set_bit(n, j, v)
|
||||
local current = get_bit(n, j)
|
||||
if current == v then
|
||||
return n
|
||||
elseif v then
|
||||
return n + (2 ^ (j - 1))
|
||||
else
|
||||
return n - (2 ^ (j - 1))
|
||||
end
|
||||
end
|
||||
|
||||
function BitArray:set(k, v)
|
||||
if type(v) == "number" then
|
||||
if v < 0 or v > 1 then
|
||||
error(f("invalid argument %s", v))
|
||||
end
|
||||
v = v == 1
|
||||
elseif type(v) ~= "boolean" then
|
||||
error(f("invalid argument of type %s", type(v)))
|
||||
end
|
||||
local i = math.ceil(k / BITS_PER_NUMBER)
|
||||
local n = self._data[i]
|
||||
local j = ((k - 1) % BITS_PER_NUMBER) + 1
|
||||
self._data[i] = set_bit(n, j, v)
|
||||
end
|
||||
|
||||
function BitArray:serialize()
|
||||
local data = self._data
|
||||
local parts = {}
|
||||
for i = 1, #data do
|
||||
local datum = data[i]
|
||||
parts[i] = s_char(datum % 256)
|
||||
.. s_char(m_floor(datum / 256) % 256)
|
||||
.. s_char(m_floor(datum / (256 ^ 2)) % 256)
|
||||
.. s_char(m_floor(datum / (256 ^ 3)) % 256)
|
||||
.. s_char(m_floor(datum / (256 ^ 4)) % 256)
|
||||
.. s_char(m_floor(datum / (256 ^ 5)) % 256)
|
||||
.. s_char(m_floor(datum / (256 ^ 6)) % 256)
|
||||
end
|
||||
return table.concat(parts, "")
|
||||
end
|
||||
|
||||
function BitArray.deserialize(s)
|
||||
if type(s) ~= "string" then
|
||||
error(f("invalid argument of type %s", type(s)))
|
||||
elseif #s % 7 ~= 0 then
|
||||
error(f("invalid serialized string (wrong length)"))
|
||||
end
|
||||
local ba = BitArray(#s / 7)
|
||||
local i = 1
|
||||
for a = 1, #s, 7 do
|
||||
local bs = {}
|
||||
for j = 0, 6 do
|
||||
bs[j + 1] = s_byte(s:sub(a + j, a + j))
|
||||
end
|
||||
ba._data[i] = bs[1]
|
||||
+ 256 * (bs[2] + 256 * (bs[3] + 256 * (bs[4] + 256 * (bs[5] + 256 * (bs[6] + 256 * bs[7])))))
|
||||
i = i + 1
|
||||
end
|
||||
return ba
|
||||
end
|
||||
|
||||
futil.BitArray = BitArray
|
11
mods/futil/data_structures/default_table.lua
Normal file
11
mods/futil/data_structures/default_table.lua
Normal file
|
@ -0,0 +1,11 @@
|
|||
-- https://www.lua.org/pil/13.4.3.html
|
||||
|
||||
function futil.DefaultTable(initializer)
|
||||
return setmetatable({}, {
|
||||
__index = function(t, k)
|
||||
local v = initializer(k)
|
||||
t[k] = v
|
||||
return v
|
||||
end,
|
||||
})
|
||||
end
|
88
mods/futil/data_structures/deque.lua
Normal file
88
mods/futil/data_structures/deque.lua
Normal file
|
@ -0,0 +1,88 @@
|
|||
-- inspired by https://www.lua.org/pil/11.4.html
|
||||
|
||||
local Deque = futil.class1()
|
||||
|
||||
function Deque:_init(def)
|
||||
self._a = 0
|
||||
self._z = -1
|
||||
self._m = def and def.max_size
|
||||
end
|
||||
|
||||
function Deque:size()
|
||||
return self._z - self._a + 1
|
||||
end
|
||||
|
||||
function Deque:push_front(value)
|
||||
local max_size = self._m
|
||||
if max_size and (self._z - self._a + 1) >= max_size then
|
||||
return false
|
||||
end
|
||||
local a = self._a - 1
|
||||
self._a = a
|
||||
self[a] = value
|
||||
return true, a
|
||||
end
|
||||
|
||||
function Deque:peek_front()
|
||||
return self[self._a]
|
||||
end
|
||||
|
||||
function Deque:pop_front()
|
||||
local a = self._a
|
||||
if a > self._z then
|
||||
return nil
|
||||
end
|
||||
local value = self[a]
|
||||
self[a] = nil
|
||||
self._a = a + 1
|
||||
return value
|
||||
end
|
||||
|
||||
function Deque:push_back(value)
|
||||
local max_size = self._m
|
||||
if max_size and (self._z - self._a + 1) >= max_size then
|
||||
return false
|
||||
end
|
||||
local z = self._z + 1
|
||||
self._z = z
|
||||
self[z] = value
|
||||
return true, z
|
||||
end
|
||||
|
||||
function Deque:peek_back()
|
||||
return self[self._z]
|
||||
end
|
||||
|
||||
function Deque:pop_back()
|
||||
local z = self._z
|
||||
if self._a > z then
|
||||
return nil
|
||||
end
|
||||
local value = self[z]
|
||||
self[z] = nil
|
||||
self._z = z + 1
|
||||
return value
|
||||
end
|
||||
|
||||
-- this iterator is kinda wonky, and the behavior may be changed in the future.
|
||||
-- unexpected behavior may result from modifying a deque *while* iterating it.
|
||||
-- note that you *cannot* iterate the deque directly using `pairs()` because of e.g. "_a" and "_z"
|
||||
function Deque:iterate()
|
||||
local i = self._a - 1
|
||||
return function()
|
||||
i = i + 1
|
||||
return self[i]
|
||||
end
|
||||
end
|
||||
|
||||
function Deque:clear()
|
||||
for k in pairs(self) do
|
||||
if type(k) == "number" then
|
||||
self[k] = nil
|
||||
end
|
||||
end
|
||||
self._a = 0
|
||||
self._z = -1
|
||||
end
|
||||
|
||||
futil.Deque = Deque
|
7
mods/futil/data_structures/init.lua
Normal file
7
mods/futil/data_structures/init.lua
Normal file
|
@ -0,0 +1,7 @@
|
|||
futil.dofile("data_structures", "bitarray")
|
||||
futil.dofile("data_structures", "default_table")
|
||||
futil.dofile("data_structures", "deque")
|
||||
futil.dofile("data_structures", "pairing_heap")
|
||||
futil.dofile("data_structures", "point_search_tree")
|
||||
futil.dofile("data_structures", "set")
|
||||
futil.dofile("data_structures", "sparse_graph") -- requires default_table and set
|
156
mods/futil/data_structures/pairing_heap.lua
Normal file
156
mods/futil/data_structures/pairing_heap.lua
Normal file
|
@ -0,0 +1,156 @@
|
|||
-- https://en.wikipedia.org/wiki/Pairing_heap
|
||||
-- https://www.cs.cmu.edu/~sleator/papers/pairing-heaps.pdf
|
||||
-- https://www.cise.ufl.edu/~sahni/dsaaj/enrich/c13/pairing.htm
|
||||
|
||||
local inf = math.huge
|
||||
|
||||
local function add_child(node1, node2)
|
||||
node2.parent = node1
|
||||
node2.sibling = node1.child
|
||||
node1.child = node2
|
||||
end
|
||||
|
||||
local function meld(node1, node2)
|
||||
if node1 == nil or node1.value == nil then
|
||||
return node2
|
||||
elseif node2 == nil or node2.value == nil then
|
||||
return node1
|
||||
elseif node1.priority > node2.priority then
|
||||
add_child(node1, node2)
|
||||
return node1
|
||||
else
|
||||
add_child(node2, node1)
|
||||
return node2
|
||||
end
|
||||
end
|
||||
|
||||
local function merge_pairs(node)
|
||||
if node.value == nil or not node.sibling then
|
||||
return
|
||||
end
|
||||
|
||||
local sibling = node.sibling
|
||||
local siblingsibling = sibling.sibling
|
||||
|
||||
node.sibling = nil
|
||||
sibling.sibling = nil
|
||||
|
||||
node = meld(node, sibling)
|
||||
|
||||
if siblingsibling then
|
||||
return meld(node, merge_pairs(siblingsibling))
|
||||
else
|
||||
return node
|
||||
end
|
||||
end
|
||||
|
||||
local function cut(node)
|
||||
local parent = node.parent
|
||||
|
||||
if parent.child == node then
|
||||
parent.child = node.sibling
|
||||
else
|
||||
parent = parent.child
|
||||
local sibling = parent.sibling
|
||||
while sibling ~= node do
|
||||
parent = sibling
|
||||
sibling = parent.sibling
|
||||
end
|
||||
parent.sibling = node.sibling
|
||||
end
|
||||
|
||||
node.parent = nil
|
||||
node.sibling = nil
|
||||
end
|
||||
|
||||
local function need_to_move(node, new_priority)
|
||||
local cur_priority = node.priority
|
||||
if cur_priority < new_priority then
|
||||
-- priority increase, make sure we don't dominate our parent
|
||||
local parent = node.parent
|
||||
return (parent and new_priority > parent.priority)
|
||||
elseif cur_priority > new_priority then
|
||||
-- priority decrease, make sure our children don't dominate us
|
||||
local child = node.child
|
||||
while child and child ~= node do
|
||||
if child.priority > new_priority then
|
||||
return true
|
||||
end
|
||||
child = child.sibling
|
||||
end
|
||||
return false
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local PairingHeap = futil.class1()
|
||||
|
||||
function PairingHeap:_new()
|
||||
self._nodes_by_value = {}
|
||||
self._size = 0
|
||||
end
|
||||
|
||||
function PairingHeap:size()
|
||||
return self._size
|
||||
end
|
||||
|
||||
function PairingHeap:peek()
|
||||
local hn = self._max_node
|
||||
|
||||
if not hn then
|
||||
error("empty")
|
||||
end
|
||||
|
||||
return hn.value, hn.priority
|
||||
end
|
||||
|
||||
function PairingHeap:remove(value)
|
||||
self:set_priority(value, inf)
|
||||
return self:pop()
|
||||
end
|
||||
|
||||
function PairingHeap:pop()
|
||||
local max = self._max_node
|
||||
|
||||
if not max then
|
||||
error("empty")
|
||||
end
|
||||
|
||||
local child = max.child
|
||||
if child then
|
||||
self._max_node = merge_pairs(child)
|
||||
end
|
||||
|
||||
local value = max._value
|
||||
|
||||
self._nodes_by_value[value] = nil
|
||||
self._size = self._size - 1
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
function PairingHeap:get_priority(value)
|
||||
return self._nodes_by_value[value].priority
|
||||
end
|
||||
|
||||
function PairingHeap:set_priority(value, priority)
|
||||
local cur_node = self._nodes_by_value[value]
|
||||
if cur_node then
|
||||
local need_to = need_to_move(cur_node, priority)
|
||||
|
||||
if need_to then
|
||||
cut(cur_node)
|
||||
self._max_node = meld(cur_node, self._max_node)
|
||||
else
|
||||
cur_node.priority = priority
|
||||
end
|
||||
else
|
||||
local node = { value = value, priority = priority }
|
||||
self._nodes_by_value[value] = node
|
||||
self._max_node = meld(self._max_node, node)
|
||||
self._size = self._size + 1
|
||||
end
|
||||
end
|
||||
|
||||
futil.PairingHeap = PairingHeap
|
268
mods/futil/data_structures/point_search_tree.lua
Normal file
268
mods/futil/data_structures/point_search_tree.lua
Normal file
|
@ -0,0 +1,268 @@
|
|||
--[[
|
||||
a data structure which can efficiently retrieve values within specific rectangular regions of 3d space.
|
||||
|
||||
the closest relevant descriptions of this problem and solution are in the following:
|
||||
https://en.wikipedia.org/wiki/Min/max_kd-tree
|
||||
https://medium.com/omarelgabrys-blog/geometric-applications-of-bsts-e58f0a5019f3
|
||||
|
||||
creation is O(n log n)
|
||||
finding objects in a region is O(m + log n) if there's m objects in the region.
|
||||
|
||||
the hope here is that this will provide a faster alternative to `minetest.get_objects_in_area()`. that currently
|
||||
iterates over *all* active objects in the world, which can be slow when there's ten thousand or more of objects in the
|
||||
world, and you are only interested in a few of them. in particular, the your-land server usually has 5-8 thousand
|
||||
objects loaded at once, and can have hundreds of mobs calling `get_objects_in_area` every couple server steps. perftop
|
||||
definitively implicated this routine as being a major source of lag. the question, now, is what to do about it.
|
||||
|
||||
the current implementation doesn't allow for insertion, deletion, or changes in location. if your points move around,
|
||||
you're gonna have to rebuild the whole tree from scratch, which currently limits the usefulness.
|
||||
|
||||
TODO: read this and incorporate if applicable: https://arxiv.org/abs/1410.5420
|
||||
|
||||
== footnotes about the algorithms ==
|
||||
currently, we're hard-coding the usage of the [median of medians](https://en.wikipedia.org/wiki/Median_of_medians)
|
||||
algorithm as the pivot strategy, as this resulted in unexpectedly dramatic improvements over random selection in
|
||||
some informal performance tests i did.
|
||||
|
||||
== footnotes about results ==
|
||||
currently, performance can range from taking 1/20th of the time of the engine call, to 100 times as long. this
|
||||
makes me realize that this is worth pursuing, but probably this will need to be ported to c++ to consistently provide
|
||||
a benefit. but, i'll absolutely need to figure out a self-balancing strategy before that'll be appropriate.
|
||||
]]
|
||||
local sort = table.sort
|
||||
|
||||
local in_area = vector.in_area
|
||||
or function(pos, pmin, pmax)
|
||||
local x, y, z = pos.x, pos.y, pos.z
|
||||
return pmin.x <= x and x <= pmax.x and pmin.y <= y and y <= pmax.y and pmin.z <= z and z <= pmax.z
|
||||
end
|
||||
|
||||
local axes = { "x", "y", "z" }
|
||||
local POS = 1
|
||||
local VALUE = 2
|
||||
|
||||
local Leaf = futil.class1()
|
||||
|
||||
function Leaf:_init(pos_and_value)
|
||||
self[POS] = pos_and_value[POS]
|
||||
self[VALUE] = pos_and_value[VALUE]
|
||||
end
|
||||
|
||||
local Node = futil.class1()
|
||||
|
||||
function Node:_init(min, max, left, right)
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.left = left
|
||||
self.right = right
|
||||
end
|
||||
|
||||
local PointSearchTree = futil.class1()
|
||||
|
||||
futil.min_median_max = {}
|
||||
|
||||
function futil.min_median_max.sort(t, indexer)
|
||||
if indexer then
|
||||
sort(t, function(a, b)
|
||||
return indexer(a) < indexer(b)
|
||||
end)
|
||||
else
|
||||
sort(t)
|
||||
end
|
||||
return t[1], math.floor(#t / 2), t[#t]
|
||||
end
|
||||
|
||||
function futil.min_median_max.gen_select(pivot_alg)
|
||||
return function(t, indexer)
|
||||
local median = futil.selection.select(t, pivot_alg, function(a, b)
|
||||
return indexer(a) < indexer(b)
|
||||
end)
|
||||
local min = indexer(t[1])
|
||||
local max = min
|
||||
for i = 2, #t do
|
||||
local v = indexer(t[i])
|
||||
if v < min then
|
||||
min = v
|
||||
elseif v > max then
|
||||
max = v
|
||||
end
|
||||
end
|
||||
return min, median, max
|
||||
end
|
||||
end
|
||||
|
||||
local function bisect(pos_and_values, axis_i, min_median_max)
|
||||
if #pos_and_values == 1 then
|
||||
return Leaf(pos_and_values[1])
|
||||
end
|
||||
|
||||
local axis = axes[axis_i]
|
||||
|
||||
local min, median, max = min_median_max(pos_and_values, function(i)
|
||||
return i[POS][axis]
|
||||
end)
|
||||
|
||||
local next_axis_i = (axis_i % #axes) + 1
|
||||
return Node(
|
||||
min,
|
||||
max,
|
||||
bisect({ unpack(pos_and_values, 1, median) }, next_axis_i, min_median_max),
|
||||
bisect({ unpack(pos_and_values, median + 1) }, next_axis_i, min_median_max)
|
||||
)
|
||||
end
|
||||
|
||||
function PointSearchTree:_init(pos_and_values, min_median_max)
|
||||
pos_and_values = pos_and_values or {}
|
||||
min_median_max = min_median_max or futil.min_median_max.gen_select(futil.selection.pivot.median_of_medians)
|
||||
self._len = #pos_and_values
|
||||
if #pos_and_values > 0 then
|
||||
self._root = bisect(pos_and_values, 1, min_median_max)
|
||||
end
|
||||
end
|
||||
|
||||
-- -DLUAJIT_ENABLE_LUA52COMPAT
|
||||
function PointSearchTree:__len()
|
||||
return self._len
|
||||
end
|
||||
|
||||
function PointSearchTree:dump()
|
||||
local function getlines(node, axis_i)
|
||||
local axis = axes[axis_i]
|
||||
if not node then
|
||||
return {}
|
||||
elseif node:is_a(Leaf) then
|
||||
return { minetest.pos_to_string(node[POS], 1) }
|
||||
else
|
||||
local lines = {}
|
||||
for _, line in ipairs(getlines(node.left, (axis_i % #axes) + 1)) do
|
||||
lines[#lines + 1] = string.format("%s=[%.1f,%.1f] %s", axis, node.min, node.max, line)
|
||||
end
|
||||
for _, line in ipairs(getlines(node.right, (axis_i % #axes) + 1)) do
|
||||
lines[#lines + 1] = string.format("%s=[%.1f,%.1f] %s", axis, node.min, node.max, line)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(getlines(self._root, 1), "\n")
|
||||
end
|
||||
|
||||
local function make_iterator(pmin, pmax, predicate, accumulate)
|
||||
local function iterate(node, axis_i)
|
||||
local next_axis_i = (axis_i % 3) + 1
|
||||
local next_axis = axes[next_axis_i]
|
||||
|
||||
local left = node.left
|
||||
if left then
|
||||
if left:is_a(Leaf) then
|
||||
if predicate(left) then
|
||||
accumulate(left)
|
||||
end
|
||||
elseif pmin[next_axis] <= left.max then
|
||||
iterate(left, next_axis_i)
|
||||
end
|
||||
end
|
||||
|
||||
local right = node.right
|
||||
if right then
|
||||
if right:is_a(Leaf) then
|
||||
if predicate(right) then
|
||||
accumulate(right)
|
||||
end
|
||||
elseif right.min <= pmax[next_axis] then
|
||||
iterate(right, next_axis_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
return iterate
|
||||
end
|
||||
|
||||
function PointSearchTree:iterate_values_in_area(pmin, pmax)
|
||||
if not self._root then
|
||||
return function() end
|
||||
end
|
||||
|
||||
pmin, pmax = vector.sort(pmin, pmax)
|
||||
|
||||
if self._root.max < pmin.x or pmax.x < self._root.min then
|
||||
return function() end
|
||||
end
|
||||
|
||||
return coroutine.wrap(function()
|
||||
make_iterator(pmin, pmax, function(leaf)
|
||||
return in_area(leaf[POS], pmin, pmax)
|
||||
end, function(leaf)
|
||||
coroutine.yield(leaf[POS], leaf[VALUE])
|
||||
end)(self._root, 1)
|
||||
end)
|
||||
end
|
||||
|
||||
function PointSearchTree:get_values_in_area(pmin, pmax)
|
||||
local via = {}
|
||||
if not self._root then
|
||||
return via
|
||||
end
|
||||
|
||||
pmin, pmax = vector.sort(pmin, pmax)
|
||||
|
||||
if self._root.max < pmin.x or pmax.x < self._root.min then
|
||||
return via
|
||||
end
|
||||
|
||||
make_iterator(pmin, pmax, function(leaf)
|
||||
return in_area(leaf[POS], pmin, pmax)
|
||||
end, function(leaf)
|
||||
via[#via + 1] = leaf[VALUE]
|
||||
end)(self._root, 1)
|
||||
|
||||
return via
|
||||
end
|
||||
|
||||
function PointSearchTree:iterate_values_inside_radius(center, radius)
|
||||
if not self._root then
|
||||
return function() end
|
||||
end
|
||||
|
||||
local pmin = vector.subtract(center, radius)
|
||||
local pmax = vector.add(center, radius)
|
||||
|
||||
if self._root.max < pmin.x or pmax.x < self._root.min then
|
||||
return function() end
|
||||
end
|
||||
|
||||
local v_distance = vector.distance
|
||||
|
||||
return coroutine.wrap(function()
|
||||
make_iterator(pmin, pmax, function(leaf)
|
||||
return v_distance(center, leaf[POS]) <= radius
|
||||
end, function(leaf)
|
||||
coroutine.yield(leaf[POS], leaf[VALUE])
|
||||
end)(self._root, 1)
|
||||
end)
|
||||
end
|
||||
|
||||
function PointSearchTree:get_values_inside_radius(center, radius)
|
||||
local vir = {}
|
||||
if not self._root then
|
||||
return vir
|
||||
end
|
||||
|
||||
local pmin = vector.subtract(center, radius)
|
||||
local pmax = vector.add(center, radius)
|
||||
|
||||
if self._root.max < pmin.x or pmax.x < self._root.min then
|
||||
return vir
|
||||
end
|
||||
|
||||
local v_distance = vector.distance
|
||||
|
||||
make_iterator(pmin, pmax, function(leaf)
|
||||
return v_distance(center, leaf[POS]) <= radius
|
||||
end, function(leaf)
|
||||
vir[#vir + 1] = leaf[VALUE]
|
||||
end)(self._root, 1)
|
||||
|
||||
return vir
|
||||
end
|
||||
|
||||
futil.PointSearchTree = PointSearchTree
|
267
mods/futil/data_structures/set.lua
Normal file
267
mods/futil/data_structures/set.lua
Normal file
|
@ -0,0 +1,267 @@
|
|||
-- based more-or-less on python's set
|
||||
|
||||
local f = string.format
|
||||
|
||||
local Set = futil.class1()
|
||||
|
||||
local function is_a_set(thing)
|
||||
return type(thing) == "table" and type(thing.is_a) == "function" and thing:is_a(Set)
|
||||
end
|
||||
|
||||
function Set:_init(t_or_i)
|
||||
self._size = 0
|
||||
self._set = {}
|
||||
if t_or_i then
|
||||
if type(t_or_i.is_a) == "function" then
|
||||
if t_or_i:is_a(Set) then
|
||||
self._set = table.copy(t_or_i._set)
|
||||
self._size = t_or_i._size
|
||||
else
|
||||
for v in t_or_i:iterate() do
|
||||
self:add(v)
|
||||
end
|
||||
end
|
||||
elseif type(t_or_i) == "table" then
|
||||
for i = 1, #t_or_i do
|
||||
self:add(t_or_i[i])
|
||||
end
|
||||
elseif type(t_or_i) == "function" or getmetatable(t_or_i).__call then
|
||||
for v in t_or_i do
|
||||
self:add(v)
|
||||
end
|
||||
else
|
||||
error(f("unknown argument of type %s", type(t_or_i)))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- turn a table like {foo=true, bar=true} into a Set
|
||||
function Set.convert(t)
|
||||
local set = Set()
|
||||
set._set = t
|
||||
set._size = futil.table.size(t)
|
||||
return set
|
||||
end
|
||||
|
||||
-- -DLUAJIT_ENABLE_LUA52COMPAT
|
||||
function Set:__len()
|
||||
return self._size
|
||||
end
|
||||
|
||||
function Set:len()
|
||||
return self._size
|
||||
end
|
||||
|
||||
function Set:size()
|
||||
return self._size
|
||||
end
|
||||
|
||||
function Set:is_empty()
|
||||
return self._size == 0
|
||||
end
|
||||
|
||||
function Set:__tostring()
|
||||
local elements = {}
|
||||
for element in pairs(self._set) do
|
||||
elements[#elements + 1] = f("%q", element)
|
||||
end
|
||||
return f("Set({%s})", table.concat(elements, ", "))
|
||||
end
|
||||
|
||||
function Set:__eq(other)
|
||||
if not is_a_set(other) then
|
||||
return false
|
||||
end
|
||||
for k in pairs(self._set) do
|
||||
if not other._set[k] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return self._size == other._size
|
||||
end
|
||||
|
||||
function Set:contains(element)
|
||||
return self._set[element] == true
|
||||
end
|
||||
|
||||
function Set:add(element)
|
||||
if not self:contains(element) then
|
||||
self._set[element] = true
|
||||
self._size = self._size + 1
|
||||
end
|
||||
end
|
||||
|
||||
function Set:remove(element)
|
||||
if not self:contains(element) then
|
||||
error(f("set does not contain %s", element))
|
||||
end
|
||||
self._set[element] = nil
|
||||
self._size = self._size - 1
|
||||
end
|
||||
|
||||
function Set:discard(element)
|
||||
if self:contains(element) then
|
||||
self._set[element] = nil
|
||||
self._size = self._size - 1
|
||||
end
|
||||
end
|
||||
|
||||
function Set:clear()
|
||||
self._set = {}
|
||||
self._size = 0
|
||||
end
|
||||
|
||||
function Set:iterate()
|
||||
return futil.table.ikeys(self._set)
|
||||
end
|
||||
|
||||
function Set:intersects(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
local smaller, bigger
|
||||
if other:size() < self:size() then
|
||||
smaller = other
|
||||
bigger = self
|
||||
else
|
||||
smaller = self
|
||||
bigger = other
|
||||
end
|
||||
for element in smaller:iterate() do
|
||||
if bigger:contains(element) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Set:isdisjoint(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
local smaller, bigger
|
||||
if other:size() < self:size() then
|
||||
smaller = other
|
||||
bigger = self
|
||||
else
|
||||
smaller = self
|
||||
bigger = other
|
||||
end
|
||||
for element in smaller:iterate() do
|
||||
if bigger:contains(element) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Set:issubset(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
if self:size() > other:size() then
|
||||
return false
|
||||
end
|
||||
for element in self:iterate() do
|
||||
if not other:contains(element) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Set:__le(other)
|
||||
return self:issubset(other)
|
||||
end
|
||||
|
||||
function Set:__lt(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
if self:size() >= other:size() then
|
||||
return false
|
||||
end
|
||||
for element in self:iterate() do
|
||||
if not other:contains(element) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Set:issuperset(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
return other:issubset(self)
|
||||
end
|
||||
|
||||
function Set:update(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
for element in other:iterate() do
|
||||
self:add(element)
|
||||
end
|
||||
end
|
||||
|
||||
function Set:union(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
local union = Set(self)
|
||||
union:update(other)
|
||||
return union
|
||||
end
|
||||
|
||||
function Set:__add(other)
|
||||
return self:union(other)
|
||||
end
|
||||
|
||||
function Set:intersection_update(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
for element in self:iterate() do
|
||||
if not other:contains(element) then
|
||||
self:remove(element)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Set:intersection(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
local intersection = Set()
|
||||
for element in self:iterate() do
|
||||
if other:contains(element) then
|
||||
intersection:add(element)
|
||||
end
|
||||
end
|
||||
return intersection
|
||||
end
|
||||
|
||||
function Set:difference_update(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
for element in other:iterate() do
|
||||
self:discard(element)
|
||||
end
|
||||
end
|
||||
|
||||
function Set:difference(other)
|
||||
if not is_a_set(other) then
|
||||
other = Set(other)
|
||||
end
|
||||
local difference = Set(self)
|
||||
difference:difference_update(other)
|
||||
return difference
|
||||
end
|
||||
|
||||
function Set:__sub(other)
|
||||
return self:difference(other)
|
||||
end
|
||||
|
||||
futil.Set = Set
|
32
mods/futil/data_structures/sparse_graph.lua
Normal file
32
mods/futil/data_structures/sparse_graph.lua
Normal file
|
@ -0,0 +1,32 @@
|
|||
local f = string.format
|
||||
|
||||
local SparseGraph = futil.class1()
|
||||
|
||||
function SparseGraph:_init(size)
|
||||
self._size = size or 0
|
||||
self._adj_by_vertex = futil.DefaultTable(function()
|
||||
return futil.Set()
|
||||
end)
|
||||
end
|
||||
|
||||
function SparseGraph:size()
|
||||
return self._size
|
||||
end
|
||||
|
||||
function SparseGraph:add_vertex()
|
||||
self._size = self._size + 1
|
||||
end
|
||||
|
||||
function SparseGraph:add_edge(a, b)
|
||||
assert(1 <= a and a <= self._size, f("invalid vertex a %s", a))
|
||||
assert(1 <= b and b <= self._size, f("invalid vertex b %s", b))
|
||||
self._adj_by_vertex[a]:add(b)
|
||||
end
|
||||
|
||||
function SparseGraph:has_edge(a, b)
|
||||
assert(1 <= a and a <= self._size, f("invalid vertex a %s", a))
|
||||
assert(1 <= b and b <= self._size, f("invalid vertex b %s", b))
|
||||
return self._adj_by_vertex[a]:contains(b)
|
||||
end
|
||||
|
||||
futil.SparseGraph = SparseGraph
|
Loading…
Add table
Add a link
Reference in a new issue