diff --git a/lib/underscore.lua b/lib/underscore.lua
index 7415190..46db233 100644
--- a/lib/underscore.lua
+++ b/lib/underscore.lua
@@ -59,6 +59,27 @@ function Underscore.range(start_i, end_i, step)
return Underscore:new(range_iter)
end
+function Underscore.rangeV2(start_i, end_i, step)
+ if end_i == nil then
+ end_i = start_i
+ start_i = 1
+ end
+ step = step or 1
+ return coroutine.wrap(function()
+ for i=start_i, end_i, step do
+ coroutine.yield(i)
+ end
+ end)
+end
+
+-- adds a universal table iterator
+Underscore.table_iterator = function(table)
+ return coroutine.wrap(function()
+ for key, value in pairs(table) do
+ coroutine.yield({key = key, value = value}) end
+ end)
+end
+
--- Identity function. This function looks useless, but is used throughout Underscore as a default.
-- @name _.identity
-- @param value any object
@@ -82,12 +103,19 @@ end
-- iter
-function Underscore.funcs.each(list, func)
- for i in Underscore.iter(list) do
- func(i)
+Underscore.funcs.each = (function()
+ local inner
+ inner = function(iter, func)
+ local _ = iter()
+ if _ then
+ func(_)
+ return inner(iter, func) end
end
- return list
-end
+ local each = function(list_or_iter, func)
+ inner(Underscore.iter(list_or_iter), func)
+ return list_or_iter end
+ return each
+end)()
function Underscore.funcs.map(list, func)
local mapped = {}
@@ -100,7 +128,7 @@ end
function Underscore.funcs.reduce(list, memo, func)
for i in Underscore.iter(list) do
memo = func(memo, i)
- end
+ end
return memo
end
@@ -111,6 +139,17 @@ function Underscore.funcs.detect(list, func)
return nil
end
+Underscore.funcs.detect_predicate = (function()
+ local detect_predicate = function(list, func)
+ local condition
+ Underscore.funcs.any(list, Underscore.funcs.wrap(func, function(callback, item)
+ condition = callback(item)
+ return condition end))
+ return condition
+ end
+ return detect_predicate
+end)()
+
function Underscore.funcs.select(list, func)
local selected = {}
for i in Underscore.iter(list) do
@@ -200,19 +239,15 @@ function Underscore.funcs.max(list, func)
end).item
end
-function Underscore.funcs.to_array(list)
- local array = {}
- for i in Underscore.iter(list) do
- array[#array+1] = i
- end
- return array
-end
+Underscore.funcs.to_array = (function ()
+ return function (iter)
+ return Underscore.funcs.map (iter, function (item) return item end)
+ end
+end)()
function Underscore.funcs.reverse(list)
local reversed = {}
- for i in Underscore.iter(list) do
- table.insert(reversed, 1, i)
- end
+ Underscore.funcs.each(list, function(item) table.insert(reversed, 1, item) end)
return reversed
end
@@ -225,6 +260,72 @@ function Underscore.funcs.sort(iter, comparison_func)
return array
end
+-- usage: simple_reduce({...}, callback), where callback = function(x, y)
end
+-- simplifies a reduce function by ditching the memo base case
+-- @param : list_or_iter, following the underscoreLua specs
+-- @param : func, a callback of the form x -> y -> list of z
+-- @return : a list
+Underscore.funcs.simple_reduce = (function()
+ local simple_reduce = function(list_or_iter, func)
+ local iter = Underscore.iter(list_or_iter)
+ local _ = iter()
+ return Underscore.funcs.reduce(iter, _, func) end
+ return simple_reduce
+end)()
+
+-- usage: multi_map({{...}, {...}, ...}, callback), where callback = function() end
+-- provides a map function for an arbitrary number of lists and/or iterators
+-- @param : lists_or_iters, a list of lists and/or iterators following the underscoreLua specs
+-- @param : func, a callback that takes the same amount of arguments as there are items in lists_or_iters
+-- the function stops execution immediately upon finding a nil value in any of the items in each of the lists_or_iters
+-- the use of iterators or lists with holes is therefore discouraged, a very useful side-effect of this behaviour is that
+-- the function will cater itself towards the iterator or list with the least amount of items
+-- @return : a list
+Underscore.funcs.multi_map = (function()
+ local inner
+ inner = function(iters, func, accumulator)
+ local _
+ local _s = {}
+ if Underscore.funcs.all(iters, function(iter)
+ _ = iter()
+ table.insert(_s, _)
+ return _ end) then
+ table.insert(accumulator, func(unpack(_s)))
+ return inner(iters, func, accumulator) end
+ end
+ local multi_map = function(lists_or_iters, func)
+ local accumulator = {}
+ local iters = Underscore.funcs.map(lists_or_iters, Underscore.iter)
+ inner(iters, func, accumulator)
+ return accumulator end
+ return multi_map
+end)()
+
+-- our classic zip function, has almost the same signature as multi-map, but omits the callback
+Underscore.funcs.zip = (function()
+ local zip = function (lists_or_iters)
+ return Underscore.funcs.multi_map(lists_or_iters, function(...) return {...} end)
+ end
+ return zip
+end)()
+
+Underscore.funcs.each_while = (function()
+ local each_while = function (lists_or_iters, func, predicate)
+ func = Underscore.funcs.wrap(func, function(callback, ...)
+ local go = predicate(...)
+ if go then callback(...) end
+ return go end)
+ Underscore.funcs.all(lists_or_iters, func) end
+ return each_while
+end)()
+
+Underscore.funcs.each_untill = (function()
+ local each_untill = function (lists_or_iters, func, predicate)
+ predicate = Underscore.funcs.negate(predicate)
+ Underscore.funcs.each_while(lists_or_iters, func, predicate) end
+ return each_untill
+end)()
+
-- arrays
function Underscore.funcs.first(array, n)
@@ -283,6 +384,47 @@ function Underscore.funcs.pop(array)
return table.remove(array)
end
+Underscore.funcs.clear = (function ()
+ return function (array)
+ Underscore.funcs.safe_each (array, function () Underscore.funcs.pop (array) end)
+ end
+end)()
+
+Underscore.funcs.append = (function ()
+ return function (array, ...)
+ local acc = {unpack (array)} -- identity, abstract away later
+ Underscore.funcs.each ({...}, function (item) Underscore.funcs.push (acc, item) end)
+ return acc
+ end
+end)()
+
+Underscore.funcs.destructive_append = (function ()
+ return function (array, ...)
+ Underscore.funcs.each ({...}, function (item) Underscore.funcs.push (array, item) end)
+ return array
+ end
+end)()
+
+Underscore.funcs.append_all = (function ()
+ return function (array, ...)
+ local acc = {unpack (array)} -- identity, abstract away later
+ Underscore.funcs.each ({...}, function (item) Underscore.funcs.destructive_append (acc, unpack (item)) end)
+ return acc
+ end
+end)()
+
+Underscore.funcs.flatten_once = (function ()
+ return function (array)
+ if Underscore.funcs.is_empty (array) then return array end
+ local _ = Underscore.funcs.first (array)
+ if #array == 1 then return _ end
+ return Underscore.funcs.append_all (_, unpack (Underscore.funcs.rest (array))) end
+end)()
+
+Underscore.funcs.safe_each = (function ()
+ return function (list, func) return Underscore.funcs.each (Underscore.rangeV2 (1, #list), func) end
+end)()
+
function Underscore.funcs.shift(array)
return table.remove(array, 1)
end
@@ -292,10 +434,24 @@ function Underscore.funcs.unshift(array, item)
return array
end
+Underscore.funcs.grab = (function ()
+ return function (list, idx) return table.remove(list, idx) end
+end)()
+
function Underscore.funcs.join(array, separator)
return table.concat(array, separator)
end
+Underscore.funcs.shuffle = (function ()
+ return function (list)
+ math.randomseed (os.time ())
+ list = {unpack (list)}
+ local acc = {}
+ Underscore.funcs.safe_each (list, function () Underscore.funcs.push (acc, Underscore.funcs.grab(list, math.random (#list))) end)
+ return acc
+ end
+end)()
+
-- objects
function Underscore.funcs.keys(obj)
@@ -356,7 +512,7 @@ end
function Underscore.funcs.compose(...)
local function call_funcs(funcs, ...)
if #funcs > 1 then
- return funcs[1](call_funcs(_.rest(funcs), ...))
+ return funcs[1](call_funcs(Underscore.funcs.rest(funcs), ...))
else
return funcs[1](...)
end
@@ -380,7 +536,23 @@ function Underscore.funcs.curry(func, argument)
end
end
-function Underscore.functions()
+Underscore.funcs.multi_curry = (function()
+ local multi_curry = function (func, ...)
+ Underscore.funcs.each ({...}, function (arg) func = Underscore.funcs.curry(func, arg) end)
+ return function(...) return func(...) end
+ end
+ return multi_curry
+end)()
+
+Underscore.funcs.negate = (function()
+ local negate = function (predicate)
+ predicate = Underscore.funcs.wrap(predicate, function(callback, ...)
+ return not callback(...) end)
+ return predicate end
+ return negate
+end)()
+
+function Underscore.functions()
return Underscore.keys(Underscore.funcs)
end
diff --git a/readme.txt b/readme.txt
index 0d71e2a..27d4a89 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1 +1,15 @@
-A Lua version of http://documentcloud.github.com/underscore/, see http://mirven.github.com/underscore.lua/ for more information.
\ No newline at end of file
+A Lua version of http://documentcloud.github.com/underscore/, see http://mirven.github.com/underscore.lua/ for more information.
+
+This fork contains three additions to the core underscore.lua:
+simple_reduce, multi_map and table_iterator.
+
+simple_reduce doesn't require a base case,
+it uses the first yield of an iterator passed to it instead.
+
+multi_map can be used on a list of lists of indefinite length,
+with a callback given having a number of arguments equal
+to the number of iterators in the list of iterators.
+This function caters to the iterator with the least amount of yields.
+
+table_iterator is a simple iterator to run through a table,
+it yields a key-value pair as a table.