prosody/util/array.lua
2023-07-22 16:31:05 +02:00

261 lines
5.1 KiB
Lua

-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local t_insert, t_sort, t_remove, t_concat
= table.insert, table.sort, table.remove, table.concat;
local t_move = require "prosody.util.table".move;
local setmetatable = setmetatable;
local getmetatable = getmetatable;
local math_random = math.random;
local math_floor = math.floor;
local pairs, ipairs = pairs, ipairs;
local tostring = tostring;
local type = type;
local array = {};
local array_base = {};
local array_methods = {};
local array_mt = {
__index = array_methods;
__name = "array";
__tostring = function (self) return "["..self:concat(", ").."]"; end;
};
function array_mt:__freeze() return self; end
local function new_array(self, t, _s, _var)
if type(t) == "function" then -- Assume iterator
t = self.collect(t, _s, _var);
end
return setmetatable(t or {}, array_mt);
end
function array.new(t)
return setmetatable(t or {}, array_mt);
end
function array_mt.__add(a1, a2)
local res = new_array();
return res:append(a1):append(a2);
end
function array_mt.__eq(a, b)
if getmetatable(a) ~= array_mt or getmetatable(b) ~= array_mt then
-- Lua 5.3+ calls this if both operands are tables, even if metatables differ
return false;
end
if #a == #b then
for i = 1, #a do
if a[i] ~= b[i] then
return false;
end
end
else
return false;
end
return true;
end
function array_mt.__div(a1, func)
local a2 = new_array();
local o = 0;
for i = 1, #a1 do
local new_value = func(a1[i]);
if new_value ~= nil then
o = o + 1;
a2[o] = new_value;
end
end
return a2;
end
setmetatable(array, { __call = new_array });
-- Read-only methods
function array_methods:random()
return self[math_random(1, #self)];
end
-- Return a random value excluding the one at idx
function array_methods:random_other(idx)
local max = #self;
return self[((math.random(1, max-1)+(idx-1))%max)+1];
end
-- These methods can be called two ways:
-- array.method(existing_array, [params [, ...]]) -- Create new array for result
-- existing_array:method([params, ...]) -- Transform existing array into result
--
function array_base.map(outa, ina, func)
for k, v in ipairs(ina) do
outa[k] = func(v);
end
return outa;
end
function array_base.filter(outa, ina, func)
local inplace, start_length = ina == outa, #ina;
local write = 1;
for read = 1, start_length do
local v = ina[read];
if func(v) then
outa[write] = v;
write = write + 1;
end
end
if inplace and write <= start_length then
for i = write, start_length do
outa[i] = nil;
end
end
return outa;
end
function array_base.slice(outa, ina, i, j)
if j == nil then
j = -1;
end
if j < 0 then
j = #ina + (j+1);
end
if i < 0 then
i = #ina + (i+1);
end
if i < 1 then
i = 1;
end
if j > #ina then
j = #ina;
end
if i > j then
for idx = 1, #outa do
outa[idx] = nil;
end
return outa;
end
t_move(ina, i, j, 1, outa);
if ina == outa then
-- Clear (nil) remainder of range
t_move(ina, #outa+1, #outa*2, 2+j-i, ina);
end
return outa;
end
function array_base.sort(outa, ina, ...)
if ina ~= outa then
outa:append(ina);
end
t_sort(outa, ...);
return outa;
end
function array_base.unique(outa, ina)
local seen = {};
return array_base.filter(outa, ina, function (item)
if seen[item] then
return false;
else
seen[item] = true;
return true;
end
end);
end
function array_base.pluck(outa, ina, key, default)
for i = 1, #ina do
local v = ina[i][key];
if v == nil then
v = default;
end
outa[i] = v;
end
return outa;
end
function array_base.reverse(outa, ina)
local len = #ina;
if ina == outa then
local middle = math_floor(len/2);
len = len + 1;
local o; -- opposite
for i = 1, middle do
o = len - i;
outa[i], outa[o] = outa[o], outa[i];
end
else
local off = len + 1;
for i = 1, len do
outa[i] = ina[off - i];
end
end
return outa;
end
--- These methods only mutate the array
function array_methods:shuffle()
local len = #self;
for i = 1, #self do
local r = math_random(i, len);
self[i], self[r] = self[r], self[i];
end
return self;
end
function array_methods:append(ina)
t_move(ina, 1, #ina, #self+1, self);
return self;
end
function array_methods:push(x)
t_insert(self, x);
return self;
end
array_methods.pop = t_remove;
function array_methods:concat(sep)
return t_concat(array.map(self, tostring), sep);
end
function array_methods:length()
return #self;
end
--- These methods always create a new array
function array.collect(f, s, var)
local t = {};
while true do
var = f(s, var);
if var == nil then break; end
t_insert(t, var);
end
return setmetatable(t, array_mt);
end
---
-- Setup methods from array_base
for method, f in pairs(array_base) do
local base_method = f;
-- Setup global array method which makes new array
array[method] = function (old_a, ...)
local a = new_array();
return base_method(a, old_a, ...);
end
-- Setup per-array (mutating) method
array_methods[method] = function (self, ...)
return base_method(self, self, ...);
end
end
return array;