Underscore.lua is a Lua library that provides a set of utility functions for dealing with iterators, arrays, tables, and functions. It's api and documentation are heavily inspired by Underscore.js.
It can be installed with LuaRocks via:
luarocks install underscore.lua --from=http://marcusirven.s3.amazonaws.com/rocks/
Alternatively underscore.lua can be downloaded directly from here.
The source code is available at Github.
_ = require 'underscore' _.each({1,2,3}, print)
It is idiomatic in Lua to use the underscore character for throw away variables so you can simply assign it to another variable name such as:
__ = require 'underscore' __.each({1,2,3}, print)
All the iterator functions can take either an iterator such as:
function sq(n) return coroutine.wrap(function() for i=1,n do coroutine.yield(i*i) end end) end _.each(sq(5), print) => 1 => 4 => 9 => 16 => 25
or and array such as:
_.each({1,2,3,4,5}, print) => 1 => 2 => 3 => 4 => 5
This is accomplished with
_.iter()
which with create an iterator over and array or if passed a function it will use that.
for i in _.iter({1,2,3,4,5}) do print(i) end => 1 => 2 => 3 => 4 => 5
You can use Underscore in either an object-oriented or a functional style, depending on your preference. The following two lines of code are identical ways to double a list of numbers.
_.map({1, 2, 3}, function(n) return n * 2 end) _({1, 2, 3}):map(function(n) return n * 2 end)
Using the object-oriented style allows you to chain together methods. Calling chain on a wrapped object will cause all future method calls to return wrapped objects as well. When you've finished the computation, use value to retrieve the final value. Here's an example of chaining together a map/flatten/reduce, in order to get the word count of every word in a song.
local lyrics = { { line = 1, words = "I'm a lumberjack and I'm okay" }, { line = 2, words = "I sleep all night and I work all day" }, { line = 3, words = "He's a lumberjack and he's okay" }, { line = 4, words = "He sleeps all night and he works all day" } } _(lyrics):chain() :map(function(line) local words = {} for w in line.words:gmatch("%S+") do words[#words+1] = w end return words end) :flatten() :reduce({}, function(counts, word) counts[word] = (counts[word] or 0) + 1 return counts end):value()
Note: This can be written even more succinctly as (notice the map function):
_(lyrics):chain() :map(function(line) return _.to_array(line.words:gmatch("%S+")) end) :flatten() :reduce({}, function(counts, word) counts[word] = (counts[word] or 0) + 1 return counts end):value()
map
_.map(iter, func)
Aliases:
collect
Produces a new array by mapping each value in iter through a transformation function.
_.map({1,2,3}, function(i) return i*2 end) => { 2,4,6 }
each
_.each(iter, func)
Aliases:
for_each
Passes each value to function(i) and returns the input.
_.each({1,2,3}, print) => {1,2,3}
select
_.select(iter, func)
Aliases:
filter
Removes items that do not match the provided criteria.
_.select({1,2,3}, function(i) return i%2 == 1 end) => {1,3}
reject
_.reject(iter, func)
Removes items that match the provided criteria.
_.reject({1,2,3}, function(i) return i%2 == 1 end) => {2}
invoke
_.invoke(foo, blah)
Calls a function with specified name on each item using the colon operator.
Person = {} Person.__index = Person function Person:new(name) return setmetatable({ name=name }, self) end function Person:print() print(self.name) end _.invoke({ Person:new("Tom"), Person:new("Dick"), Person:new("Harry") }, "print") => Calls person:print() on each Person
pluck
_.pluck(iter, property_name)
An convenient version of the common use-case of mapextracting a list of properties.
_.pluck({ {id=1}, {id=2}, {id=3} }, 'id') => { 1, 2, 3 }
reduce
_.reduce(iter, memo, func)
Aliases:
inject,foldl
Boils down a list of values into a single value. Memo is the initial state of the reduction, and each successive step of it should be returned by func.
_.reduce({1,2,3}, 0, function(memo, i) return memo+i end) => 6
max
_.max(iter, [func])
Returns the item with the largest value. If a func is provided it will be used on each value to generate the criterion by which the value is ranked.
_.max({1,2,3,4}) => 4 _.max({ {age=15}, {age=12}, {age=19} }, function(p) return p.age end) => {age=19}
min
_.min(iter, [func])
Returns the item with the smallest value. If a func is provided it will be used on each value to generate the criterion by which the value is ranked.
_.max({1,2,3,4}) => 1 _.max({ {age=15}, {age=12}, {age=19} }, function(p) return p.age end) => {age=12}
include
_.include(iter, value)
Returns true if the list include's value. Uses the == operator.
_.include({1,2,3,4}, function(i) return i%2 == 0 end) => true _.include({1,3,5}, function(i) return i%2 == 0 end) => false
detect
_.detect(iter, func)
Looks through a list returning the first element that matches a truth function. The function returns as soon as it finds an acceptable element.
_.detect({1,2,3,4}, func(i) return i > 3 end) => 4 _.detect({1,2,3,4}, func(i) return i > 7 end) => nil
all
_.all(iter, [func])
Aliases:
every
Returns true if func(item) returns true for all item in items.
_.all({2,4,8}, function(i) return i%2 == 0 end) => true _.all({1,2,3,4}, function(i) return i%2 == 0 end) => false
any
_.any(iter, [func])
Aliases:
some
Returns true if func(item) returns true for any item in items.
_.any({1,2,3,4}, function(i) return i%2 == 0 end) => true _.any({1,3,5}, function(i) return i%2 == 0 end) => false
to_array
_.to_array(iter)
Collects all values into a new array.
_.to_array(string.gmatch("dog cat goat", "%S+")) => { "dog", "cat", "goat" }
sort
_.sort(iter, [comparison_func])
Returns an array with all elements sorted by the comparison function, by default it uses the < operator. If an array is passed in it will sort the elements in place and not create a new array.
_.sort({ 3, 1, 2}) => { 1, 2, 3 }
reverse
_.reverse(iter, [comparison_func])
Iterates over all the items and returns a new array with the items in reverse order.
_.reverse({ 1, 2, 3}) => { 3, 2, 1 }
flatten
_.flatten(array)
Flattens a nested array (the nesting can be to any depth).
_.flatten({1, {2}, {3, {{{4}}}}}) => { 1, 2, 3, 4 }
first
_.first(array, [length])
Aliases:
head
Returns the first element of an array. Optionally it will return an array of the first n items.
_.first({1,2,3}) => 1 _.first({1,2,3}, 2) => {1,2,}
rest
_.rest(array, [start_index])
Aliases:
tail
Returns an array of all items except the first. Optionally it will start at start_index.
_.rest({1,2,3}) => {2,3} _.rest({1,2,3}, 2) => {3}
slice
_.slice(array, start_index, length)
Returns a section of an array starting with start_index of length items length.
_.slice({ 1, 2, 3, 4, 5 }, 2, 3) => { 2, 3, 4 }
push
_.push(array, item)
Inserts item at the end of an array
_.push({1,2,3}, 4) => {1,2,3,4}
pop
_.pop(array)
Removes and returns the last item in an array
_.pop({1,2,3}) => 3
shift
_.shift(array)
Removes and returns the first item in an array
_.shift({1,2,3}) => 1
unshift
_.unshift(array, item)
Inserts item at the beginning of an array
_.push({1,2,3}, 4) => {4,1,2,3}
join
_.join(array)
Returns a string with all items in the array concatenated together with an optional separator.
_.join({'c','a','t'}) => "cat" _.join({'c','a','t'}, '/') => "c/a/t"
extend
_.extend(destination, source)
Copy all of the properties in the source object over to the destination object.
_.extend({ name = 'moe' }, { age = 50 }) => { name = 'moe', age = 50 }
keys
_.keys(object)
Returns an array of all the property names in a table. (Note: order is not defined)
_.keys { name = "John", age = 25 } => { "name", "age" }
values
_.values(object)
Returns an array of all the property values in a table. (Note: order is not defined)
_.values { name = "John", age = 25 } => { "John", 25 }
is_empty
_.is_empty(object)
Returns true if object contains no values.
_.is_empty({}) => true -.is_empty({ name = "moe" }) => false
curry
_.curry(func, arg)
Creates a wrapper function substituing the supplied arg for the first argument in original function.
function f(p1,p2) return {p1,p2} end g = _.curry(f, "a") g("b") => {"a","b"} g("c") => {"a","c"}
wrap
_.wrap(func, wrapper)
Wraps the first function inside of the wrapper function, passing it as the first argument. This allows the wrapper to execute code before and after the function runs, adjust the arguments, and execute it conditionally.
hello = function(name) return "hello: "..name end hello = _.wrap(hello, function(func, ...) return "before, "..func(...)..", after" end) hello('moe') => before, hello: moe, after
compose
_.compose(func1, func2, [...])
Returns the composition of a list of functions, where each function consumes the return value of the function that follows. In math terms, composing the functions f(), g(), and h() produces f(g(h())).
greet = function(name) return "hi: "..name end exclaim = function(statement) return statement.."!" end welcome = _.compose(print, greet, exclaim) welcome('moe') => hi: moe!
functions
_.functions()
Returns a list of function names in this library.
_.functions() => { 'each', 'map', 'reduce', ... }
identity
_.identity(v)
Identity function, simply returns whatever is passed in. This function looks useless, but is used within Underscore as a default function.
_.identity("foo") => "foo"
iter
_.iter()
Creates an iterator function over an array.
for i in _.iter({1,2,3}) do print(i) end
range
_.range(start_or_length, [end], [step])
Iterates over a range of integers
_.range(5,10):to_array() => { 5,6,7,8,9,10 } _.range(10):to_array() => { 1,2,3,4,5,6,7,8,9,10 } _.range(2,10,2):to_array() => { 2,4,6,8,10 } _.range(10,2,-2):to_array() => { 10,8,6,4,2 }
is_equal
_.is_equal(o1,o2,[ignore_mt])
Performs an optimized deep comparison between the two objects, to determine if they should be considered equal. By default it uses the _eql metamethod if it is present.
_.is_equal({1,2,3},{1,2,3}) => true _.is_equal({a=1,b=2},{a=1,b=2}) => true _.is_equal({a=1,b=2},{a=2,b=3}) => false
chain
_.chain()
Puts underscore into chaining mode.
_.({1,2,3,4}):chain():map(function(i) return i+1 end):select(function(i) i%2 == 0 end):value() => { 2,4 }
value
_.value()
Stops chaining and returns the current value.
_.({1,2,3,4}):chain():map(function(i) return i+1 end):select(function(i) i%2 == 0 end):value() => { 2,4 }