diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..1dcbdbd --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + luaVersion: ["5.4"] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - uses: leafo/gh-actions-lua@v9 + with: + luaVersion: ${{ matrix.luaVersion }} + + - uses: leafo/gh-actions-luarocks@v4 + + - name: Install dependencies + run: luarocks install simple_test + + - name: Run unit tests + run: | + LUA_PATH="./src/?.lua;./src/?/init.lua;;$(luarocks path --lr-path)" lua test.lua diff --git a/.gitignore b/.gitignore index 5334d64..bf547dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.rock +*.rockspec .luarocks_key diff --git a/.travis.yml b/.travis.yml index ce49346..75d3ce0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,11 +12,10 @@ before_install: - export PATH=$PATH:$PWD/lua_install/bin install: - - luarocks install luautf8 - luarocks install simple_test script: - - lua ./test.lua + - make test notifications: email: diff --git a/Makefile b/Makefile index 12ee6be..e8ddd6b 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ install_dependencies: luarocks install simple_test test: - lua test.lua + LUA_PATH="./src/?.lua;./src/?/init.lua;./src/array/?.lua;;" lua test.lua rockspec: bash $(task_folder)create_rockspec.sh $(version) diff --git a/README.md b/README.md index dee013a..0f4813b 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,12 @@ # array.lua -A small library with useful methods to handle Lua's table when it's working like an Array - -[![Build -Status](https://travis-ci.org/EvandroLG/array.lua.svg?branch=master)](https://travis-ci.org/EvandroLG/array.lua) +A small library with useful methods to handle Lua's table when it's working like an Array.
+For docs, see: https://evandrolg.github.io/array.lua/ ## Installation To install array, run: ```sh $ luarocks install array ``` -Or simply copy the array.lua file and paste in your project. - -## Methods -* array.is_array(object:table):boolean
-Checks if table is an Array - -* array.is_empty(object:table):boolean
-Checks if table is empty - -* array.slice(object:table, start:number, end:number):table
-Returns a shallow copy of a portion of a table into a new table - -* array.index_of(object:table, value:*):number
-Returns the index at which value can be found or -1 if value is not present - -* array.reverse(object:table):table
-Creates a new table with reverse values - -* array.first(object:table):*
-Returns the first value in a table - -* array.last(object:table):*
-Returns the last value in a table - -* array.max(object:table):*
-Returns the maximum value in a table - -* array.min(object:table):*
-Returns the minimum value in a table - -* array.map(object:table, callback:function):table
-Creates a new table of values by mapping each value in list through a transformation function - -* array.filter(object:table, callback:function):table
-Produces a new table containing all elements that pass truth test - -* array.reduce(object:table, callback:function [, memo]):*
-Applies a function against an accumulator and each value of the table to reduce it to a single value - -* array.reduce_right(object:table, callback:function [, memo]):*
-Works like `reduce` except that it iterates over table's elements from right to left - -* array.sum(object:table):number
-Returns the sum of the values of the table passed by parameter - -* array.concat(object:table, object:table):table
-Returns a new table by joining all values from the two tables - -* array.uniq(object:table):table
-Returns a new table by removing duplicates values - -* array.without(object:table, object:table):table
-Returns a copy of the table with all instances of the values removed - -* array.some(object:table, callback:function):boolean
-Tests whether at least one element in the table passes the test implemented by the callback - -* array.every(object:table, callback:function):boolean
-Tests whether all elements in the table passes the test implemented by the callback - -* array.zip(object:table, object:table):table
-Returns a table of the two supplied by pairing up equally-positioned elements from both tables - -* array.shallow_copy(object:table):table
-Returns a shallow copy of the table passed as parameter - -* array.deep_copy(object:table):table
-Returns a deep copy of the table passed as parameter - -* array.diff(object:table, object:table):table
-Returns a new table with the items which exist only in the first table - -* array.flat(object:table):table
-Creates a new table with the sub-table elements concatenated into it - -* array.fill(value:*, [start:number], end:number):table
-Creates a table filling all the elements from a start index (default -one) to an end index with a default value passed by parameter. -* array.remove(object:table, callback:function):table
-Removes all elements from table that `callback` returns truthy for and returns a new table with the removed elements +## License +[MIT](https://github.com/EvandroLG/array.lua/tree/master/LICENSE) diff --git a/array.lua b/array.lua deleted file mode 100644 index 86006ec..0000000 --- a/array.lua +++ /dev/null @@ -1,497 +0,0 @@ --- array --- author: Evandro Leopoldino Gonçalves --- https://github.com/evandrolg --- License: MIT - --- Helper function to check if value passed by parameter is a table --- @obj {table} --- @returns {boolean} -local function is_table(obj) - return type(obj) == 'table' -end - --- Raises error if @param is not an array --- @obj {table} --- @param {table} --- @method {string} --- @returns {void} -local function raises_error(obj, param, method) - assert(obj.is_array(param), string.format('%s expects an array', method)) -end - -local array - -array = { - __VERSION = '1.2.6', - __DESCRIPTION = "A small library with useful methods to handle Lua's table when it's working like an Array", - __LICENCE = [[ - The MIT License (MIT) - Copyright (c) 2017 Evandro Leopoldino Gonçalves - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - ]], - - -- Verify if table object works as an array - -- @obj {table} - -- @returns {boolean} - is_array = function(obj) - if not is_table(obj) then return false end - - local i = 0 - for _ in pairs(obj) do - i = i + 1 - if obj[i] == nil then return false end - end - - return true - end, - - -- Check if parameter is an empty table - -- @obj {table} - -- @returns {boolean} - is_empty = function(obj) - return array.is_array(obj) and #obj == 0 - end, - - -- Return a shallow copy of a portion of a table into a new table - -- @obj {table} - -- @start {number} start value - -- @finish {number} end value - -- @returns {boolean} - slice = function(obj, start, finish) - raises_error(array, obj, 'slice') - - if array.is_empty(obj) then return {} end - - local _start = start or 1 - - local output = {} - for i=_start, finish or #obj do - table.insert(output, obj[i]) - end - - return output - end, - - -- Return the index at which value can be found or -1 in case value is not present - -- @obj {table} - -- @value {*} - -- @returns {boolean} - index_of = function(obj, value) - raises_error(array, obj, 'index_of') - - for i=1, #obj do - if obj[i] == value then - return i - end - end - - return -1 - end, - - -- Create a new table with reverse values - -- @obj {table} - -- @returns {table} - reverse = function(obj) - raises_error(array, obj, 'reverse') - - local output = {} - - for i=#obj, 1, -1 do - table.insert(output, obj[i]) - end - - return output - end, - - -- Return first element from the table - -- @obj {table} - -- @returns {*} - first = function(obj) - raises_error(array, obj, 'first') - return obj[1] - end, - - -- Return last element from the table - -- @obj {table} - -- @returns {*} - last = function(obj) - raises_error(array, obj, 'last') - return obj[#obj] - end, - - -- Return maximum value from the table - -- @obj {table} - -- @returns {*} - max = function(obj) - raises_error(array, obj, 'max') - - local max = obj[1] - - for i=2, #obj do - if obj[i] > max then - max = obj[i] - end - end - - return max - end, - - -- Return minimum value from the table - -- @obj {table} - -- @returns {*} - min = function(obj) - raises_error(array, obj, 'min') - - local min = obj[1] - - for i=2, #obj do - if obj[i] < min then - min = obj[i] - end - end - - return min - end, - - -- Create a new table of values by mapping each value in table through a transformation function - -- @obj {table} - -- @callback {function} - -- @returns {*} - map = function(obj, callback) - raises_error(array, obj, 'map') - - local output = {} - - for i=1, #obj do - table.insert(output, callback(obj[i], i)) - end - - return output - end, - - -- Create a new table containing all elements that pass truth test - -- @obj {table} - -- @callback {function} - -- @returns {*} - filter = function(obj, callback) - raises_error(array, obj, 'filter') - - local output = {} - - for i=1, #obj do - if callback(obj[i], i) then - table.insert(output, obj[i]) - end - end - - return output - end, - - -- Applies a function against an accumulator and each value of the table to reduce it to a single value - -- @obj {table} - -- @callback {function} - -- @memo {*} - -- @returns {*} - reduce = function(obj, callback, memo) - raises_error(array, obj, 'reduce') - - local initialIndex = 1 - local _memo = memo - - if _memo == nil then - initialIndex = 2 - _memo = obj[1] - end - - for i=initialIndex, #obj do - _memo = callback(_memo, obj[i], i) - end - - return _memo - end, - - -- This function is like reduce except that it interates over table's elements from right to left - -- @obj {table} - -- @callback {function} - -- @memo {*} - -- @returns {*} - reduce_right = function(obj, callback, memo) - raises_error(array, obj, 'reduce_right') - - local initialIndex = #obj - local _memo = memo - - if _memo == nil then - initialIndex = initialIndex - 1 - _memo = obj[#obj] - end - - for i=initialIndex, 1, -1 do - _memo = callback(_memo, obj[i], i) - end - - return _memo - end, - - -- Return the sum of the values in table - -- @obj {table} - -- @callback {function} - -- @returns {number} - sum = function(obj) - raises_error(array, obj, 'sum') - - return array.reduce(obj, function(memo, value) - return memo + value - end) - end, - - -- Return a new table joining all values from the two tables passed by parameter - -- @obj {table} - -- @obj2 {table} - -- @returns {table} - concat = function(obj, obj2) - raises_error(array, obj, 'concat') - raises_error(array, obj2, 'concat') - - local output = {} - - for i=1, #obj do - table.insert(output, obj[i]) - end - - for i=1, #obj2 do - table.insert(output, obj2[i]) - end - - return output - end, - - -- Create a new table, removing duplicates values - -- @obj {table} - -- @returns {table} - uniq = function(obj) - raises_error(array, obj, 'uniq') - - local output = {} - local seen = {} - - for i=1, #obj do - local value = obj[i] - if not seen[value] then - seen[value] = true - table.insert(output, value) - end - end - - return output - end, - - -- Return a copy of the table with all instances of the values removed - -- @obj {table} - -- @values {table} - -- @returns {table} - without = function(obj, values) - raises_error(array, obj, 'without') - - local output = {} - - for i=1, #obj do - if array.index_of(values, obj[i]) == -1 then - table.insert(output, obj[i]) - end - end - - return output - end, - - -- Tests if at least one element in the table passes the test implemented by the callback - -- @obj {table} - -- @callback {function} - -- @returns {boolean} - some = function(obj, callback) - raises_error(array, obj, 'some') - - for i=1, #obj do - if callback(obj[i], i) then - return true - end - end - - return false - end, - - -- Return a table of the two supplied by pairing up equally-positioned elements from both tables - -- @obj {table} - -- @obj2 {table} - -- @returns {table} - zip = function(obj1, obj2) - raises_error(array, obj1, 'zip') - raises_error(array, obj2, 'zip') - - local output = {} - local size = #obj1 > #obj2 and #obj2 or #obj1 - - for i=1, size do - table.insert(output, { obj1[i], obj2[i] }) - end - - return output - end, - - -- Return a table of the two supplied by pairing up equally-positioned elements from both tables - -- @obj {table} - -- @obj2 {table} - -- @returns {table} - every = function(obj, callback) - raises_error(array, obj, 'every') - - for i=1, #obj do - if not callback(obj[i], i) then - return false - end - end - - return true - end, - - -- Returns a shallow copy of the table passed as parameter - -- @obj {table} - -- @returns {table} - shallow_copy = function(obj) - raises_error(array, obj, 'shallow_copy') - - local output = {} - - for i=1, #obj do - table.insert(output, obj[i]) - end - - return output - end, - - -- Return a deep copy of the table passed as parameter - -- @value {*} - -- @returns {table} - deep_copy = function(value) - local output = value - - if is_table(value) then - output = {} - - for i=1, #value do - table.insert(output, array.deep_copy(value[i])) - end - end - - return output - end, - - -- Return a new table with the items which exist only in the first table - -- @obj {table} - -- @obj2 {table} - -- @returns {table} - diff = function(obj1, obj2) - raises_error(array, obj1, 'diff') - raises_error(array, obj2, 'diff') - - local output = {} - - for i=1, #obj1 do - local has_value = false - local value = obj1[i] - - for j=1, #obj2 do - if value == obj2[j] then - has_value = true - end - end - - if not has_value then - table.insert(output, value) - end - end - - return output - end, - - -- Create a new table with the sub-table elements concatenated into it - -- @obj {table} - -- @_memo {table} - -- @returns {table} - flat = function(obj, _memo) - local output = _memo or {} - - for i=1, #obj do - local value = obj[i] - - if is_table(value) then - array.flat(value, output) - else - table.insert(output, value) - end - end - - return output - end, - - -- Creates a table filling all the elements from a start index (default one) to an end index with a default value - -- @value {*} - -- @start_or_finish {number} - -- @finish {number} - -- @returns {table} - fill = function(value, start_or_finish, finish) - local output = {} - local item = value - local start = start_or_finish - local size = finish - - if finish == nil then - start = 1 - size = start_or_finish - end - - for i=start, size do - output[i] = item - end - - return output - end, - - -- Remove all elements from table that @callback returns thruthy for - -- and returns a new table with the removed items - -- @obj {table} - -- @callback {function} - -- @returns {@table} - remove = function(obj, callback) - local output = {} - local copy = array.deep_copy(obj) - - for i=1, #copy do - local value = copy[i] - - if callback(value, i) then - table.insert(output, value) - local index = array.index_of(obj, value) - table.remove(obj, index) - end - end - - return output - end -} - -return array diff --git a/src/array/init.lua b/src/array/init.lua new file mode 100644 index 0000000..210c5bd --- /dev/null +++ b/src/array/init.lua @@ -0,0 +1,672 @@ +local utils = require('array.utils') + +local array + +array = { + __VERSION = '1.3.6', + __DESCRIPTION = "A small library with useful methods to handle Lua's table when it's working like an Array", + __LICENSE = [[ + The MIT License (MIT) + Copyright (c) 2017 Evandro Leopoldino Gonçalves + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + ]], + + -- Verify if table object works as an array + -- @param obj {table} + -- @return {boolean} + is_array = function(obj) + if not utils.is_table(obj) then return false end + + local i = 0 + for _ in pairs(obj) do + i = i + 1 + if obj[i] == nil then return false end + end + + return true + end, + + -- Checks if parameter is an empty table + -- @param obj {table} + -- @return {boolean} + is_empty = function(obj) + return array.is_array(obj) and #obj == 0 + end, + + -- Returns a shallow copy of a portion of a table into a new table + -- @param obj {table} + -- @param start {number} start value + -- @param finish {number} end value + -- @return {boolean} + slice = function(obj, start, finish) + utils.raises_error(array, obj, 'slice') + + if array.is_empty(obj) or start == finish then return {} end + + local output = {} + local _finish = #obj + local _start = 1 + + if start >= 0 then + _start = start + elseif utils.is_nil(finish) and start < 0 then + _start = #obj + start + 1 + end + + if (finish and finish >= 0) then + _finish = finish - 1 + elseif finish and finish < 0 then + _finish = #obj + finish + end + + for i = _start, _finish do + table.insert(output, obj[i]) + end + + return output + end, + + -- Returns the index at which value can be found or -1 in case value is not present + -- @param obj {table} + -- @param value {*} + -- @return {number} + index_of = function(obj, value) + utils.raises_error(array, obj, 'index_of') + + for i=1, #obj do + if obj[i] == value then + return i + end + end + + return -1 + end, + + -- Return true whether table includes the values, otherwise it returns false + -- @param obj {table} + -- @param value {*} + -- @return {boolean} + includes = function(obj, value) + utils.raises_error(array, obj, 'includes') + + local index = array.index_of(obj, value) + + if index == -1 then return false end + return true + end, + + -- Create a new table with reverse values + -- @param obj {table} + -- @return {table} + reverse = function(obj) + utils.raises_error(array, obj, 'reverse') + + local output = {} + + for i=#obj, 1, -1 do + table.insert(output, obj[i]) + end + + return output + end, + + -- Return first element from the table + -- @param obj {table} + -- @return {*} + first = function(obj) + utils.raises_error(array, obj, 'first') + return obj[1] + end, + + -- Return last element from the table + -- @param obj {table} + -- @return {*} + last = function(obj) + utils.raises_error(array, obj, 'last') + return obj[#obj] + end, + + -- Return maximum value from the table + -- @param obj {table} + -- @return {*} + max = function(obj) + utils.raises_error(array, obj, 'max') + + local max = obj[1] + + for i=2, #obj do + if obj[i] > max then + max = obj[i] + end + end + + return max + end, + + -- Return minimum value from the table + -- @param obj {table} + -- @return {*} + min = function(obj) + utils.raises_error(array, obj, 'min') + + local min = obj[1] + + for i=2, #obj do + if obj[i] < min then + min = obj[i] + end + end + + return min + end, + + -- Create a new table of values by mapping each value in table through a transformation function + -- @param obj {table} + -- @param callback {function} + -- @return {*} + map = function(obj, callback) + utils.raises_error(array, obj, 'map') + + local initial_value = {} + local reducer = function(accumulator, current, i) + table.insert(accumulator, callback(current, i)) + return accumulator + end + + return array.reduce(obj, reducer, initial_value) + end, + + -- Create a new table containing all elements that pass truth test + -- @param obj {table} + -- @param callback {function} + -- @return {*} + filter = function(obj, callback) + utils.raises_error(array, obj, 'filter') + + local initial_value = {} + local reducer = function(accumulator, current) + if callback(current) then + table.insert(accumulator, current) + end + + return accumulator + end + + return array.reduce(obj, reducer, initial_value) + end, + + -- Applies a function against an accumulator and each value of the table to reduce it to a single value + -- @param obj {table} + -- @param callback {function} + -- @param memo {table} + -- @return {*} + reduce = function(obj, callback, memo) + utils.raises_error(array, obj, 'reduce') + + local initialIndex = 1 + local _memo = memo + + if _memo == nil then + initialIndex = 2 + _memo = obj[1] + end + + for i=initialIndex, #obj do + _memo = callback(_memo, obj[i], i) + end + + return _memo + end, + + -- This function is like reduce except that it interates over table's elements from right to left + -- @param obj {table} + -- @param callback {function} + -- @param memo {table} + -- @return {*} + reduce_right = function(obj, callback, memo) + utils.raises_error(array, obj, 'reduce_right') + + local initialIndex = #obj + local _memo = memo + + if _memo == nil then + initialIndex = initialIndex - 1 + _memo = obj[#obj] + end + + for i=initialIndex, 1, -1 do + _memo = callback(_memo, obj[i], i) + end + + return _memo + end, + + -- Return the sum of the values in table + -- @param obj {table} + -- @param callback {function} + -- @return {number} + sum = function(obj) + utils.raises_error(array, obj, 'sum') + + return array.reduce(obj, function(memo, value) + return memo + value + end) + end, + + -- Return a new table joining all values from N tables passed by parameter + -- @param arg {...} + -- @return {table} + concat = function(...) + local arg = {...} + local output = {} + + for i, list in ipairs(arg) do + for j=1, #list do + table.insert(output, list[j]) + end + end + + return output + end, + + -- Create a new table, removing duplicates values + -- @param obj {table} + -- @return {table} + uniq = function(obj) + utils.raises_error(array, obj, 'uniq') + + local output = {} + local seen = {} + + for i=1, #obj do + local value = obj[i] + if not seen[value] then + seen[value] = true + table.insert(output, value) + end + end + + return output + end, + + -- Return a copy of the table with all instances of the values removed + -- @param obj {table} + -- @param values {table} + -- @return {table} + without = function(obj, values) + utils.raises_error(array, obj, 'without') + + local initial_value = {} + local reducer = function(accumulator, current) + if (array.includes(values, current) == false) then + table.insert(accumulator, current) + end + + return accumulator + end + + return array.reduce(obj, reducer, initial_value) + end, + + -- Tests if at least one element in the table passes the test implemented by the callback + -- @param obj {table} + -- @param callback {function} + -- @return {boolean} + some = function(obj, callback) + utils.raises_error(array, obj, 'some') + + for i=1, #obj do + if callback(obj[i], i) then + return true + end + end + + return false + end, + + -- Return a table of the two supplied by pairing up equally-positioned elements from both tables + -- @param obj {table} + -- @param obj2 {table} + -- @return {table} + zip = function(obj1, obj2) + utils.raises_error(array, obj1, 'zip') + utils.raises_error(array, obj2, 'zip') + + local output = {} + local size = #obj1 > #obj2 and #obj2 or #obj1 + + for i=1, size do + table.insert(output, { obj1[i], obj2[i] }) + end + + return output + end, + + -- Run a predicate on each value. If the predicate evaluates to any false value, this function will immediately return false; otherwise, it returns true. + -- @param obj {table} + -- @param callback {function} + -- @return {boolean} + every = function(obj, callback) + utils.raises_error(array, obj, 'every') + + for i=1, #obj do + if not callback(obj[i], i) then + return false + end + end + + return true + end, + + -- return a shallow copy of the table passed as parameter + -- @param obj {table} + -- @return {table} + shallow_copy = function(obj) + utils.raises_error(array, obj, 'shallow_copy') + + local output = {} + + for i=1, #obj do + table.insert(output, obj[i]) + end + + return output + end, + + -- Return a deep copy of the table passed as parameter + -- @param value {*} + -- @return {table} + deep_copy = function(value) + local output = value + + if utils.is_table(value) then + output = {} + + for i=1, #value do + table.insert(output, array.deep_copy(value[i])) + end + end + + return output + end, + + -- Return a new table with the items which exist only in the first table + -- @param obj {table} + -- @param obj2 {table} + -- @return {table} + diff = function(obj1, obj2) + utils.raises_error(array, obj1, 'diff') + utils.raises_error(array, obj2, 'diff') + + local output = {} + local hash = utils.convert_to_hash(obj2) + + for i=1, #obj1 do + local value = obj1[i] + + if not hash[value] then + table.insert(output, value) + end + end + + return output + end, + + -- Create a new table with the sub-table elements concatenated into it up to the specific depth + -- @param obj {table, depth} + -- @return {table} + flat = function(obj, depth) + if depth == nil then + depth = 1 / 0 -- Infinity + end + + return array.reduce(obj, function(acc, item) + if array.is_array(item) and depth > 0 then + return array.concat( + acc, + array.flat(item, depth - 1) + ) + else + table.insert(acc, item) + return acc + end + end, {}); + end, + + -- Creates a table filling all the elements from a start index (default one) to an end index with a default value + -- @param value {*} + -- @param start_or_finish {number} + -- @param finish {number} + -- @return {table} + fill = function(value, start_or_finish, finish) + local output = {} + local item = value + local start = start_or_finish + local size = finish + + if finish == nil then + start = 1 + size = start_or_finish + end + + for i=start, size do + output[i] = item + end + + return output + end, + + -- Remove all elements from table that @callback return thruthy for + -- and return a new table with the removed items + -- @param obj {table} + -- @param callback {function} + -- @return {table} + remove = function(obj, callback) + local output = {} + local copy = array.deep_copy(obj) + + for i=1, #copy do + local value = copy[i] + + if callback(value, i) then + table.insert(output, value) + local index = array.index_of(obj, value) + table.remove(obj, index) + end + end + + return output + end, + + -- Return a new table in hash structure, where keys represent each array value + -- and value represent the number of times the same item was found in the list + -- @param obj {table} + -- @return {table} + counter = function(obj) + local output = {} + + for i=1, #obj do + local value = obj[i] + output[value] = (output[value] and output[value] or 0) + 1 + end + + return output + end, + + -- Return a new table with the values that exist in both tables + -- @param obj1 {table} + -- @param obj2 {table} + -- @return {table} + intersect = function(obj1, obj2) + local output = {} + local count1 = array.counter(obj1) + local count2 = array.counter(obj2) + + for k, v in pairs(count1) do + if count2[k] then + local new_array = array.fill( + k, + utils.lowest_value(v, count2[k]) + ) + + utils.multiple_inserts(output, new_array) + end + end + + return output + end, + + -- Return a table composed from key-value pairs + -- @param obj1 {table} + -- @param obj2 {table} + -- @return {table} + from_pairs = function(obj) + utils.raises_error(array, obj, 'from_pairs') + + local output = {} + + for i=1, #obj do + local item = obj[i] + output[item[1]] = item[2] + end + + return output + end, + + -- Executes callback once for each table element + -- @param obj {table} + -- @param callback {function} + -- @return {void} + each = function(obj, callback) + for i=1, #obj do + callback(obj[i], i) + end + end, + + -- Executes callback once for each table element in reverse order + -- @param obj {table} + -- @param callback {function} + -- @return {void} + reverse_each = function(obj, callback) + for i = #obj, 1, -1 do + callback(obj[i], i) + end + end, + + -- Returns a new table composed by keys created from the results of running each element through `callback` + -- @param obj {table} + -- @param callback {function} + -- @return {table} + group_by = function(obj, callback) + utils.raises_error(array, obj, 'group_by') + + function reducer(accumulator, current) + local result = callback(current) + if not accumulator[result] then + accumulator[result] = {} + end + + table.insert(accumulator[result], current) + + return accumulator + end + + return array.reduce(obj, reducer, {}) + end, + + -- Returns a value from a random key of the given array + -- @param obj {table} + -- @return {*} + random = function(obj) + utils.raises_error(array, obj, 'random') + return obj[math.random(#obj)] + end, + + -- Creates a new table returning all permutations of length of the elements of the given table + -- @param obj {table} + -- @return {table} + permutation = function(obj) + local output = {} + + if #obj == 1 then + return { obj } + end + + local partial_permutations = array.permutation(array.slice(obj, 2)) + local first = obj[1] + + for i=1, #partial_permutations do + local partial = partial_permutations[i] + + for j=1, #partial+1 do + local permutation_front = array.slice(partial, 1, j) + local permutation_after = array.slice(partial, j) + + table.insert(output, array.concat(permutation_front, { first }, permutation_after)) + end + end + + return output + end, + + -- Returns a new array-like table with elements splitted into groups of length of `size` + -- @param obj {table} + -- @param size {number} + -- @return {table} + chunk = function(obj, size) + local output = {} + + for _, v in pairs(obj) do + local last = output[#output] + + if utils.is_nil(last) or #last == size then + table.insert(output, { v }) + else + table.insert(last, v) + end + end + + return output + end, + + -- Returns a new array-like table by applying a given callback to each element of the table, and then flattening the result by one level + -- @param obj {table} + -- @param callback {function} + -- @return {table} + flat_map = function(obj, callback) + utils.raises_error(array, obj, 'flat_map') + local mapped = array.map(obj, callback) + + return array.flat(mapped) + end, + + -- Creates a new table composed of keys generated from the results of running each element of the given table through the given callback + -- @param obj {table} + -- @param callback {function} + -- @return {table} + key_by = function(obj, callback) + utils.raises_error(array, obj, 'key_by') + + return array.reduce(obj, function(accumulator, current) + local key = callback(current) + accumulator[key] = current + return accumulator + end, {}) + end +} + +return array diff --git a/src/array/utils.lua b/src/array/utils.lua new file mode 100644 index 0000000..5f1c472 --- /dev/null +++ b/src/array/utils.lua @@ -0,0 +1,66 @@ +return { + -- Helper function to check if value passed by parameter is a table + -- @obj {table} + -- @returns {boolean} + is_table = function(obj) + return type(obj) == 'table' + end, + + -- Raises error if @param is not an array + -- @obj {table} + -- @param {table} + -- @method {string} + -- @returns {void} + raises_error = function(obj, param, method) + assert(obj.is_array(param), string.format('%s expects an array', method)) + end, + + -- Returns lowest value between two values + -- @a {number} + -- @b {number} + -- @returns number + lowest_value = function(a, b) + return a < b and a or b + end, + + -- Makes multiple inserts in a table (array-like) + -- @obj {table} + -- @values {table} + -- @returns {void} + multiple_inserts = function(obj, values) + for i=1, #values do + local value = values[i] + table.insert(obj, value) + end + end, + + -- Convert array to hash table + -- @obj {table} + -- @returns {table} + convert_to_hash = function(obj) + local output = {} + + for i=1, #obj do + local value = obj[i] + output[value] = true + end + + return output + end, + + -- Loop through table and print all values + -- @obj {table} + -- @returns {nil} + print_items = function(obj) + for i=1, #obj do + print(obj[i]) + end + end, + + -- Checks wether value is nil + -- @obj {any} + -- @returns {boolean} + is_nil = function(value) + return value == nil + end +} diff --git a/tasks/default_rockspec b/tasks/default_rockspec index 76a68a3..46e57fa 100644 --- a/tasks/default_rockspec +++ b/tasks/default_rockspec @@ -20,6 +20,7 @@ dependencies = { build = { type = "builtin", modules = { - ['array'] = "array.lua" + ["array"] = "src/array/init.lua", + ["array.utils"] = "src/array/utils.lua" } } diff --git a/test.lua b/test.lua index 350ef62..e5b5a3f 100644 --- a/test.lua +++ b/test.lua @@ -1,355 +1,448 @@ -local array = require 'array' local test = require 'simple_test' +local array = require 'array' test('meta infos', function(a) - a.equal(array.__VERSION, '1.2.6') + a.equal(array.__VERSION, '1.3.6') a.equal(array.__DESCRIPTION, "A small library with useful methods to handle Lua's table when it's working like an Array") end) -test('is_array should return false when object is not a table', function(a) - a.equal(array.is_array('lua'), false) -end) - -test('is_array should return false when the table is working like a dictionary', function(a) - a.equal(array.is_array({ language='lua' }), false) -end) - -test('is_array should return true when table is empty', function(a) - a.equal(array.is_array({}), true) -end) - -test('is_array should return true when table is working like an array', function(a) - a.equal(array.is_array({ 'a', 'b', 'c', 'd' }), true) -end) - -test('is_empty should return false when table has at least one item', function(a) - a.equal(array.is_empty({ 'a' }), false) +test('is_array', function(a) + a.not_ok(array.is_array('lua')) + a.not_ok(array.is_array({ language='lua' })) + a.ok(array.is_array({})) + a.ok(array.is_array({ 'a', 'b', 'c', 'd' })) end) -test('is_empty should return false when table does not have any item', function(a) - a.equal(array.is_empty({}), true) +test('is_empty', function(a) + a.not_ok(array.is_empty({ 'a' })) + a.ok(array.is_empty({})) end) -test('first should return first item from table', function(a) +test('first', function(a) a.equal(array.first({ 'a', 'b', 'c', 'd' }), 'a') end) -test('last should return last item from table', function(a) +test('last', function(a) a.equal(array.last({ 'a', 'b', 'c', 'd' }), 'd') end) -test('slice should return an empty table when it does not have any element', function(a) +test('slice', function(a) a.equal(#array.slice({}, 1, 2), 0) -end) -test('slice should return a table with values between start index and end index', function(a) - local result = array.slice({ 'lua', 'javascript', 'python', 'ruby', 'c' }, 2, 4) + a.deep_equal( + array.slice({ 'lua', 'javascript', 'python', 'ruby', 'c' }, 2, 5), + { 'javascript', 'python', 'ruby' } + ) - a.equal(type(result), 'table') - a.equal(#result, 3) - a.equal(result[1], 'javascript') - a.equal(result[2], 'python') - a.equal(result[3], 'ruby') -end) + a.deep_equal( + array.slice({ 'lua', 'javascript', 'python', 'ruby', 'c' }, 2), + { 'javascript', 'python', 'ruby', 'c' } + ) -test('slice should return a table with every values from start index until last index', function(a) - local result = array.slice({ 'lua', 'javascript', 'python', 'ruby', 'c' }, 2) + a.deep_equal( + array.slice({ 'lua', 'javascript', 'python', 'ruby', 'c' }, -2), + { 'ruby', 'c' } + ) - a.equal(type(result), 'table') - a.equal(#result, 4) - a.equal(result[1], 'javascript') - a.equal(result[2], 'python') - a.equal(result[3], 'ruby') - a.equal(result[4], 'c') + a.deep_equal( + array.slice({ 'lua', 'javascript', 'python', 'ruby', 'c' }, 2, -2), + { 'javascript', 'python' } + ) end) -test('reverse should return an inverted table', function(a) - local result = array.reverse({ 'lua', 'javascript', 'python' }) - - a.equal(type(result), 'table') - a.equal(#result, 3) - a.equal(result[1], 'python') - a.equal(result[2], 'javascript') - a.equal(result[3], 'lua') +test('reverse', function(a) + a.deep_equal( + array.reverse({ 'lua', 'javascript', 'python' }), + { 'python', 'javascript', 'lua' } + ) end) -test('map should return a table with 2, 4, 6 values', function(a) - local result = array.map({ 1, 2, 3 }, function(value) - return value * 2 - end) - - a.equal(type(result), 'table') - a.equal(#result, 3) - a.equal(result[1], 2) - a.equal(result[2], 4) - a.equal(result[3], 6) +test('map', function(a) + a.deep_equal( + array.map({ 1, 2, 3 }, function(value) + return value * 2 + end), + { 2, 4, 6 } + ) end) -test('filter should return a table with 10, 15, 20 values', function(a) - local result = array.filter({ 15, 10, 5, 3, 20 }, function(value) - return value >= 10 - end) - - a.equal(type(result), 'table') - a.equal(#result, 3) - a.equal(result[1], 15) - a.equal(result[2], 10) - a.equal(result[3], 20) +test('filter', function(a) + a.deep_equal( + array.filter({ 15, 10, 5, 3, 20 }, function(value) + return value >= 10 + end), + { 15, 10, 20 } + ) end) -test('max should return the biggest value from a table', function(a) +test('max', function(a) a.equal(array.max({ 20, 22, 1, 3, 30, 42 }), 42) -end) - -test('max should return nil when table is empty', function(a) a.equal(array.max({}), nil) end) -test('min should return the smallest value from a table', function(a) +test('min', function(a) a.equal(array.min({ 20, 22, 1, 3, 30, 42 }), 1) -end) - -test('min should return nil when table is empty', function(a) a.equal(array.min({}), nil) end) -test('reduce should return 90', function(a) - local result = array.reduce({ 20, 30, 40 }, function(memo, value) - return memo + value - end) +test('reduce', function(a) + a.equal( + array.reduce({ 20, 30, 40 }, function(memo, value) + return memo + value + end), + 90 + ) - a.equal(result, 90) + a.equal( + array.reduce({ 'a', 'b', 'c', 'd', 'e' }, function(memo, value) + return memo .. value + end), + 'abcde' + ) end) -test('reduce should return 100', function(a) - local result = array.reduce({ 20, 30, 40 }, function(memo, value) - return memo + value - end, 10) +test('reduce_right', function(a) + a.equal( + array.reduce_right({ 20, 30, 40 }, function(memo, value) + return memo + value + end), + 90 + ) - a.equal(result, 100) + a.equal( + array.reduce_right({ 'a', 'b', 'c', 'd', 'e' }, function(memo, value) + return memo .. value + end), + 'edcba' + ) end) -test('reduce should return first item', function(a) - local result = array.reduce({ 20 }, function(memo, value) - return memo + value - end) - - a.equal(result, 20) +test('sum', function(a) + a.equal( + array.sum({10, 20, 30, 40, 50}), + 150 + ) end) -test('reduce should concatenate all items', function(a) - local result = array.reduce({ 'a', 'b', 'c', 'd', 'e' }, function(memo, value) - return memo .. value - end) +test('index_of', function(a) + a.equal( + array.index_of({ 20, 30, 40, 50 }, 40), + 3 + ) - a.equal(result, 'abcde') + a.equal( + array.index_of({ 20, 30, 40 }, 50), + -1 + ) end) -test('reduce_right should concatenate all items starting from right to left', function(a) - local result = array.reduce_right({ 'a', 'b', 'c', 'd', 'e' }, function(memo, value) - return memo .. value - end) - - a.equal(result, 'edcba') -end) +test('concat', function(a) + a.deep_equal( + array.concat({ 1, 2, 3 }, { 4, 5, 6 }), + { 1, 2, 3, 4, 5, 6 } + ) -test('reduce_right should return 100', function(a) - local result = array.reduce_right({ 20, 30, 40 }, function(memo, value) - return memo + value - end, 10) + a.deep_equal( + array.concat({ 'a', 'b', 'c' }, { 'd', 'e' }, { 'f', 'g' }), + { 'a', 'b', 'c', 'd', 'e', 'f', 'g' } + ) - a.equal(result, 100) + a.deep_equal( + array.concat({ 1, 2, 3 }, { 4, 5 }, { 6, 7 }, { 8, 9 }), + { 1, 2, 3, 4, 5, 6, 7, 8, 9 } + ) end) -test('reduce_right', function(a) - local result = array.reduce_right({ 'a', 'b', 'c', 'd', 'e' }, function(memo, value) - return memo .. value - end) - - a.equal(result, 'edcba') +test('uniq', function(a) + a.deep_equal( + array.uniq({ 'a', 'b', 'a', 'b', 'c', 'd' }), + { 'a', 'b', 'c', 'd' } + ) end) -test('sum should return sum of the values in table', function(a) - local result = array.sum({10, 20, 30, 40, 50}) - a.equal(result, 150) +test('without', function(a) + a.deep_equal( + array.without({ 10, 20, 30, 10, 4 }, { 10, 4 }), + { 20, 30 } + ) end) -test('index_of should return correct position of value in the table', function(a) - a.equal(array.index_of({ 20, 30, 40, 50 }, 40), 3) -end) +test('some', function(a) + a.ok( + array.some({'a', 'b', 'c'}, function(element) + if (element == 'b') then return true end + end) + ) -test('index_of should return -1 when the value is not in the table', function(a) - a.equal(array.index_of({ 20, 30, 40 }, 50), -1) + a.not_ok( + array.some({'a', 'b', 'c'}, function(element) + if (element == 'd') then return true end + end) + ) end) -test('concat should join the two array', function(a) - local result = array.concat({ 1, 2, 3 }, { 4, 5, 6 }) +test('zip', function(a) + a.deep_equal( + array.zip({ 'a', 'b' }, { 'A', 'B', 'C' }), + { + { 'a', 'A' }, + { 'b', 'B' } + } + ) - a.equal(#result, 6) - a.equal(result[4], 4) + a.deep_equal( + array.zip({ 'a', 'b' }, { 'A', 'B' }), + { + { 'a', 'A' }, + { 'b', 'B' } + } + ) end) -test('uniq should return every value once', function(a) - local result = array.uniq({ 'a', 'b', 'a', 'b', 'c', 'd' }) +test('every', function(a) + a.not_ok( + array.every({ 10, 20, 30 }, function(value) + return value > 10 + end) + ) - a.equal(#result, 4) - a.equal(result[1], 'a') - a.equal(result[2], 'b') - a.equal(result[3], 'c') - a.equal(result[4], 'd') + a.ok( + array.every({ 10, 20, 30 }, function(value) + return value >= 10 + end) + ) end) -test('without should return all values less the items from second array', function(a) - local result = array.without({ 10, 20, 30, 10, 4 }, { 10, 4 }) +test('shallow_copy', function(a) + local obj = { + { 'a' }, + { 'b' } + } - a.equal(#result, 2) - a.equal(result[1], 20) - a.equal(result[2], 30) + a.deep_equal( + array.shallow_copy(obj), + obj + ) end) -test('some should return true when exist at least one match', function(a) - local result = array.some({'a', 'b', 'c'}, function(element) - if (element == 'b') then return true end - end) +test('deep_copy', function(a) + local obj = { + { 'a' }, + { 'b' } + } - a.ok(result) -end) + local result = array.deep_copy(obj) -test('some should return true when exist at least one match', function(a) - local result = array.some({'a', 'b', 'c'}, function(element) - if (element == 'd') then return true end - end) + a.equal(#result, #obj) + a.equal(result[1][1], obj[1][1]) + a.equal(result[2][1], obj[2][1]) + + obj[1][1] = 'c' + a.equal(result[1][1], 'a') - a.not_ok(result) + obj[1] = 'c' + a.equal(type(result[1]), 'table') end) -test('zip should return a new table with the correct pairs', function(a) - local result = array.zip({ 'a', 'b' }, { 'A', 'B' }) +test('diff', function(a) + local result = array.diff( + { 'a', 'b', 'g', 'z', 'h' }, + { 'a', 'c', 'd', 'e', 'f', 'h', 'x' } + ) - a.equal(result[1][1], 'a') - a.equal(result[1][2], 'A') - a.equal(result[2][1], 'b') - a.equal(result[2][2], 'B') + a.deep_equal(result, { 'b', 'g', 'z' }) end) -test('zip should not consider values without pairs', function(a) - local result = array.zip({ 'a', 'b' }, { 'A', 'B', 'C' }) +test('flat', function(a) + a.deep_equal( + array.flat({ 'a', 'b', 'c', { 'd', 'e', 'f', { 'g', 'h' } }, { 'i' }, 'j' }), + { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' } + ) - a.equal(#result, 2) - a.equal(#result[1], 2) - a.equal(#result[2], 2) - a.equal(result[1][1], 'a') - a.equal(result[1][2], 'A') - a.equal(result[2][1], 'b') - a.equal(result[2][2], 'B') + a.deep_equal( + array.flat({ 'a', 'b', 'c', { 'd', 'e', 'f', { 'g', 'h' } }, { 'i' }, 'j' }, 1), + { 'a', 'b', 'c', 'd', 'e', 'f', { 'g', 'h' }, 'i', 'j' } + ) + + a.deep_equal( + array.flat({ 'a', 'b', 'c', { 'd', 'e', { 'f', { 'g', 'h' } } }, { 'i' }, 'j' }, 2), + { 'a', 'b', 'c', 'd', 'e', 'f', { 'g', 'h' }, 'i', 'j' } + ) end) -test('every should return true when all elements in the table pass the callback test', function(a) - local result = array.every({ 10, 20, 30 }, function(value) - return value >= 10 - end) +test('fill', function(a) + local value = 'Lua' + local result = array.fill(value, 3, 4) + a.equal(result[3], value) + a.equal(result[4], value) - a.ok(result) + a.deep_equal( + array.fill(value, 3), + { value, value, value } + ) end) -test('every should return false when at least a match fails', function(a) - local result = array.every({ 10, 20, 30 }, function(value) - return value > 10 +test('remove', function(a) + local list = { 1, 2, 3, 4 } + local result = array.remove(list, function(value) + return math.fmod(value, 2) == 0 end) - a.not_ok(result) -end) + a.deep_equal( + list, + { 1, 3 } + ) -test('shallow_copy should return a shallow copy of the array passed as parameter', function(a) - local obj = { - { 'a' }, - { 'b' } - } + a.deep_equal( + result, + { 2, 4 } + ) +end) - local result = array.shallow_copy(obj) +test('counter', function(a) + a.deep_equal( + array.counter({ 'a', 'b', 'a', 'a', 'c', 'b' }), + { a = 3, b = 2, c = 1 } + ) +end) - a.equal(#result, #obj) - a.equal(result[1][1], obj[1][1]) - a.equal(result[2][1], obj[2][1]) +test('intersect', function(a) + local first_list = { 'a', 'b', 'a', 'c', 'e', 'f', 'a' } + local second_list = { 'b', 'a', 'd', 'a' } - obj[1][1] = 'c' - a.equal(result[1][1], obj[1][1]) + a.equal( + #array.intersect(first_list, second_list), + 3 + ) +end) - obj[1] = 'c' - a.equal(type(result[1]), 'table') +test('from_pairs', function(a) + a.deep_equal( + array.from_pairs({ {'a', 1}, {'b', 2} }), + { a = 1, b = 2 } + ) end) -test('deep_copy should return a deep copy of the array passed as parameter', function(a) - local obj = { - { 'a' }, - { 'b' } - } +test('includes', function(a) + local list = { 'a', 'b', 'c' } - local result = array.deep_copy(obj) + a.ok( + array.includes(list, 'c') + ) - a.equal(#result, #obj) - a.equal(result[1][1], obj[1][1]) - a.equal(result[2][1], obj[2][1]) + a.not_ok( + array.includes(list, 'd') + ) +end) - obj[1][1] = 'c' - a.equal(result[1][1], 'a') +test('each', function(a) + local list = { 'a', 'b', 'c' } + local values = {} + local keys = {} - obj[1] = 'c' - a.equal(type(result[1]), 'table') + array.each(list, function(v, k) + table.insert(values, v) + table.insert(keys, k) + end) + + a.deep_equal(list, values) + a.deep_equal(keys, { 1, 2, 3 }) end) -test('diff should return a new table with the items which exist only in first table', function(a) - local result = array.diff( - { 'a', 'b' }, - { 'a', 'c', 'd' } - ) +test('reverse_each', function(a) + local values = {} + local keys = {} - a.equal(#result, 1) - a.equal(result[1], 'b') + array.reverse_each({ 'a', 'b', 'c' }, function(v, k) + table.insert(values, v) + table.insert(keys, k) + end) + + a.deep_equal(values, { 'c', 'b', 'a' }) + a.deep_equal(keys, { 3, 2, 1 }) end) -test('flat should return a new table that is an one-dimensional flatting of the table passed by parameter', function(a) - local obj = { 'a', 'b', 'c', { 'd', 'e', 'f', { 'g', 'h' } }, { 'i' }, 'j' } - local result = array.flat(obj) +test('group_by', function(a) + a.deep_equal( + array.group_by({ 6.1, 4.1, 6.3, 4.4, 5.1 }, function(item) return math.floor(item) end), + { + [4] = { 4.1, 4.4 }, + [6] = { 6.1, 6.3 }, + [5] = { 5.1 } + } + ) - a.equal(#result, 10) + a.deep_equal( + array.group_by({ 'one', 'two', 'three', 'four' }, function(item) return #item end), + { + [3] = { 'one', 'two' }, + [5] = { 'three' }, + [4] = { 'four' } + } + ) end) -test('fill should create a table with the size passed by parameter and fill every item using the value passed as argument', function(a) - local value = 1 - local size = 3 - local result = array.fill(value, size) +test('random', function(a) + local list = { 'a', 'b', 'c' } - a.equal(#result, size) - a.equal(result[1], value) - a.equal(result[2], value) - a.equal(result[3], value) + a.ok( + array.includes(list, array.random(list)) + ) + + a.equal(array.random({ 'a' }), 'a') end) -test('fill should create a table using the value passed by argument from start to end', function(a) - local value = 'a' - local start = 3 - local finish = 4 - local result = array.fill(value, start, finish) +test('permutation', function(a) + a.deep_equal( + array.permutation({ 'lua', 'javascript' }), + { + { 'lua', 'javascript' }, + { 'javascript', 'lua' }, + } + ) - a.equal(result[1], nil) - a.equal(result[2], nil) - a.equal(result[3], value) - a.equal(result[4], value) + a.deep_equal( + array.permutation({ 'lua', 'javascript', 'ruby' }), + { + { 'lua', 'javascript', 'ruby' }, + { 'javascript', 'lua', 'ruby' }, + { 'javascript', 'ruby', 'lua' }, + { 'lua', 'ruby', 'javascript' }, + { 'ruby', 'lua', 'javascript' }, + { 'ruby', 'javascript', 'lua' }, + } + ) end) -test('remove should delete items that callback returns thruthy and should return a new table with the removed items', function(a) - local list = { 1, 2, 3, 4 } - local result = array.remove(list, function(value) - return math.fmod(value, 2) == 0 - end) +test('chunk', function(a) + a.deep_equal( + array.chunk({ 'a', 'b', 'c', 'd', 'e', 'f', 'g' }, 3), + { + { 'a', 'b', 'c' }, + { 'd', 'e', 'f' }, + { 'g' }, + } + ) +end) - a.equal(#list, 2) - a.equal(list[1], 1) - a.equal(list[2], 3) +test('flat_map', function(a) + a.deep_equal( + array.flat_map({ 1, 2, 3 }, function(value) + return { value, value * 2 } + end), + { 1, 2, 2, 4, 3, 6 } + ) +end) - a.equal(#result, 2) - a.equal(result[1], 2) - a.equal(result[2], 4) +test('key_by', function(a) + a.deep_equal( + array.key_by({ { id = 1, name = 'lua' }, { id = 2, name = 'javascript' } }, function(item) + return item.id + end), + { + [1] = { id = 1, name = 'lua' }, + [2] = { id = 2, name = 'javascript' } + } + ) end)