LiquidProxy Lua Edition
1local table = {}
2for k,v in pairs(require 'table') do table[k] = v end
3
4table.__index = table
5
6function table.new(...)
7 return setmetatable({}, table):union(...)
8end
9
10setmetatable(table, {
11 __call = function(t, ...)
12 return table.new(...)
13 end
14})
15
16-- 5.2 or 5.3 compatible
17table.unpack = table.unpack or unpack
18
19-- [[ how about table.unpack(t) defaults to table.unpack(t, 1, t.n) if t.n is present?
20-- for cohesion with table.pack?
21-- already table.unpack's default is #t, but this doesn't account for nils
22-- this might break compatability somewhere ...
23local origTableUnpack = table.unpack
24function table.unpack(...)
25 local nargs = select('#', ...)
26 local t, i, j = ...
27 if nargs < 3 and t.n ~= nil then
28 return origTableUnpack(t, i or 1, t.n)
29 end
30 return origTableUnpack(...)
31end
32--]]
33
34-- 5.1 compatible
35if not table.pack then
36 function table.pack(...)
37 local t = {...}
38 t.n = select('#', ...)
39 return setmetatable(t, table)
40 end
41else
42 local oldpack = table.pack
43 function table.pack(...)
44 return setmetatable(oldpack(...), table)
45 end
46end
47
48-- non-5.1 compat:
49if not table.maxn then
50 function table.maxn(t)
51 local max = 0
52 for k,v in pairs(t) do
53 if type(k) == 'number' then
54 max = math.max(max, k)
55 end
56 end
57 return max
58 end
59end
60
61-- applies to the 'self' table
62-- same behavior as new
63function table:union(...)
64 for i=1,select('#', ...) do
65 local o = select(i, ...)
66 if o then
67 for k,v in pairs(o) do
68 self[k] = v
69 end
70 end
71 end
72 return self
73end
74
75-- something to consider:
76-- mapvalue() returns a new table
77-- but append() modifies the current table
78-- for consistency shouldn't append() create a new one as well?
79function table:append(...)
80 for i=1,select('#', ...) do
81 local u = select(i, ...)
82 if u then
83 for _,v in ipairs(u) do
84 table.insert(self, v)
85 end
86 end
87 end
88 return self
89end
90
91function table:removeKeys(...)
92 for i=1,select('#', ...) do
93 local v = select(i, ...)
94 self[v] = nil
95 end
96end
97
98-- cb(value, key, newtable) returns newvalue[, newkey]
99-- nil newkey means use the old key
100function table:map(cb)
101 local t = table()
102 for k,v in pairs(self) do
103 local nv, nk = cb(v,k,t)
104 if nk == nil then nk = k end
105 t[nk] = nv
106 end
107 return t
108end
109
110-- cb(value, key, newtable) returns newvalue[, newkey]
111-- nil newkey means use the old key
112function table:mapi(cb)
113 local t = table()
114 for k=1,#self do
115 local v = self[k]
116 local nv, nk = cb(v,k,t)
117 if nk == nil then nk = k end
118 t[nk] = nv
119 end
120 return t
121end
122
123-- this excludes keys that don't pass the callback function
124-- if the key is an ineteger then it is table.remove'd
125-- currently the handling of integer keys is the only difference between this
126-- and calling table.map and returning nil kills on filtered items
127function table:filter(f)
128 local t = table()
129 for k,v in pairs(self) do
130 if f(v,k) then
131 -- TODO now that i made filteri, should this only ever always directly map keys
132 -- even if the key is an integer?
133 -- or should it still insert integer keys?
134 if type(k) == 'string' then
135 t[k] = v
136 else
137 t:insert(v)
138 end
139 end
140 end
141 return t
142end
143
144-- like filter but only works on ipairs entries
145function table:filteri(f)
146 local t = table()
147 for k,v in ipairs(self) do
148 if f(v,k) then
149 t:insert(v)
150 end
151 end
152 return t
153end
154
155function table:keys()
156 local t = table()
157 for k,_ in pairs(self) do
158 t:insert(k)
159 end
160 return t
161end
162
163function table:values()
164 local t = table()
165 for _,v in pairs(self) do
166 t:insert(v)
167 end
168 return t
169end
170
171-- should we also return value, key to match map, sup, and inf?
172-- that seems redundant if it's find-by-value ...
173function table:find(value, eq)
174 if eq then
175 for k,v in pairs(self) do
176 if eq(v, value) then return k, v end
177 end
178 else
179 for k,v in pairs(self) do
180 if v == value then return k, v end
181 end
182 end
183end
184
185function table:findi(value, eq)
186 if eq then
187 for k,v in ipairs(self) do
188 if eq(v, value) then return k, v end
189 end
190 else
191 for k,v in ipairs(self) do
192 if v == value then return k, v end
193 end
194 end
195end
196
197-- should insertUnique only operate on the pairs() ?
198-- especially when insert() itself is an ipairs() operation
199function table:insertUnique(value, eq)
200 if not table.find(self, value, eq) then table.insert(self, value) end
201end
202
203function table:removeObject(...)
204 local removedKeys = table()
205 local len = #self
206 local k = table.find(self, ...)
207 while k ~= nil do
208 if type(k) == 'number' and tonumber(k) <= len then
209 table.remove(self, k)
210 else
211 self[k] = nil
212 end
213 removedKeys:insert(k)
214 k = table.find(self, ...)
215 end
216 return table.unpack(removedKeys)
217end
218
219function table:kvpairs()
220 local t = table()
221 for k,v in pairs(self) do
222 table.insert(t, {[k]=v})
223 end
224 return t
225end
226
227-- TODO - math instead of table?
228-- TODO - have cmp default to operator> just like inf and sort?
229function table:sup(cmp)
230 local bestk, bestv
231 if cmp then
232 for k,v in pairs(self) do
233 if bestv == nil or cmp(v, bestv) then bestk, bestv = k, v end
234 end
235 else
236 for k,v in pairs(self) do
237 if bestv == nil or v > bestv then bestk, bestv = k, v end
238 end
239 end
240 return bestv, bestk
241end
242
243-- TODO - math instead of table?
244function table:inf(cmp)
245 local bestk, bestv
246 if cmp then
247 for k,v in pairs(self) do
248 if bestv == nil or cmp(v, bestv) then bestk, bestv = k, v end
249 end
250 else
251 for k,v in pairs(self) do
252 if bestv == nil or v < bestv then bestk, bestv = k, v end
253 end
254 end
255 return bestv, bestk
256end
257
258-- combine elements of
259function table:combine(callback)
260 local s
261 for _,v in pairs(self) do
262 if s == nil then
263 s = v
264 else
265 s = callback(s, v)
266 end
267 end
268 return s
269end
270
271local op = require 'ext.op'
272
273function table:sum()
274 return table.combine(self, op.add)
275end
276
277function table:product()
278 return table.combine(self, op.mul)
279end
280
281function table:last()
282 return self[#self]
283end
284
285-- just like string subset
286function table.sub(t,i,j)
287 if i < 0 then i = math.max(1, #t + i + 1) end
288 --if i < 0 then i = math.max(1, #t + i + 1) else i = math.max(1, i) end -- TODO this is affecting symmath edge cases somewhere ...
289 j = j or #t
290 j = math.min(j, #t)
291 if j < 0 then j = math.min(#t, #t + j + 1) end
292 --if j < 0 then j = math.min(#t, #t + j + 1) else j = math.max(1, j) end -- TODO this is affecting symmath edge cases somewhere ...
293 local res = {}
294 for k=i,j do
295 res[k-i+1] = t[k]
296 end
297 setmetatable(res, table)
298 return res
299end
300
301function table.reverse(t)
302 local r = table()
303 for i=#t,1,-1 do
304 r:insert(t[i])
305 end
306 return r
307end
308
309function table.rep(t,n)
310 local c = table()
311 for i=1,n do
312 c:append(t)
313 end
314 return c
315end
316
317-- in-place sort is fine, but it returns nothing. for kicks I'd like to chain methods
318local oldsort = require 'table'.sort
319function table:sort(...)
320 oldsort(self, ...)
321 return self
322end
323
324-- returns a shuffled duplicate of the ipairs in table 't'
325function table.shuffle(t)
326 t = table(t)
327 -- https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
328 for i=#t,2,-1 do
329 local j = math.random(i-1)
330 t[i], t[j] = t[j], t[i]
331 end
332 return t
333end
334
335function table.pickRandom(t)
336 return t[math.random(#t)]
337end
338
339-- keys = options, values = probs
340function table.pickWeighted(t)
341 local total = table.values(t):sum()
342 local x = math.random() * total
343 for k,v in pairs(t) do
344 x = x - v
345 if x <= 0 then
346 return k
347 end
348 end
349 -- this should never be reached unless you have some not-a-number's as values
350end
351
352-- where to put this ...
353-- I want to convert iterators into tables
354-- it looks like a coroutine but it is made for functions returned from coroutine.wrap
355-- also, what to do with multiple-value iterators (like ipairs)
356-- do I only wrap the first value?
357-- do I wrap both values in a double table?
358-- do I do it optionally based on the # args returned?
359-- how about I ask for a function to convert the iterator to the table?
360-- this is looking very similar to table.map
361-- I'll just wrap it with table.wrap and then let the caller use :mapi to transform the results
362-- usage: table.wrapfor(ipairs(t))
363-- if you want to wrap a 'for=' loop then just use range(a,b[,c])
364-- ok at this point I should just start using lua-fun ...
365function table.wrapfor(f, s, var)
366 local t = table()
367 while true do
368 local vars = table.pack(f(s, var))
369 local var_1 = vars[1]
370 if var_1 == nil then break end
371 var = var_1
372 t:insert(vars)
373 end
374 return t
375end
376
377-- https://www.lua.org/pil/9.3.html
378local function permgen(t, n)
379 if n < 1 then
380 coroutine.yield(t)
381 else
382 for i=n,1,-1 do
383 -- put i-th element as the last one
384 t[n], t[i] = t[i], t[n]
385 -- generate all permutations of the other elements
386 permgen(t, n - 1)
387 -- restore i-th element
388 t[n], t[i] = t[i], t[n]
389 end
390 end
391end
392
393-- return iterator of permutations of the table
394function table.permutations(t)
395 return coroutine.wrap(function()
396 permgen(t, table.maxn(t))
397 end)
398end
399
400-- I won't add table.getmetatable because, as a member method, that will always return 'table'
401
402-- if you use this as a member method then know that you can't use it a second time (unless the metatable you set it to has a __index that has 'setmetatable' defined)
403table.setmetatable = setmetatable
404
405return table