Table functions - Yonaba/Moses GitHub Wiki

Table functions

local M = require 'moses'

clear (t)

Clears a table. All its values becomes nil. Returns the passed-in table.

M.clear({1,2,'hello',true}) -- => {}

each (t, f)

Aliases: forEach.

Iterates over each value-key pair in the passed-in table.

M.each({4,2,1},print)

-- => 4 1
-- => 2 2
-- => 1 3

The table can be map-like (both array part and hash part).

M.each({one = 1, two = 2, three = 3},print)

-- => 1 one
-- => 2 two
-- => 3 three

Can index and assign in an outer table or in the passed-in table:

t = {'a','b','c'}
M.each(t,function(v,i)
  t[i] = v:rep(2)
  print(t[i])
end)

-- => aa
-- => bb
-- => cc

eachi (t, f)

Aliases: forEachi.

Iterates only on integer keys in an array table. It returns value-key pairs.

M.eachi({4,2,1},print)

-- => 4 1
-- => 2 2
-- => 1 3

The given array can be sparse, or even have a hash-like part.

local t = {a = 1, b = 2, [0] = 1, [-1] = 6, 3, x = 4, 5}
M.eachi(t,print)

-- => 6 -1
-- => 1 0
-- => 3 1	
-- => 5 2

at (t, ...)

Collects values at given keys and returns them in an array.

local t = {4,5,6}
M.at(t,1,3) -- => "{4,6}"

local t = {a = 4, bb = true, ccc = false}
M.at(t,'a', 'ccc') -- => "{4, false}"

adjust (t, key, f)

Adjusts the value at a given key using a function or a value. In case f is a function, it should be prototyped f(v). It does not mutate the given table, but rather returns a new array.

local t = {1,2,3}
M.adjust(t, 2, math.sin) -- => {1, 0.90929, 3}

local v = {x = 1}
 M.adjust(t, 'x', 4) -- => {x = 4}

In case the given key does not exist in t, it throws an error.

count (t [, val])

Counts the number of occurences of a given value in a table.

M.count({1,1,2,3,3,3,2,4,3,2},1) -- => 2
M.count({1,1,2,3,3,3,2,4,3,2},2) -- => 3
M.count({1,1,2,3,3,3,2,4,3,2},3) -- => 4
M.count({false, false, true},false) -- => 2
M.count({false, false, true},true) -- => 1

Returns the size of the list in case no value was provided.

M.count({1,1,2,3,3}) -- => 5

countf (t, f)

Counts the number of values passing an iterator test.

M.countf({1,2,3,4,5,6}, function(v)
  return v%2==0
end) -- => 3

M.countf({print, pairs, os, assert, ipairs}, function(v)
  return type(v)=='function'
end) -- => 4

allEqual (t [, comp])

Aliases: alleq.

Checks if all values in a collection are equal. Uses M.isEqual by default to compare values.

M.allEqual({1,1,1,1,1}, comp) -- => true
M.allEqual({1,1,2,1,1}, comp) -- => false

local t1 = {1, 2, {3}}
local t2 = {1, 2, {3}}
M.allEqual({t1, t2}) -- => true

Can take an optional comp function which will be used to compare values.

local t1 = {x = 1, y = 0}
local t2 = {x = 1, y = 0}
local t3 = {x = 1, y = 2}
local t4 = {x = 1, y = 2}
local function compx(a, b) return a.x == b.x end
local function compy(a, b) return a.y == b.y end

M.allEqual({t1, t2}, compx) -- => true
M.allEqual({t1, t2}, compy) -- => true
M.allEqual({t3, t4}, compx) -- => true
M.allEqual({t3, t4}, compy) -- => true
M.allEqual({t1, t2, t3, t4}, compx) -- => true
M.allEqual({t1, t2, t3, t4}, compy) -- => false

cycle (t [, n = 1])

Aliases: loop.

Returns a function which iterates on each value-key pair in a given table (similarly to M.each), except that it restarts iterating again n times. If n is not provided, it defaults to 1.

local t = {'a','b','c'}
for v in M.cycle(t, 2) do
  print(v)
