Module:Sandbox/Alexiscoutinho/Lua set

Module:Sandbox/Alexiscoutinho/Lua set

require('Module:Lua class')
local libraryUtil = require('libraryUtil')
local TableTools = require('Module:TableTools')
local warn = require('Module:Warning')

local basic_types = {boolean=1, string=1, number=1, ['function']=1}


local f_hashes = {} -- so that function elements can be properly compared and ordered in frozenset._hash
local f_hashes_mt = {
	__index = function (t, key)
		local h = tonumber('0x' .. mw.hash.hashValue('fnv1a32', tostring(os.time() + math.random())))
		f_hashes[key] = h
		return h
	end,
	__mode = 'k'
}
setmetatable(f_hashes, f_hashes_mt)


local frozenset, _frozenset = class('frozenset', {
	__init = function (self, args)
		local elements = {}
		self._elements = elements
		self._elements_obj = {}

		if #args == 0 then -- for performance
			return
		elseif #args == 1 then
			local arg = args[1]
			if type(arg) == 'string' then
				local c
				for i = 1, mw.ustring.len(arg) do
					c = mw.ustring.sub(arg, i,i)
					elements[c] = c
				end
				return
			elseif pcall(pairs, arg) or pcall(ipairs, arg) then
				args = arg
			end
		end

		if TableTools.isArrayLike(args) then
			for i, v in ipairs(args) do
				if type(v) == 'table' or isinstance(v) and v.hash == nil then
					error(("TypeError: invalid element #%d type (got %s, which is not hashable)"):format(i, type(v)), 3)
				end
				self._set(v)
			end
		else
			for k in pairs(args) do
				if type(k) == 'table' or isinstance(k) and k.hash == nil then
					error(("TypeError: invalid element type (got %s, which is not hashable)"):format(type(k)), 3)
				end
				self._set(k)
			end
		end
	end,

	_get = function (self, elem)
		if basic_types[type(elem)] then
			return self._elements[elem]
		else
			local elements_obj = self._elements_obj
			local h = elem.hash()

			while elements_obj[h] ~= nil do
				if elements_obj[h] == elem then
					return elem
				end
				h = h + 1
			end
		end
	end,

	_set = function (self, elem)
		if basic_types[type(elem)] then
			self._elements[elem] = elem
		else
			local elements_obj = self._elements_obj
			local h = elem.hash()

			while elements_obj[h] ~= nil do
				if elements_obj[h] == elem then
					return
				end
				h = h + 1
			end
			elements_obj[h] = elem -- otherwise different objects with the same content would duplicate
		end
	end,

	__pairs = function (self)
		local elems = self._elements
		local k, v

		local function iterator()
			k, v = next(elems, k)
			if k == nil and elems == self._elements then
				elems = self._elements_obj
				k, v = next(elems, k)
			end
			return v -- nil at the very end
		end

		return iterator
	end,

	__ipairs = function (self)
		error("IterationError: a set is unordered, use 'pairs' instead", 2)
	end,

	_keySort = function (item1, item2)
		-- "number" < "string", so numbers will be sorted before strings.
		local type1, type2 = type(item1), type(item2)
		if type1 ~= type2 then
			return type1 < type2
		elseif type1 == 'number' or type1 == 'string' then
			return item1 < item2
		elseif type1 == 'boolean' then
			return tostring(item1) < tostring(item2)
		else
			local hash1, hash2
			if type1 == 'function' then
				hash1, hash2 = f_hashes[item1], f_hashes[item2]
				if hash1 == hash2 then
					warn(("HashWarning: function hash collision at %d"):format(hash1), 2)--what should be the level?
				end
			else
				hash1, hash2 = item1.hash(), item2.hash()
				if hash1 == hash2 then
					warn(("HashWarning: object hash collision at %d"):format(hash1), 2)
				end
			end
			return hash1 < hash2
		end
	end,

	_hash = function (self)
		if self.__hash ~= nil then
			return self.__hash
		end

		-- frozensets with the same elements (meaning equal) may have a different order, so 'order' them before hashing
		local ordered_elems = TableTools.keysToList(self, self._keySort, true)

		-- convert elements to strings for table.concat
		local elemType
		for i, elem in ipairs(ordered_elems) do
			elemType = type(elem)
			if elemType == 'number' or elemType == 'boolean' then
				ordered_elems[i] = tostring(elem)
			elseif elemType == 'string' then
				ordered_elems[i] = "'" .. elem .. "'"
			elseif elemType == 'function' then
				ordered_elems[i] = 'f' .. f_hashes[elem]
			else
				ordered_elems[i] = 'o' .. elem.hash()
			end
		end

		local str = '{' .. table.concat(ordered_elems, ',') .. '}' -- wrap in {} to differentiate from tuple
		self.__hash = tonumber('0x' .. mw.hash.hashValue('fnv1a32', str))
		return self.__hash
	end,

	__tostring = function (self)
		local string_elems = {}
		local elemType

		for elem in pairs(self) do
			elemType = type(elem)
			if elemType == 'string' then
				string_elems[#string_elems+1] = "'" .. elem .. "'"
			elseif elemType == 'function' then
				string_elems[#string_elems+1] = 'f' .. f_hashes[elem]
			else
				string_elems[#string_elems+1] = tostring(elem)
			end
		end

		local str = '{' .. table.concat(string_elems, ', ') .. '}'
		return str
	end,

	len = function (self)
		return TableTools.size(self._elements) + TableTools.size(self._elements_obj)
	end,

	has = function (self, elem)
		if isinstance(elem, 'set') then
			elem = frozenset{elem}
		elseif type(elem) == 'table' or isinstance(elem) and elem.hash == nil then
			error(("TypeError: invalid element type (got %s, which is not hashable)"):format(type(elem)), 2)
		end
		return self._get(elem) ~= nil and true or false
	end,

	isdisjoint = function (self, other)
		libraryUtil.checkTypeMulti('isdisjoint', 1, other, {'set', 'frozenset'})
		for elem in pairs(other) do
			if self._get(elem) ~= nil then
				return false
			end
		end
		return true
	end,

	issubset = function (self, other)
		return self <= frozenset{other}
	end,

	__le = function (a, b)
		for elem in pairs(a) do
			if b._get(elem) == nil then
				return false
			end
		end
		return true
	end,

	__lt = function (a, b)
		return a <= b and a.len() < b.len() -- is calculating a's length during its traversal in __le faster?
	end,

	issuperset = function (self, other)
		return self >= frozenset{other}
	end,

	union = function (self, ...)
		local sum = set{self}
		sum.update(...)
		return sum
	end,

	__add = function (a, b)
		local sum, _sum = a.__class{}
		for elem in pairs(a) do
			_sum._set(elem)
		end
		for elem in pairs(b) do
			_sum._set(elem)
		end
		return sum
	end,

	intersection = function (self, ...)
		local product = set{self}
		product.intersection_update(...)
		return product
	end,

	__mul = function (a, b)
		local product, _product = a.__class{}
		for elem in pairs(a) do
			if b._get(elem) ~= nil then
				_product._set(elem)
			end
		end
		return product
	end,

	difference = function (self, ...)
		local difference = set{self}
		difference.difference_update(...)
		return difference
	end,

	__sub = function (a, b)
		local difference, _difference = a.__class{}
		for elem in pairs(a) do
			if b._get(elem) == nil then
				_difference._set(elem)
			end
		end
		return difference
	end,

	symmetric_difference = function (self, other)
		return self ^ frozenset{other}
	end,

	__pow = function (a, b)
		local symm_diff, _symm_diff = a.__class{}
		for elem in pairs(a) do
			if b._get(elem) == nil then
				_symm_diff._set(elem)
			end
		end
		for elem in pairs(b) do
			if a._get(elem) == nil then
				_symm_diff._set(elem)
			end
		end
		return symm_diff
	end,

	copy = function (self)
		return (self.__class{self}) -- to not leak the private instance
	end,

	__eq = function (a, b)
		return a <= b and a >= b
	end,

	__staticmethods = {'_keySort'},
	__protected = {'_get', '_set'}
})


local set = class('set', frozenset, {
	_del = function (self, elem)
		if basic_types[type(elem)] then
			self._elements[elem] = nil
		else
			local elements_obj = self._elements_obj
			local h = elem.hash()

			while elements_obj[h] ~= nil do
				if elements_obj[h] == elem then
					elements_obj[h] = nil
					return
				end
				h = h + 1
			end
		end
	end,

	update = function (self, ...)
		local others, other = {...}
		for i = 1, select('#', ...) do
			other = frozenset{others[i]}
			for elem in pairs(other) do
				self._set(elem)
			end
		end
	end,

	intersection_update = function (self, ...)
		local others, _, _other = {...}
		for i = 1, select('#', ...) do
			_, _other = _frozenset{others[i]}
			for elem in pairs(self) do -- probably faster than iterating through (likely longer) "other"
				if _other._get(elem) == nil then
					self._del(elem)
				end
			end
		end
	end,

	difference_update = function (self, ...)
		local others, _, _other = {...}
		for i = 1, select('#', ...) do
			_, _other = _frozenset{others[i]}
			for elem in pairs(self) do
				if _other._get(elem) ~= nil then
					self._del(elem)
				end
			end
		end
	end,

	symmetric_difference_update = function (self, other)
		local _, _other = _frozenset{other}
		for elem in pairs(self) do
			if _other._get(elem) ~= nil then
				self._del(elem)
			end
		end
		for elem in pairs(_other) do
			if self._get(elem) == nil then
				self._set(elem)
			end
		end
	end,

	add = function (self, elem)
		if type(elem) == 'table' or isinstance(elem) and elem.hash == nil then
			error(("TypeError: invalid element type (got %s, which is not hashable)"):format(type(elem)), 2)
		end
		self._set(elem)
	end,

	remove = function (self, elem)
		if isinstance(elem, 'set') then
			elem = frozenset{elem}
		elseif type(elem) == 'table' or isinstance(elem) and elem.hash == nil then
			error(("TypeError: invalid element type (got %s, which is not hashable)"):format(type(elem)), 2)
		end
		if self._get(elem) == nil then
			error(("KeyError: %s"):format(tostring(elem)), 2)
		end
		self._del(elem)
	end,

	discard = function (self, elem)
		if isinstance(elem, 'set') then
			elem = frozenset{elem}
		elseif type(elem) == 'table' or isinstance(elem) and elem.hash == nil then
			error(("TypeError: invalid element type (got %s, which is not hashable)"):format(type(elem)), 2)
		end
		self._del(elem)
	end,

	pop = function (self)
		local k, v = next(self._elements)
		if k == nil then
			k, v = next(self._elements_obj)
			if k == nil then
				error("KeyError: pop from an empty set", 2)
			end
		end
		self._del(v)
		return v
	end,

	clear = function (self)
		self._elements = {}
		self._elements_obj = {}
	end,

	__protected = {'_del'}
})

return {frozenset, set}

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.