1258 lines
33 KiB
Lua
1258 lines
33 KiB
Lua
-- collections.lua - A robust collection class based on Laravel collections
|
|
--
|
|
-- @module collections.lua
|
|
-- @alias Collection
|
|
-- @url https://github.com/ImLiam/Lua-Collections
|
|
|
|
local Collection = {
|
|
version = '0.2.0'
|
|
}
|
|
|
|
--- Returns all elements from a collection as a table
|
|
function Collection:all()
|
|
local tbl = {}
|
|
for key, value in pairs(self.table) do
|
|
tbl[key] = value
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
--- Adds an item to the end of a collection
|
|
function Collection:append(value)
|
|
table.insert(self.table, value)
|
|
return self
|
|
end
|
|
|
|
--- Returns the average value of a list or given key
|
|
function Collection:average(key)
|
|
local count = self:count()
|
|
if count > 0 then
|
|
return self:sum(key) / count
|
|
end
|
|
end
|
|
|
|
--- Breaks the collection into multiple smaller collections of a given size
|
|
function Collection:chunk(count)
|
|
local chunks = self:new()
|
|
local currentChunk = {}
|
|
|
|
if count <= 0 then
|
|
return Collection:new({ {} })
|
|
end
|
|
|
|
for _, value in pairs(self.table) do
|
|
table.insert(currentChunk, value)
|
|
if #currentChunk == count then
|
|
chunks:push(currentChunk)
|
|
currentChunk = {}
|
|
end
|
|
end
|
|
if #currentChunk > 0 then
|
|
chunks:push(currentChunk)
|
|
end
|
|
return chunks
|
|
end
|
|
|
|
--- Returns a copy of the collection
|
|
function Collection:clone()
|
|
local cloned = {}
|
|
for key, value in pairs(self.table) do
|
|
cloned[key] = value
|
|
end
|
|
return self:new(cloned)
|
|
end
|
|
|
|
--- Collapses a collection of tables into a single, flat collection
|
|
function Collection:collapse()
|
|
local collapsed = self:new()
|
|
for _, value in pairs(self.table) do
|
|
for _, innerValue in pairs(value) do
|
|
collapsed:push(innerValue)
|
|
end
|
|
end
|
|
return collapsed
|
|
end
|
|
|
|
--- Combines the keys of the collection with the values of another table
|
|
function Collection:combine(values)
|
|
local combined = self:new()
|
|
for key, value in pairs(values) do
|
|
if self.table[key] then
|
|
combined:set(self.table[key], value)
|
|
end
|
|
end
|
|
return combined
|
|
end
|
|
|
|
--- Determines whether the collection contains a given item
|
|
function Collection:contains(containValue, recursive)
|
|
|
|
local function checkContains(key, value)
|
|
if type(containValue) == 'function' then
|
|
local result = containValue(key, value)
|
|
if result then
|
|
return true
|
|
end
|
|
else
|
|
if value == containValue then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
for key, value in pairs(self.table) do
|
|
if type(value) == 'table' and recursive then
|
|
for innerKey, innerValue in pairs(value) do
|
|
if checkContains(innerKey, innerValue) then
|
|
return true
|
|
end
|
|
end
|
|
else
|
|
if checkContains(key, value) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Turns an associative table into an indexed one, removing string keys
|
|
function Collection:convertToIndexed()
|
|
local notAssociative = self:new()
|
|
for _, value in pairs(self.table) do
|
|
notAssociative:push(value)
|
|
end
|
|
return notAssociative
|
|
end
|
|
|
|
--- Returns the total number of items in the collection
|
|
function Collection:count()
|
|
local i = 0
|
|
for _ in pairs(self.table) do
|
|
i = i + 1
|
|
end
|
|
return i
|
|
end
|
|
|
|
--- Deals the collection into a number of groups in order, one at a time
|
|
function Collection:deal(hands)
|
|
local splitted = self:times(hands, function() return {} end)
|
|
local currentSection = 1
|
|
|
|
for _, value in pairs(self.table) do
|
|
table.insert(splitted.table[currentSection], value)
|
|
|
|
currentSection = currentSection + 1
|
|
if currentSection > #splitted.table then
|
|
currentSection = 1
|
|
end
|
|
end
|
|
|
|
return splitted
|
|
end
|
|
|
|
--- Compares a collection against another table based on its values
|
|
--- Returns the values in the original collection that are not present in the given table
|
|
function Collection:diff(difference)
|
|
local differenceList = {}
|
|
for _, value in pairs(difference) do
|
|
differenceList[value] = true
|
|
end
|
|
|
|
local finalDifferences = self:new()
|
|
for _, value in pairs(self.table) do
|
|
if not differenceList[value] then
|
|
finalDifferences:push(value)
|
|
end
|
|
end
|
|
return finalDifferences
|
|
end
|
|
|
|
--- Compares the collection against another table based on its keys
|
|
--- Returns the key / value pairs in the original collection that are not present in the table
|
|
function Collection:diffKeys(difference)
|
|
local differenceList = {}
|
|
for key in pairs(difference) do
|
|
differenceList[key] = true
|
|
end
|
|
|
|
local finalDifferences = self:new()
|
|
for key, value in pairs(self.table) do
|
|
if not differenceList[key] then
|
|
finalDifferences:set(key, value)
|
|
end
|
|
end
|
|
return finalDifferences
|
|
end
|
|
|
|
--- Iterates over the items in the collection and passes each to a callback
|
|
function Collection:each(callback)
|
|
for key, value in pairs(self.table) do
|
|
if callback(key, value) == false then
|
|
break
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Iterates over the numerically indexed items in the collection and passes each to a callback
|
|
function Collection:eachi(callback)
|
|
for key, value in ipairs(self.table) do
|
|
if callback(key, value) == false then
|
|
break
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Compares a table with the internal table of the collection
|
|
function Collection:equals(tbl, ignoreMetatables, tbl2)
|
|
tbl2 = tbl2 or self.table
|
|
if tbl == tbl2 then return true end
|
|
local tblType = type(tbl)
|
|
local tbl2Type = type(tbl2)
|
|
if tblType ~= tbl2Type then return false end
|
|
if tblType ~= 'table' then return false end
|
|
|
|
if not ignoreMetatables then
|
|
local mt1 = getmetatable(tbl)
|
|
if mt1 and mt1.__eq then
|
|
return tbl == tbl2
|
|
end
|
|
end
|
|
|
|
local keySet = {}
|
|
|
|
for key1, value1 in pairs(tbl) do
|
|
local value2 = tbl2[key1]
|
|
if value2 == nil or self:equals(value1, ignoreMetatables, value2) == false then
|
|
return false
|
|
end
|
|
keySet[key1] = true
|
|
end
|
|
|
|
for key2, _ in pairs(tbl2) do
|
|
if not keySet[key2] then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- Verify that all elements of the collection pass a truth test
|
|
function Collection:every(callback)
|
|
for key, value in pairs(self.table) do
|
|
if not callback(key, value) then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- Returns all items in the collection except for those with specified keys
|
|
function Collection:except(keys)
|
|
local exceptList = {}
|
|
for _, value in pairs(keys) do
|
|
exceptList[value] = true
|
|
end
|
|
|
|
local tbl = self:new()
|
|
for key, value in pairs(self.table) do
|
|
if not exceptList[key] then
|
|
tbl:set(key, value)
|
|
end
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
--- Internal function used to determine if a value is falsey
|
|
function Collection.falsyValue(_, value)
|
|
for _, v in ipairs({0, false, ''}) do
|
|
if v == value then
|
|
return true
|
|
end
|
|
end
|
|
|
|
if type(value) == 'table' then
|
|
if next(value) == nil then
|
|
return true
|
|
end
|
|
end
|
|
|
|
if not value then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Filters the collection using the given callback, keeping only items that pass a truth test
|
|
function Collection:filter(callback)
|
|
local filtered = self:new()
|
|
for key, value in pairs(self.table) do
|
|
local response = false
|
|
if callback then
|
|
response = callback(key, value)
|
|
elseif not self:falsyValue(value) then
|
|
response = true
|
|
end
|
|
if response then
|
|
filtered:set(key, value)
|
|
end
|
|
end
|
|
return filtered
|
|
end
|
|
|
|
--- Returns the first element in the collection, or that passes a truth test
|
|
function Collection:first(callback)
|
|
for key, value in pairs(self.table) do
|
|
if callback then
|
|
if callback(key, value) then
|
|
return value
|
|
end
|
|
else
|
|
return value
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Flattens a multi-dimensional collection into a single dimension
|
|
function Collection:flatten(depth, tbl, currentDepth)
|
|
local flattened = self:new()
|
|
local iterable = tbl or self.table
|
|
currentDepth = currentDepth or 0
|
|
for _, value in pairs(iterable) do
|
|
if type(value) == 'table'
|
|
and ((depth and currentDepth < depth - 1) or not depth) then
|
|
local flatInside = self:flatten(depth, value, currentDepth + 1):all()
|
|
for _, v in pairs(flatInside) do
|
|
flattened:push(v)
|
|
end
|
|
else
|
|
flattened:push(value)
|
|
end
|
|
end
|
|
if tbl then
|
|
return flattened
|
|
else
|
|
return flattened
|
|
end
|
|
end
|
|
|
|
--- Swaps the collection's keys with their corresponding values
|
|
function Collection:flip()
|
|
local flipped = self:new()
|
|
for key, value in pairs(self.table) do
|
|
flipped:set(value, key)
|
|
end
|
|
return flipped
|
|
end
|
|
|
|
--- Removes an item from the collection by its key
|
|
function Collection:forget(key)
|
|
if self.table[key] then
|
|
self.table[key] = nil
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Returns a collection containing the items that would be present for a given page number
|
|
function Collection:forPage(pageNumber, perPage)
|
|
local page = self:new()
|
|
local i = 1
|
|
for _, value in pairs(self.table) do
|
|
if i > (pageNumber - 1) * perPage and i <= pageNumber * perPage then
|
|
page:push(value)
|
|
end
|
|
i = i + 1
|
|
end
|
|
return page
|
|
end
|
|
|
|
--- Returns the item of a given key
|
|
function Collection:get(key, default)
|
|
if self.table[key] then
|
|
return self.table[key]
|
|
elseif default then
|
|
if type(default) == 'function' then
|
|
return default(key)
|
|
else
|
|
return default
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Groups the collection's items by a given key
|
|
function Collection:groupBy(groupKey)
|
|
local grouped = self:new()
|
|
for _, value in pairs(self.table) do
|
|
|
|
local currentGroupKey = groupKey
|
|
|
|
if value[currentGroupKey] then
|
|
if not grouped:has(value[currentGroupKey]) then
|
|
grouped:set(value[currentGroupKey], {})
|
|
end
|
|
table.insert(grouped.table[value[currentGroupKey]], value)
|
|
end
|
|
|
|
end
|
|
return grouped
|
|
end
|
|
|
|
--- Determines if a given key exists in the collection
|
|
function Collection:has(key)
|
|
if self.table[key] then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Joins the items in a collection into a string
|
|
function Collection:implode(implodedKey, delimeter)
|
|
if type(self:first()) == 'table' then
|
|
local toImplode = {}
|
|
for _, value in pairs(self.table) do
|
|
if value[implodedKey] then
|
|
table.insert(toImplode, value[implodedKey])
|
|
end
|
|
end
|
|
return table.concat(toImplode, delimeter or ', ')
|
|
else
|
|
return table.concat(self.table, implodedKey or ', ')
|
|
end
|
|
end
|
|
|
|
--- Inserts a value at a given numeric index
|
|
function Collection:insert(value, position)
|
|
table.insert(self.table, position, value)
|
|
return self
|
|
end
|
|
|
|
--- Removes any values from the original collection that are not present in the passed table
|
|
function Collection:intersect(intersection)
|
|
local intersected = self:new()
|
|
intersection = Collection:new(intersection):flip():all()
|
|
|
|
for key, value in pairs(self.table) do
|
|
if intersection[value] then
|
|
intersected:set(key, value)
|
|
end
|
|
end
|
|
|
|
return intersected
|
|
end
|
|
|
|
--- Determines whether the collection is associative
|
|
function Collection:isAssociative()
|
|
if self:count() > #self.table then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Determines if the collection is empty
|
|
function Collection:isEmpty()
|
|
if next(self.table) == nil then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Determines if the collection is not empty
|
|
function Collection:isNotEmpty()
|
|
if next(self.table) ~= nil then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Keys the collection by the given key
|
|
function Collection:keyBy(keyName)
|
|
local keyed = self:new()
|
|
for key, value in pairs(self.table) do
|
|
if type(keyName) == 'function' then
|
|
local response = keyName(key, value)
|
|
keyed:set(response, value)
|
|
else
|
|
keyed:set(value[keyName], value)
|
|
end
|
|
end
|
|
return keyed
|
|
end
|
|
|
|
--- Returns a list of the collection's keys
|
|
function Collection:keys()
|
|
local keys = self:new()
|
|
for key in pairs(self.table) do
|
|
keys:push(key)
|
|
end
|
|
return keys
|
|
end
|
|
|
|
--- Returns the last element in the collection, or that passes a truth test
|
|
function Collection:last(callback)
|
|
local currentValue
|
|
for key, value in pairs(self.table) do
|
|
if callback then
|
|
if callback(key, value) then
|
|
currentValue = value
|
|
end
|
|
else
|
|
currentValue = value
|
|
end
|
|
end
|
|
return currentValue
|
|
end
|
|
|
|
--- Iterates through the collection and passes each value to the callback, which can then modify the values
|
|
function Collection:map(callback)
|
|
local remapped = self:new()
|
|
for key, value in pairs(self.table) do
|
|
local newKey, newValue = callback(key, value)
|
|
remapped:set(newKey, newValue)
|
|
end
|
|
return remapped
|
|
end
|
|
|
|
--- Iterates through the the collection and remaps the key and value based on the return of a callback
|
|
function Collection:mapWithKeys(callback)
|
|
local mapped = self:new()
|
|
for key, value in pairs(self.table) do
|
|
local k, v = callback(key, value)
|
|
mapped:set(k, v)
|
|
end
|
|
return mapped
|
|
end
|
|
|
|
--- Returns the maximum value of a set of given values
|
|
function Collection:max(maxKey)
|
|
local max
|
|
for _, value in pairs(self.table) do
|
|
if maxKey then
|
|
if not max or value[maxKey] > max then
|
|
max = value[maxKey]
|
|
end
|
|
else
|
|
if not max or value > max then
|
|
max = value
|
|
end
|
|
end
|
|
end
|
|
return max
|
|
end
|
|
|
|
--- Returns the median value of a set of given values
|
|
function Collection:median(medianKey)
|
|
local all = {}
|
|
for _, value in pairs(self.table) do
|
|
if medianKey then
|
|
table.insert(all, value[medianKey])
|
|
else
|
|
table.insert(all, value)
|
|
end
|
|
end
|
|
table.sort(all, function(a, b) return a < b end)
|
|
|
|
if math.fmod(#all, 2) == 0 then
|
|
return (all[#all / 2] + all[(#all / 2) + 1] ) / 2
|
|
else
|
|
return all[math.ceil(#all/2)]
|
|
end
|
|
end
|
|
|
|
--- Merges the given table with the original collection
|
|
function Collection:merge(toMerge)
|
|
local merged = self:clone()
|
|
for key, value in pairs(toMerge) do
|
|
if type(key) == 'number' then
|
|
merged:push(value)
|
|
else
|
|
merged:set(key, value)
|
|
end
|
|
end
|
|
return merged
|
|
end
|
|
|
|
--- Returns the minimum value of a set of given values
|
|
function Collection:min(minKey)
|
|
local min
|
|
for _, value in pairs(self.table) do
|
|
if minKey then
|
|
if not min or value[minKey] < min then
|
|
min = value[minKey]
|
|
end
|
|
else
|
|
if not min or value < min then
|
|
min = value
|
|
end
|
|
end
|
|
end
|
|
return min
|
|
end
|
|
|
|
--- Returns the mode value of a given key
|
|
function Collection:mode(modeKey)
|
|
local counts = {}
|
|
|
|
for _, value in pairs(self.table) do
|
|
if modeKey then
|
|
value = value[modeKey]
|
|
end
|
|
if counts[value] == nil then
|
|
counts[value] = 1
|
|
else
|
|
counts[value] = counts[value] + 1
|
|
end
|
|
end
|
|
|
|
local biggestCount = 0
|
|
|
|
for _, value in pairs(counts) do
|
|
if value > biggestCount then
|
|
biggestCount = value
|
|
end
|
|
end
|
|
|
|
local temp = self:new()
|
|
|
|
for key, value in pairs(counts) do
|
|
if value == biggestCount then
|
|
temp:push(key)
|
|
end
|
|
end
|
|
|
|
return temp
|
|
end
|
|
|
|
--- Creates a new collection instance
|
|
function Collection.new(_, tbl)
|
|
return setmetatable({ table = tbl or {} }, { __index = Collection, __tostring = Collection.toString })
|
|
end
|
|
|
|
--- Creates a new collection consisting of every nth element
|
|
function Collection:nth(step, offset)
|
|
local nth = self:new()
|
|
local position = 1
|
|
offset = (offset and offset + 1) or 1
|
|
|
|
for _, value in pairs(self.table) do
|
|
if position % step == offset then
|
|
nth:push(value)
|
|
end
|
|
position = position + 1
|
|
end
|
|
|
|
return nth
|
|
end
|
|
|
|
--- Returns the items in the collection with the specified keys
|
|
function Collection:only(keys)
|
|
local onlyList = {}
|
|
for _, value in pairs(keys) do
|
|
onlyList[value] = true
|
|
end
|
|
|
|
local tbl = self:new()
|
|
for key, value in pairs(self.table) do
|
|
if onlyList[key] then
|
|
tbl:set(key, value)
|
|
end
|
|
end
|
|
return tbl
|
|
end
|
|
|
|
--- Returns a pair of elements that pass and fail a given truth test
|
|
function Collection:partition(callback)
|
|
local valid = Collection:new()
|
|
local invalid = Collection:new()
|
|
|
|
for key, value in pairs(self.table) do
|
|
local result = callback(key, value)
|
|
if result then
|
|
valid:push(value)
|
|
else
|
|
invalid:push(value)
|
|
end
|
|
end
|
|
|
|
return valid, invalid
|
|
end
|
|
|
|
-- Passes the collection to the given callback and returns the result
|
|
function Collection:pipe(callback)
|
|
return callback(self)
|
|
end
|
|
|
|
--- Retrives all of the values for a given key
|
|
function Collection:pluck(valueName, keyName)
|
|
local plucked = self:new()
|
|
for _, value in pairs(self.table) do
|
|
if value[valueName] then
|
|
if keyName then
|
|
plucked:set(value[keyName], value[valueName])
|
|
else
|
|
plucked:push(value[valueName])
|
|
end
|
|
end
|
|
end
|
|
return plucked
|
|
end
|
|
|
|
--- Removes and returns the last item from the collection
|
|
function Collection:pop()
|
|
return table.remove(self.table, #self.table)
|
|
end
|
|
|
|
--- Adds an item to the beginning of the collection
|
|
function Collection:prepend(value)
|
|
table.insert(self.table, 1, value)
|
|
return self
|
|
end
|
|
|
|
--- Removes and returns an item from the collection by key
|
|
function Collection:pull(key)
|
|
if type(key) == 'number' then
|
|
return table.remove(self.table, key)
|
|
else
|
|
local pulled = self.table[key]
|
|
self.table[key] = nil
|
|
return pulled
|
|
end
|
|
end
|
|
|
|
--- Sets the given key and value in the collection
|
|
function Collection:put(key, value)
|
|
self.table[key] = value
|
|
return self
|
|
end
|
|
|
|
|
|
--- Returns a random item or number of items from the collection
|
|
function Collection:random(count, rep)
|
|
local all = self:new(self.table):convertToIndexed():all()
|
|
local random = self:new()
|
|
count = count or 1
|
|
|
|
if count < 0 then
|
|
error('Positive number expected, negative number given.')
|
|
end
|
|
|
|
for _ = 1, count do
|
|
if #all > 0 then
|
|
local randomElement
|
|
if rep then
|
|
randomElement = all[math.random(#all)]
|
|
else
|
|
randomElement = table.remove(all, math.random(#all))
|
|
end
|
|
random:push(randomElement)
|
|
end
|
|
end
|
|
|
|
if count == 1 and random[1] then
|
|
return random[1]
|
|
end
|
|
return random
|
|
end
|
|
|
|
--- Reduces the collection to a single value, passing the result of each iteration into the next
|
|
function Collection:reduce(callback, default)
|
|
local carry = default
|
|
for _, value in pairs(self.table) do
|
|
carry = callback(carry, value)
|
|
end
|
|
return carry
|
|
end
|
|
|
|
--- Filters the collection using the given fallback
|
|
function Collection:reject(callback)
|
|
local notRejected = self:new()
|
|
for key, value in pairs(self.table) do
|
|
local rejected = false
|
|
if callback then
|
|
rejected = callback(key, value)
|
|
elseif not self:falsyValue(value) then
|
|
rejected = true
|
|
end
|
|
if not rejected then
|
|
notRejected:set(key, value)
|
|
end
|
|
end
|
|
return notRejected
|
|
end
|
|
|
|
--- Fixes numerical keys to put them in order
|
|
function Collection:resort()
|
|
local sorted = self:new()
|
|
for key, value in pairs(self.table) do
|
|
if type(key) == 'number' then
|
|
sorted:push(value)
|
|
else
|
|
sorted[key] = value
|
|
end
|
|
end
|
|
return sorted
|
|
end
|
|
|
|
--- Reverses the order of the numerical keys in the collection
|
|
function Collection:reverse()
|
|
local reversed = self:new()
|
|
for key, value in pairs(self.table) do
|
|
if type(key) == 'number' then
|
|
reversed:prepend(value)
|
|
else
|
|
reversed:set(key, value)
|
|
end
|
|
end
|
|
return reversed
|
|
end
|
|
|
|
--- Searches the collection for a value and returns the key
|
|
function Collection:search(callback)
|
|
for key, value in pairs(self.table) do
|
|
if type(callback) == 'function' then
|
|
local result = callback(key, value)
|
|
if result then
|
|
return key
|
|
end
|
|
else
|
|
if callback == value then
|
|
return key
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Removes and returns the first item from the collection
|
|
function Collection:shift()
|
|
for key, value in pairs(self.table) do
|
|
if type(key) == 'number' then
|
|
return table.remove(self.table, key)
|
|
else
|
|
self.table[key] = nil
|
|
return value
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Randomly shuffles the order of items in the collection
|
|
function Collection:shuffle()
|
|
local shuffled = self:new()
|
|
local numericKeys = {}
|
|
local numericValues = {}
|
|
for key, value in pairs(self.table) do
|
|
if type(key) == 'number' then
|
|
table.insert(numericKeys, key)
|
|
table.insert(numericValues, value)
|
|
end
|
|
end
|
|
|
|
for key, value in pairs(self.table) do
|
|
if type(key) == 'number' then
|
|
shuffled:set(table.remove(numericKeys, math.random(#numericKeys)), table.remove(numericValues, math.random(#numericValues)))
|
|
-- todo: make this push into a random spot in the array
|
|
else
|
|
shuffled:set(key, value)
|
|
end
|
|
end
|
|
return shuffled
|
|
end
|
|
|
|
--- Returns a slice of the collection at the given index
|
|
function Collection:slice(index, size)
|
|
local slice = self:new()
|
|
local i = 0
|
|
for key, value in ipairs(self.table) do
|
|
if key >= index + 1 then
|
|
slice:append(value)
|
|
if size then
|
|
i = i + 1
|
|
if i == size then
|
|
return slice
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return slice
|
|
end
|
|
|
|
--- Sorts the items in the collection
|
|
function Collection:sort(callback)
|
|
local sorted = self:clone()
|
|
if callback and type(callback) == 'function' then
|
|
table.sort(sorted.table, callback)
|
|
elseif callback then
|
|
table.sort(sorted.table, function(a, b) return a[callback] < b[callback] end)
|
|
else
|
|
table.sort(sorted.table, function(a, b) return a < b end)
|
|
end
|
|
return sorted
|
|
end
|
|
|
|
--- Same as the Collection:sort() method, but returns the collection in the opposite order
|
|
function Collection:sortDesc(callback)
|
|
local sorted = self:clone()
|
|
if callback and type(callback) == 'function' then
|
|
table.sort(sorted.table, callback)
|
|
sorted = sorted:reverse()
|
|
elseif callback then
|
|
table.sort(sorted.table, function(a, b) return a[callback] > b[callback] end)
|
|
else
|
|
table.sort(sorted.table, function(a, b) return a > b end)
|
|
end
|
|
return sorted
|
|
end
|
|
|
|
--- Removes and returns a slice of items starting at the specified index
|
|
function Collection:splice(index, size, replacements)
|
|
local spliced = self:new()
|
|
local toRemove = {}
|
|
local i = 0
|
|
for key, value in ipairs(self.table) do
|
|
if key >= index + 1 then
|
|
|
|
spliced:append(value)
|
|
table.insert(toRemove, key)
|
|
|
|
if size then
|
|
i = i + 1
|
|
if i == size then
|
|
break
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
local removedIndex = 0
|
|
for _, key in pairs(toRemove) do
|
|
if type(key) == 'number' then
|
|
table.remove(self.table, key + removedIndex)
|
|
removedIndex = removedIndex - 1
|
|
else
|
|
self.table[key] = nil
|
|
end
|
|
end
|
|
|
|
if replacements then
|
|
for _ = 1, #replacements do
|
|
self:insert(table.remove(replacements, #replacements), index + 1)
|
|
end
|
|
end
|
|
|
|
return spliced
|
|
end
|
|
|
|
--- Breaks the collection into the given number of groups
|
|
function Collection:split(count)
|
|
local groupSize = math.ceil(self:count() / count)
|
|
return self:chunk(groupSize)
|
|
end
|
|
|
|
--- Returns the sum of items in the collection
|
|
function Collection:sum(key)
|
|
local sum = 0
|
|
for i, value in pairs(self.table) do
|
|
if key then
|
|
if value[key] then
|
|
sum = sum + value[key]
|
|
else
|
|
error('Value "' .. key .. '" does not exist in collection object with key "' .. i .. '"')
|
|
end
|
|
else
|
|
sum = sum + value
|
|
end
|
|
end
|
|
return sum
|
|
end
|
|
|
|
--- Returns a collection with the specified number of items
|
|
function Collection:take(count)
|
|
local taken = self:new()
|
|
if count >= 0 then
|
|
for i = 1, count do
|
|
if self.table[i] then
|
|
taken:append(self.table[i])
|
|
else
|
|
break
|
|
end
|
|
end
|
|
else
|
|
local iterations = 0
|
|
for i = #self.table, 1, -1 do
|
|
if self.table[i] then
|
|
taken:prepend(self.table[i])
|
|
else
|
|
break
|
|
end
|
|
iterations = iterations + 1
|
|
if iterations == -count then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
return taken
|
|
end
|
|
|
|
--- Internal function used to determine if a table is associative
|
|
function Collection.tableIsAssociative(_, tbl)
|
|
local totalCount = 0
|
|
for _ in pairs(tbl) do
|
|
totalCount = totalCount + 1
|
|
end
|
|
if totalCount > #tbl then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Internal method used by the Collection:toJSON method to recursively convert tables
|
|
function Collection:tableToJSON(tbl)
|
|
local jsonRepresentation = function(value)
|
|
if type(value) == 'table' then
|
|
return self:tableToJSON(value)
|
|
elseif type(value) == 'string' then
|
|
return '"' .. value:gsub('"', '\\"') .. '"'
|
|
elseif type(value) == 'number' then
|
|
return value
|
|
elseif type(value) == 'boolean' then
|
|
return (value and 'true' or 'false')
|
|
end
|
|
end
|
|
|
|
local jsonElements = {}
|
|
if self:tableIsAssociative(tbl) then
|
|
for key, value in pairs(tbl) do
|
|
local json = jsonRepresentation(value)
|
|
if json then
|
|
json = '"' .. key:gsub('"', '\\"') .. '":' .. json
|
|
table.insert(jsonElements, json)
|
|
end
|
|
end
|
|
return '{' .. table.concat(jsonElements, ',') .. '}'
|
|
else
|
|
for _, value in pairs(tbl) do
|
|
local json = jsonRepresentation(value)
|
|
table.insert(jsonElements, json)
|
|
end
|
|
return '[' .. table.concat(jsonElements, ',') .. ']'
|
|
end
|
|
end
|
|
|
|
--- Internal method used by the Collection:toString method to recursively convert tables
|
|
function Collection:tableToString(tbl)
|
|
local luaRepresentation = function(value)
|
|
if type(value) == 'table' then
|
|
return self:tableToString(value)
|
|
elseif type(value) == 'string' then
|
|
return '"' .. value .. '"'
|
|
elseif type(value) == 'number' then
|
|
return value
|
|
elseif type(value) == 'boolean' then
|
|
return (value and 'true' or 'false')
|
|
end
|
|
end
|
|
|
|
local luaElements = {}
|
|
|
|
for key, value in pairs(tbl) do
|
|
local luaString = luaRepresentation(value)
|
|
if luaString then
|
|
if type(key) == 'number' then
|
|
luaString = '[' .. key .. ']=' .. luaString
|
|
elseif type(key) == 'string' then
|
|
if key:match('%W') then
|
|
luaString = '["' .. key:gsub('"', '\\"') .. '"]=' .. luaString
|
|
else
|
|
luaString = key .. '=' .. luaString
|
|
end
|
|
end
|
|
table.insert(luaElements, luaString)
|
|
end
|
|
end
|
|
return '{' .. table.concat(luaElements, ',') .. '}'
|
|
end
|
|
|
|
--- Executes the given callback without affecting the collection itself
|
|
function Collection:tap(callback)
|
|
callback(self)
|
|
return self
|
|
end
|
|
|
|
--- Creates a new collection by invoking the callback a given amount of times
|
|
function Collection:times(count, callback)
|
|
local tbl = {}
|
|
for i = 1, count do
|
|
table.insert(tbl, callback(i, tbl))
|
|
end
|
|
return self:new(tbl)
|
|
end
|
|
|
|
--- Returns a reference to the underlying table of the collection
|
|
function Collection:toTable()
|
|
return self.table
|
|
end
|
|
|
|
--- Returns a JSON string representation of the collection's values
|
|
function Collection:toJSON()
|
|
return self:tableToJSON(self.table)
|
|
end
|
|
|
|
--- Returns a string representation of a Lua table
|
|
function Collection:toString()
|
|
return self:tableToString(self.table)
|
|
end
|
|
|
|
--- Iterates over the collection and calls the given callback with each item in the collection, replacing the values in the collection with the response
|
|
function Collection:transform(callback)
|
|
local transformed = self:new()
|
|
for key, value in pairs(self.table) do
|
|
transformed:set(key, callback(key, value))
|
|
end
|
|
return transformed
|
|
end
|
|
|
|
--- Adds the given table to the collection
|
|
function Collection:union(tbl)
|
|
local unionised = self:clone()
|
|
for key, value in pairs(tbl) do
|
|
if not unionised:has(key) then
|
|
unionised:set(key, value)
|
|
end
|
|
end
|
|
return unionised
|
|
end
|
|
|
|
--- Returns all of the unique items in the collection
|
|
function Collection:unique(callback)
|
|
local unique = self:new()
|
|
local keyList = {}
|
|
|
|
for key, value in pairs(self.table) do
|
|
local valueToCheck = value
|
|
if type(callback) == 'function' then
|
|
valueToCheck = callback(key, value)
|
|
elseif callback then
|
|
valueToCheck = value[callback]
|
|
end
|
|
if keyList[valueToCheck] ~= nil then
|
|
keyList[valueToCheck] = false
|
|
else
|
|
keyList[valueToCheck] = key
|
|
end
|
|
end
|
|
|
|
for _, key in pairs(keyList) do
|
|
if key ~= false then
|
|
unique:push(self:get(key))
|
|
end
|
|
end
|
|
|
|
return unique
|
|
end
|
|
|
|
--- Executes the given callback when a condition is met
|
|
function Collection:when(condition, callback)
|
|
if condition then
|
|
callback(self)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Filters the collection by a given key / value pair
|
|
function Collection:where(filterKey, filterValue)
|
|
local filtered = self:new()
|
|
for _, value in pairs(self.table) do
|
|
if value[filterKey] == filterValue then
|
|
filtered:push(value)
|
|
end
|
|
end
|
|
return filtered
|
|
end
|
|
|
|
--- Filters the collection by a given key / value contained within the given table
|
|
function Collection:whereIn(filterKey, filterValues)
|
|
local filtered = self:new()
|
|
for _, value in pairs(self.table) do
|
|
for _, filterValue in ipairs(filterValues) do
|
|
if value[filterKey] == filterValue then
|
|
filtered:push(value)
|
|
end
|
|
end
|
|
end
|
|
return filtered
|
|
end
|
|
|
|
--- Filters the collection by a given key / value not contained within the given table
|
|
function Collection:whereNotIn(filterKey, filterValues)
|
|
local filtered = self:new()
|
|
for _, value in pairs(self.table) do
|
|
local allowed = true
|
|
for _, filterValue in ipairs(filterValues) do
|
|
if value[filterKey] == filterValue then
|
|
allowed = false
|
|
end
|
|
end
|
|
if allowed then
|
|
filtered:push(value)
|
|
end
|
|
end
|
|
return filtered
|
|
end
|
|
|
|
--- Merges the value of the given table to the value of the original collection at the same index
|
|
function Collection:zip(values)
|
|
local zipped = self:clone()
|
|
for key, value in pairs(values) do
|
|
if zipped:has(key) then
|
|
zipped:set(key, {zipped:get(key), value})
|
|
end
|
|
end
|
|
return zipped
|
|
end
|
|
|
|
|
|
|
|
-----[[ ALIASES FOR OTHER FUNCTIONS ]]-----
|
|
|
|
|
|
|
|
--- Alias for the Collection:average() method
|
|
Collection.avg = Collection.average
|
|
|
|
--- Alias for the Collection:average() method
|
|
Collection.mean = Collection.average
|
|
|
|
--- Alias for the Collection:each() method
|
|
Collection.forEach = Collection.each
|
|
|
|
--- Alias for the Collection:eachi() method
|
|
Collection.forEachi = Collection.eachi
|
|
|
|
--- Alias for the Collection:forget() method
|
|
Collection.remove = Collection.forget
|
|
|
|
--- Alias for the Collection:convertToIndexed() method
|
|
Collection.deassociate = Collection.convertToIndexed
|
|
|
|
--- Alias for the Collection:append() method
|
|
Collection.push = Collection.append
|
|
|
|
--- Alias for the Collection:put() method
|
|
Collection.set = Collection.put
|
|
|
|
--- Alias for the Collection:resort() method
|
|
Collection.values = Collection.resort
|
|
|
|
--- Alias for the Collection:sort() method
|
|
Collection.sortAsc = Collection.sort
|
|
|
|
--- Alias for the Collection:average() method
|
|
Collection.replace = Collection.splice
|
|
|
|
return setmetatable(
|
|
{ new = Collection.new },
|
|
{ __call = Collection.new }
|
|
)
|