end

-- => 'a'
-- => 'b'
-- => 'c'
-- => 'a'
-- => 'b'
-- => 'c'

Supports array-like tables and map-like tables.

local t = {x = 1, y = 2, z = 3}
for v in M.cycle(t) do
  print(v)
end

-- => 2
-- => 1
-- => 3

map (t, f)

Aliases: collect.

Executes a function on each value in a given array.

M.map({1,2,3},function(v) 
  return v+10 
end) -- => "{11,12,13}"
M.map({a = 1, b = 2},function(v, k) 
  return k..v 
end) -- => "{a = 'a1', b = 'b2'}"

It also maps both keys and values.

M.map({a = 1, b = 2},function(v, k) 
  return k..k, v*2 
end) -- => "{aa = 2, bb = 4}"

reduce (t, f [, state = next(t)])

Aliases: inject, foldl.

Can sum all values in a table. In case state is not provided, it defaults to the first value in the given table t.

local function add(a,b) return a+b end
M.reduce({1,2,3,4},add) -- => 10

Or concatenates all values.

local function concat(a,b) return a..b end	
M.reduce({'a','b','c','d'},concat) -- => abcd	 

best (t, f)

Returns the best value passing a selector function. Acts as a special case of reduce, using the first value in t as an initial state. It thens folds the given table, testing each of its values v and selecting the value passing the call f(state,v) every time.

