Module:LuaLinq
Documentation for this module may be created at Module:LuaLinq/doc
-- LuaLinq - http://code.google.com/p/lualinq/
-- ------------------------------------------------------------------------
-- Copyright (c) 2012, Marco Mastropaolo (Xanathar)
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification,
-- are permitted provided that the following conditions are met:
--
-- o Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
-- o Redistributions in binary form must reproduce the above copyright notice,
-- this list of conditions and the following disclaimer in the documentation
-- and/or other materials provided with the distribution.
-- o Neither the name of Marco Mastropaolo nor the names of its contributors
-- may be used to endorse or promote products derived from this software
-- without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-- IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
-- INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-- BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-- OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
-- OF THE POSSIBILITY OF SUCH DAMAGE.
-- ------------------------------------------------------------------------
local p = {}
-- support lua 5.3
local unpack = table.unpack and table.unpack or unpack
-- how much log information is printed: 3 => verbose, 2 => info, 1 => only warning and errors, 0 => only errors, -1 => silent
local LOG_LEVEL = 1
-- prefix for the printed logs
local LOG_PREFIX = "LuaLinq: "
-- ============================================================
-- DEBUG TRACER
-- ============================================================
local LIB_VERSION_TEXT = "1.6.0"
local LIB_VERSION = 160
local function setLogLevel(level)
LOG_LEVEL = level;
end
local function _log(level, prefix, text)
if (level <= LOG_LEVEL) then
print(prefix .. LOG_PREFIX .. text)
end
end
local function logq(self, method)
if (LOG_LEVEL >= 3) then
logv("after " .. method .. " => " .. #self.m_Data .. " items : " .. _dumpData(self))
end
end
local function _dumpData(self)
local items = #self.m_Data
local dumpdata = "q{ "
for i = 1, 3 do
if (i <= items) then
if (i ~= 1) then
dumpdata = dumpdata .. ", "
end
dumpdata = dumpdata .. tostring(self.m_Data[i])
end
end
if (items > 3) then
dumpdata = dumpdata .. ", ..." .. items .. " }"
else
dumpdata = dumpdata .. " }"
end
return dumpdata
end
local function logv(txt)
_log(3, "[..] ", txt)
end
local function logi(txt)
_log(2, "[ii] ", txt)
end
local function logw(txt)
_log(1, "[W?] ", txt)
end
local function loge(txt)
_log(0, "[E!] ", txt)
end
-- ============================================================
-- CONSTRUCTOR
-- ============================================================
local from
local fromArray
local fromArrayInstance
local fromDictionary
local fromIterator
local fromIteratorsArray
local fromNothing
local fromSet
local _all
local _any
local _average
local _concat
local _contains
local _count
local _distinct
local _dump
local _except
local _exceptby
local _first
local _foreach
local _foreach
local _intersection
local _intersectionby
local _last
local _map
local _max
local _min
local _random
local _select
local _selectMany
local _skip
local _sum
local _take
local _toArray
local _toDictionary
local _toIterator
local _toTuple
local _union
local _where
local _whereIndex
local _xmap
local _zip
-- [private] Creates a linq data structure from an array without copying the data for efficiency
local function _new_lualinq(method, collection)
local self = { }
self.classid_71cd970f_a742_4316_938d_1998df001335 = 2
self.m_Data = collection
self.concat = _concat
self.select = _select
self.selectMany = _selectMany
self.where = _where
self.whereIndex = _whereIndex
self.take = _take
self.skip = _skip
self.zip = _zip
self.distinct = _distinct
self.union = _union
self.except = _except
self.intersection = _intersection
self.exceptby = _exceptby
self.intersectionby = _intersectionby
self.exceptBy = _exceptby
self.intersectionBy = _intersectionby
self.first = _first
self.last = _last
self.min = _min
self.max = _max
self.random = _random
self.any = _any
self.all = _all
self.contains = _contains
self.count = _count
self.sum = _sum
self.average = _average
self.dump = _dump
self.map = _map
self.foreach = _foreach
self.xmap = _xmap
self.toArray = _toArray
self.toDictionary = _toDictionary
self.toIterator = _toIterator
self.toTuple = _toTuple
-- shortcuts
self.each = _foreach
self.intersect = _intersection
self.intersectby = _intersectionby
self.intersectBy = _intersectionby
logq(self, "from")
return self
end
-- ============================================================
-- GENERATORS
-- ============================================================
function p.main(auto)
return from(auto)
end
-- Tries to autodetect input type and uses the appropriate from method
function from(auto)
if (auto == nil) then
return fromNothing()
elseif (type(auto) == "function") then
return fromIterator(auto)
elseif (type(auto) == "table") then
if (auto["classid_71cd970f_a742_4316_938d_1998df001335"] ~= nil) then
return auto
elseif (auto[1] == nil) then
return fromDictionary(auto)
elseif (type(auto[1]) == "function") then
return fromIteratorsArray(auto)
else
return fromArrayInstance(auto)
end
end
return fromNothing()
end
-- Creates a linq data structure from an array without copying the data for efficiency
function fromArrayInstance(collection)
return _new_lualinq("fromArrayInstance", collection)
end
-- Creates a linq data structure from an array copying the data first (so that changes in the original
-- table do not reflect here)
function fromArray(array)
local collection = { }
for k,v in ipairs(array) do
table.insert(collection, v)
end
return _new_lualinq("fromArray", collection)
end
-- Creates a linq data structure from a dictionary (table with non-consecutive-integer keys)
function fromDictionary(dictionary)
local collection = { }
for k,v in pairs(dictionary) do
local kvp = {}
kvp.key = k
kvp.value = v
table.insert(collection, kvp)
end
return _new_lualinq("fromDictionary", collection)
end
-- Creates a linq data structure from an iterator returning single items
function fromIterator(iterator)
local collection = { }
for s in iterator do
table.insert(collection, s)
end
return _new_lualinq("fromIterator", collection)
end
-- Creates a linq data structure from an array of iterators each returning single items
function fromIteratorsArray(iteratorArray)
local collection = { }
for _, iterator in ipairs(iteratorArray) do
for s in iterator do
table.insert(collection, s)
end
end
return _new_lualinq("fromIteratorsArray", collection)
end
-- Creates a linq data structure from a table of keys, values ignored
function fromSet(set)
local collection = { }
for k,v in pairs(set) do
table.insert(collection, k)
end
return _new_lualinq("fromIteratorsArray", collection)
end
-- Creates an empty linq data structure
function fromNothing()
return _new_lualinq("fromNothing", { } )
end
-- ============================================================
-- QUERY METHODS
-- ============================================================
-- Concatenates two collections together
function _concat(self, otherlinq)
local result = { }
for idx, value in ipairs(self.m_Data) do
table.insert(result, value)
end
for idx, value in ipairs(otherlinq.m_Data) do
table.insert(result, value)
end
return _new_lualinq(":concat", result)
end
-- Replaces items with those returned by the selector function or properties with name selector
function _select(self, selector)
local result = { }
if (type(selector) == "function") then
for idx, value in ipairs(self.m_Data) do
local newvalue = selector(value)
if (newvalue ~= nil) then
table.insert(result, newvalue)
end
end
elseif (type(selector) == "string") then
for idx, value in ipairs(self.m_Data) do
local newvalue = value[selector]
if (newvalue ~= nil) then
table.insert(result, newvalue)
end
end
else
loge("select called with unknown predicate type");
end
return _new_lualinq(":select", result)
end
-- Replaces items with those contained in arrays returned by the selector function
function _selectMany(self, selector)
local result = { }
for idx, value in ipairs(self.m_Data) do
local newvalue = selector(value)
if (newvalue ~= nil) then
for ii, vv in ipairs(newvalue) do
if (vv ~= nil) then
table.insert(result, vv)
end
end
end
end
return _new_lualinq(":selectMany", result)
end
-- Returns a linq data structure where only items for whose the predicate has returned true are included
function _where(self, predicate, refvalue, ...)
local result = { }
if (type(predicate) == "function") then
for idx, value in ipairs(self.m_Data) do
if (predicate(value, refvalue, from({...}):toTuple())) then
table.insert(result, value)
end
end
elseif (type(predicate) == "string") then
local refvals = {...}
if (#refvals > 0) then
table.insert(refvals, refvalue);
return _intersectionby(self, predicate, refvals);
elseif (refvalue ~= nil) then
for idx, value in ipairs(self.m_Data) do
if (value[predicate] == refvalue) then
table.insert(result, value)
end
end
else
for idx, value in ipairs(self.m_Data) do
if (value[predicate] ~= nil) then
table.insert(result, value)
end
end
end
else
loge("where called with unknown predicate type");
end
return _new_lualinq(":where", result)
end
-- Returns a linq data structure where only items for whose the predicate has returned true are included, indexed version
function _whereIndex(self, predicate)
local result = { }
for idx, value in ipairs(self.m_Data) do
if (predicate(idx, value)) then
table.insert(result, value)
end
end
return _new_lualinq(":whereIndex", result)
end
-- Return a linq data structure with at most the first howmany elements
function _take(self, howmany)
return self:whereIndex(function(i, v) return i <= howmany; end)
end
-- Return a linq data structure skipping the first howmany elements
function _skip(self, howmany)
return self:whereIndex(function(i, v) return i > howmany; end)
end
-- Zips two collections together, using the specified join function
function _zip(self, otherlinq, joiner)
otherlinq = from(otherlinq)
local thismax = #self.m_Data
local thatmax = #otherlinq.m_Data
local result = {}
if (thatmax < thismax) then thismax = thatmax; end
for i = 1, thismax do
result[i] = joiner(self.m_Data[i], otherlinq.m_Data[i]);
end
return _new_lualinq(":zip", result)
end
-- Returns only distinct items, using an optional comparator
function _distinct(self, comparator)
local result = {}
comparator = comparator or function (v1, v2) return v1 == v2; end
for idx, value in ipairs(self.m_Data) do
local found = false
for _, value2 in ipairs(result) do
if (comparator(value, value2)) then
found = true
end
end
if (not found) then
table.insert(result, value)
end
end
return _new_lualinq(":distinct", result)
end
-- Returns the union of two collections, using an optional comparator
function _union(self, other, comparator)
return self:concat(from(other)):distinct(comparator)
end
-- Returns the difference of two collections, using an optional comparator
function _except(self, other, comparator)
other = from(other)
return self:where(function (v) return not other:contains(v, comparator) end)
end
-- Returns the intersection of two collections, using an optional comparator
function _intersection(self, other, comparator)
other = from(other)
return self:where(function (v) return other:contains(v, comparator) end)
end
-- Returns the difference of two collections, using a property accessor
function _exceptby(self, property, other)
other = from(other)
return self:where(function (v) return not other:contains(v[property]) end)
end
-- Returns the intersection of two collections, using a property accessor
function _intersectionby(self, property, other)
other = from(other)
return self:where(function (v) return other:contains(v[property]) end)
end
-- ============================================================
-- CONVERSION METHODS
-- ============================================================
-- Converts the collection to an array
function _toIterator(self)
local i = 0
local n = #self.m_Data
return function ()
i = i + 1
if i <= n then return self.m_Data[i] end
end
end
-- Converts the collection to an array
function _toArray(self)
return self.m_Data
end
-- Converts the collection to a table using a selector functions which returns key and value for each item
function _toDictionary(self, keyValueSelector)
local result = { }
for idx, value in ipairs(self.m_Data) do
local key, value = keyValueSelector(value)
if (key ~= nil) then
result[key] = value
end
end
return result
end
-- Converts the lualinq struct to a tuple
function _toTuple(self)
return unpack(self.m_Data)
end
-- ============================================================
-- TERMINATING METHODS
-- ============================================================
-- Return the first item or default if no items in the colelction
function _first(self, default)
if (#self.m_Data > 0) then
return self.m_Data[1]
else
return default
end
end
-- Return the last item or default if no items in the colelction
function _last(self, default)
if (#self.m_Data > 0) then
return self.m_Data[#self.m_Data]
else
return default
end
end
-- Returns true if any item satisfies the predicate. If predicate is null, it returns true if the collection has at least one item.
function _any(self, predicate)
if (predicate == nil) then return #self.m_Data > 0; end
for idx, value in ipairs(self.m_Data) do
if (predicate(value)) then
return true
end
end
return false
end
-- Returns true if all items satisfy the predicate. If predicate is null, it returns true if the collection is empty.
function _all(self, predicate)
if (predicate == nil) then return #self.m_Data == 0; end
for idx, value in ipairs(self.m_Data) do
if (not predicate(value)) then
return false
end
end
return true
end
-- Returns the number of items satisfying the predicate. If predicate is null, it returns the number of items in the collection.
function _count(self, predicate)
if (predicate == nil) then return #self.m_Data; end
local result = 0
for idx, value in ipairs(self.m_Data) do
if (predicate(value)) then
result = result + 1
end
end
return result
end
-- Prints debug data.
function _dump(self)
print(_dumpData(self));
end
-- Returns a random item in the collection, or default if no items are present
function _random(self, default)
if (#self.m_Data == 0) then return default; end
return self.m_Data[math.random(1, #self.m_Data)]
end
-- Returns true if the collection contains the specified item
function _contains(self, item, comparator)
comparator = comparator or function (v1, v2) return v1 == v2; end
for idx, value in ipairs(self.m_Data) do
if (comparator(value, item)) then return true; end
end
return false
end
-- Calls the action for each item in the collection. Action takes 1 parameter: the item value.
-- If the action is a string, it calls that method with the additional parameters
function _foreach(self, action, ...)
if (type(action) == "function") then
for idx, value in ipairs(self.m_Data) do
action(value, from({...}):toTuple())
end
elseif (type(action) == "string") then
for idx, value in ipairs(self.m_Data) do
value[action](value, from({...}):toTuple())
end
else
loge("foreach called with unknown action type");
end
return self
end
-- Calls the accumulator for each item in the collection. Accumulator takes 2 parameters: value and the previous result of
-- the accumulator itself (firstvalue for the first call) and returns a new result.
function _map(self, accumulator, firstvalue)
local result = firstvalue
for idx, value in ipairs(self.m_Data) do
result = accumulator(value, result)
end
return result
end
-- Calls the accumulator for each item in the collection. Accumulator takes 3 parameters: value, the previous result of
-- the accumulator itself (nil on first call) and the previous associated-result of the accumulator(firstvalue for the first call)
-- and returns a new result and a new associated-result.
function _xmap(self, accumulator, firstvalue)
local result = nil
local lastval = firstvalue
for idx, value in ipairs(self.m_Data) do
result, lastval = accumulator(value, result, lastval)
end
return result
end
-- Returns the max of a collection. Selector is called with values and should return a number. Can be nil if collection is of numbers.
function _max(self, selector)
if (selector == nil) then
selector = function(n) return n; end
end
return self:xmap(function(v, r, l) local res = selector(v); if (l == nil or res > l) then return v, res; else return r, l; end; end, nil)
end
-- Returns the min of a collection. Selector is called with values and should return a number. Can be nil if collection is of numbers.
function _min(self, selector)
if (selector == nil) then
selector = function(n) return n; end
end
return self:xmap(function(v, r, l) local res = selector(v); if (l == nil or res < l) then return v, res; else return r, l; end; end, nil)
end
-- Returns the sum of a collection. Selector is called with values and should return a number. Can be nil if collection is of numbers.
function _sum(self, selector)
if (selector == nil) then
selector = function(n) return n; end
end
return self:map(function(n, r) r = r + selector(n); return r; end, 0)
end
-- Returns the average of a collection. Selector is called with values and should return a number. Can be nil if collection is of numbers.
function _average(self, selector)
local count = self:count()
if (count > 0) then
return self:sum(selector) / count
else
return 0
end
end
return p