local words = {'Lua', 'Programming', 'Language'}
M.best(words, function(a,b) return #a > #b end) -- => 'Programming'
M.best(words, function(a,b) return #a < #b end) -- => 'Lua'

reduceBy (t, f, pred [, state = next(t)])

Reduces a table considering only values matching a predicate. For example,let us define a set of values.

local val = {-1, 8, 0, -6, 3, -1, 7, 1, -9}

And a reduction function which will add up values.

local function add(a,b) return a+b end

We can also define some predicate functions.

-- predicate for negative values
local function neg(v) return v<=0 end

-- predicate for positive values
local function pos(v) return v>=0 end

Then we can perform reduction considering only negative or positive values :

M.reduceBy(val, add, neg) -- => -17
M.reduceBy(val, add, pos) -- => 19

An initial state can be passed in.

M.reduceBy(val, add, neg, 17) -- => 0
M.reduceBy(val, add, pos, -19) -- => 0

reduceRight (t, f [, state = next(t)])

Aliases: injectr, foldr.

Similar to M.reduce, but performs from right to left.

local initial_state = 256
local function div(a,b) return a/b end
M.reduceRight({1,2,4,16},div,initial_state) -- => 2

mapReduce (t, f [, state = next(t)])

Aliases: mapr.

Reduces while saving intermediate states.

local function concat(a,b) return a..b end
M.mapReduce({'a','b','c'},concat) -- => "{'a', 'ab', 'abc'}"

mapReduceRight (t, f [, state = next(t)])

Aliases: maprr.

Reduces from right to left, while saving intermediate states.

local function concat(a,b) return a..b end
M.mapReduceRight({'a','b','c'},concat) -- => "{'c', 'cb', 'cba'}"

include (t, value)

Aliases: any, some, contains.

Looks for a value in a table.

M.include({6,8,10,16,29},16) -- => true
M.include({6,8,10,16,29},1) -- => false

local complex_table = {18,{2,{3}}}
local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
M.include(collection, complex_table) -- => true

Handles iterator functions.

local function isUpper(v) return v:upper()== v end
M.include({'a','B','c'},isUpper) -- => true

detect (t, value)

Returns the index of a value in a table.

M.detect({6,8,10,16},8) -- => 2
M.detect({nil,true,0,true,true},false) -- => nil

local complex_table = {18,{2,6}}
local collection = {6,{18,{2,6}},10,{18,{2,{3}}},29}
M.detect(collection, complex_table) -- => 2

Handles iterator functions.

local function isUpper(v)
  return v:upper()==v
end
M.detect({'a','B','c'},isUpper) -- => 2

where (t, props)

Looks through a table and returns all the values that matches all of the key-value pairs listed in props.

local items = {
  {height = 10, weight = 8, price = 500},
  {height = 10, weight = 15, price = 700},
  {height = 15, weight = 15, price = 3000},
  {height = 10, weight = 8, price = 3000},
}
M.where(items, {height = 10}) -- => {items[1], items[2], items[4]}
M.where(items, {weight = 15}) -- => {items[2], items[3]}
M.where(items, {prince = 3000}) -- => {items[3], items[4]}
M.where(items, {height = 10, weight = 15, prince = 700}) -- => {items[2]}

findWhere (t, props)

Looks through a table and returns the first value found that matches all of the key-value pairs listed in props.

local a = {a = 1, b = 2, c = 3}
local b = {a = 2, b = 3, d = 4}
local c = {a = 3, b = 4, e = 5}
M.findWhere({a, b, c}, {a = 3, b = 4}) == c -- => true

select (t, f)

Aliases: filter.

Collects values passing a validation test.

local function isEven(v) return v%2==0 end
local function isOdd(v) return v%2~=0 end

M.select({1,2,3,4,5,6,7}, isEven) -- => "{2,4,6}"
M.select({1,2,3,4,5,6,7}, isOdd) -- => "{1,3,5,7}"

reject (t, f)

Aliases: reject.

Removes all values failing (returning false or nil) a validation test:

local function isEven(v) return v%2==0 end
local function isOdd(v) return v%2~=0 end

M.reject({1,2,3,4,5,6,7}, isEven) -- => "{1,3,5,7}"
M.reject({1,2,3,4,5,6,7}, isOdd) -- => "{2,4,6}"

all (t, f)

Aliases: every.

Checks whether or not all elements pass a validation test.

local function isEven(v) return v%2==0 end
M.all({2,4,6}, isEven) -- => true

invoke (t, method)

Invokes a given function on each value in a table.

M.invoke({'a','bea','cdhza'},string.len) -- => "{1,3,5}"

Can reference the method of the same name in each value.

local a, b, c, d = {id = 'a'}, {id = 'b'}, {id = 'c'}, {id = 'd'}
local function call(self) return self.id end
M.invoke({a,b,c,d},call) -- => "{'a','b','c','d'}"

pluck (t, property)

Fetches all values indexed with specific key in a table of objects.

local peoples = {
  {name = 'John', age = 23},{name = 'Peter', age = 17},
  {name = 'Steve', age = 15},{age = 33}}

M.pluck(peoples,'age') -- => "{23,17,15,33}"
M.pluck(peoples,'name') -- => "{'John', 'Peter', 'Steve'}"

max (t [, transform])

Returns the maximum value in a collection.

M.max {1,2,3} -- => 3
M.max {'a','b','c'} -- => 'c'

Can take an iterator function to extract a specific property.

local peoples = {
  {name = 'John', age = 23},{name = 'Peter', age = 17},
  {name = 'Steve', age = 15},{age = 33}}
M.max(peoples,function(people) return people.age end) -- => 33

min (t [, transform])

Returns the minimum value in a collection.

M.min {1,2,3} -- => 1
M.min {'a','b','c'} -- => 'a'

Can take an iterator function to extract a specific property.

local peoples = {
  {name = 'John', age = 23},{name = 'Peter', age = 17},
  {name = 'Steve', age = 15},{age = 33}}
M.min(peoples,function(people) return people.age end) -- => 15

same (a, b)

Tests whether or not all values in each of the passed-in tables exists in both tables.

local a = {'a','b','c','d'}      
local b = {'b','a','d','c'}
M.same(a,b) -- => true

b[#b+1] = 'e'
M.same(a,b) -- => false

sort (t [, comp = math.min])

Sorts a collection.

M.sort({'b','a','d','c'}) -- => "{'a','b','c','d'}"

Handles custom comparison functions.

M.sort({'b','a','d','c'}, function(a,b) 
  return a:byte() > b:byte() 
end) -- => "{'d','c','b','a'}"

sortedk (t [, comp])

Iterates on values with respect to key order. Keys are sorted using comp function which defaults to math.min. It returns upon each call a key, value pair.

local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12
for k, v in M.sortedk(tbl) do print(k, v) end

-- => 1	12
-- => 2	6
-- => 3	5
-- => 4	10
-- => 5	8

local function comp(a,b) return a > b end
for k, v in M.sortedk(tbl, comp) do print(k, v) end

-- => 5	8
-- => 4	10
-- => 3	5
-- => 2	6
-- => 1	12

sortedv (t [, comp])

Iterates on values with respect to key order. Keys are sorted using comp function which defaults to math.min. It returns upon each call a key, value pair.

local tbl = {}; tbl[3] = 5 ; tbl[2] = 6; tbl[5] = 8; tbl[4] = 10; tbl[1] = 12
for k, v in M.sortedv(tbl) do print(k, v) end

-- => 3	5
-- => 2	6
-- => 5	8
-- => 4	10
-- => 1	12

local function comp(a,b) return a > b end
for k, v in M.sortedv(tbl, comp) do print(k, v) end

-- => 1	12
-- => 4	10
-- => 5	8
-- => 2	6
-- => 3	5

sortBy (t [, transform [, comp = math.min]])

Sorts items in a collection based on the result of running a transform function through every item in the collection.

local r = M.sortBy({1,2,3,4,5}, math.sin)
print(table.concat(r,','))

-- => {5,4,3,1,2}

The transform function can also be a string name property.

local people = {
	{name = 'albert', age = 40},
	{name = 'louis', age = 55},
	{name = 'steve', age = 35},
	{name = 'henry', age = 19},
}
local r = M.sortBy(people, 'age')
M.each(r, function(v) print(v.age, v.name)	end)

-- => 19	henry
-- => 35	steve
-- => 40	albert
-- => 55	louis

As seen above, the defaut comparison function is the '<' operator. For example, let us supply a different one to sort the list of people by decreasing age order :

local people = {
	{name = 'albert', age = 40},
	{name = 'louis', age = 55},
	{name = 'steve', age = 35},
	{name = 'henry', age = 19},
}
local r = M.sortBy(people, 'age', function(a,b) return a > b end)
M.each(r, function(v) print(v.age, v.name)	end)

-- => 55	louis
-- => 40	albert
-- => 35	steve
-- => 19	henry

The transform function defaults to M.indentity and in that case, M.sortBy behaves like M.sort.

local r = M.sortBy({1,2,3,4,5})
print(table.concat(r,','))

-- => {1,2,3,4,5}

groupBy (t, iter)

Groups values in a collection depending on their return value when passed to a predicate test.

M.groupBy({0,1,2,3,4,5,6},function(v) 
  return v%2==0 and 'even' or 'odd'
end)
-- => "{odd = {1,3,5}, even = {0,2,4,6}}"

M.groupBy({0,'a',true, false,nil,b,0.5},type) 
-- => "{number = {0,0.5}, string = {'a'}, boolean = {true, false}}"		

countBy (t, iter)

Splits a table in subsets and provide the count for each subset.

M.countBy({0,1,2,3,4,5,6},function(v) 
  return v%2==0 and 'even' or 'odd'
end) -- => "{odd = 3, even = 4}"

size (...)

When given a table, provides the count for the very number of values in that table.

M.size {1,2,3} -- => 3
M.size {one = 1, two = 2} -- => 2

When given a vararg list of arguments, returns the count of these arguments.

M.size(1,2,3) -- => 3
M.size('a','b',{}, function() end) -- => 4

containsKeys (t, other)

Checks whether a table has all the keys existing in another table.

M.contains({1,2,3,4},{1,2,3}) -- => true
M.contains({1,2,'d','b'},{1,2,3,5}) -- => true
M.contains({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => true

sameKeys (tA, tB)

Checks whether both tables features the same keys:

M.sameKeys({1,2,3,4},{1,2,3}) -- => false
M.sameKeys({1,2,'d','b'},{1,2,3,5}) -- => true
M.sameKeys({x = 1, y = 2, z = 3},{x = 1, y = 2}) -- => false