├── .gitignore ├── ltrie ├── hashcode.lua ├── lua_hashcode.lua ├── tablemap.lua ├── subvec.lua ├── c_hashcode.c ├── list.lua ├── vector.lua ├── hashmap.lua └── fun.lua ├── config.ld ├── rebuild_docs.sh ├── README.md ├── circle.yml ├── ltrie-scm-1.rockspec ├── spec ├── strict.lua ├── list_spec.lua ├── tablemap_spec.lua ├── fun │ ├── indexing_spec.lua │ ├── transformations_spec.lua │ ├── composition_spec.lua │ ├── filtering_spec.lua │ ├── generators_spec.lua │ ├── reducing_spec.lua │ ├── slicing_spec.lua │ ├── operators_spec.lua │ └── basic_spec.lua ├── hash_spec.lua ├── hashmap_spec.lua └── vector_spec.lua └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.rock 3 | *.out 4 | doc/ 5 | -------------------------------------------------------------------------------- /ltrie/hashcode.lua: -------------------------------------------------------------------------------- 1 | return require 'ltrie.lua_hashcode' 2 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | project="ltrie" 2 | title="LTrie" 3 | description="A collection of immutable datastructures" 4 | format="markdown" 5 | file={"ltrie", exclude = {"ltrie/fun.lua"}} 6 | dir="doc" 7 | sort_modules=true 8 | use_markdown_titles=true 9 | custom_tags = { 10 | { 'complexity'} 11 | } 12 | -------------------------------------------------------------------------------- /rebuild_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit -o nounset 4 | 5 | rev=$(git rev-parse --short HEAD) 6 | 7 | ldoc -d $CIRCLE_ARTIFACTS/docs . 8 | 9 | cd $CIRCLE_ARTIFACTS/docs 10 | 11 | git init 12 | git config user.name "Kyle McLamb" 13 | git config user.email "kjmclamb@gmail.com" 14 | 15 | git remote add upstream "https://$GH_API@github.com/Alloyed/ltrie.git" 16 | git fetch upstream 17 | git reset upstream/gh-pages 18 | 19 | git add -A . 20 | git commit -m "rebuild pages at ${rev}" 21 | git push -q upstream HEAD:gh-pages 22 | 23 | rm -rf .git 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WARNING 2 | this package is archived. a non-archived copy is available at [https://codeberg.org/alloyed/ltrie](https://codeberg.org/alloyed/ltrie) 3 | 4 | # Ltrie 5 | 6 | Ltrie is a collection of immutable datastructures patterned after 7 | Clojure's datastructures and immutable.js 8 | 9 | ## TODO 10 | 11 | Find a way to trick luafun into thinking the collections are also 12 | wrapped iterators 13 | 14 | Explicitly document when a collection implements a given interface 15 | 16 | To Document: 17 | * Subvec 18 | * Hashmap 19 | 20 | Unimplemented datastructures: 21 | * transients 22 | * hashset 23 | * ordered map 24 | * compound vector/hash 25 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | general: 2 | branches: 3 | ignore: 4 | - gh-pages 5 | 6 | dependencies: 7 | pre: 8 | - sudo apt-get update 9 | - sudo apt-get install lua5.1 luarocks 10 | - sudo luarocks install luasocket 11 | - sudo luarocks install luasec OPENSSL_LIBDIR=/usr/lib/x86_64-linux-gnu 12 | - sudo luarocks install busted 13 | - sudo luarocks install luabitop 14 | - sudo luarocks install ldoc 15 | post: 16 | - sudo luarocks make --server=http://luarocks.org/manifests/alloyed 17 | test: 18 | override: 19 | - mkdir -p $CIRCLE_TEST_REPORTS/junit 20 | - busted -o junit > $CIRCLE_TEST_REPORTS/junit/tests.xml 21 | 22 | deployment: 23 | master: 24 | branch: master 25 | commands: 26 | - ./rebuild_docs.sh 27 | -------------------------------------------------------------------------------- /ltrie-scm-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "ltrie" 2 | version = "scm-1" 3 | source = { 4 | url = "git://github.com/Alloyed/ltrie" 5 | } 6 | description = { 7 | summary = "A collection of immutable datastructures", 8 | -- detailed = "*** please enter a detailed description ***", 9 | homepage = "https://github.com/Alloyed/ltrie", 10 | license = "MIT" 11 | } 12 | dependencies = {} 13 | build = { 14 | type = "builtin", 15 | modules = { 16 | ['ltrie.c_hashcode'] = { 17 | sources = "ltrie/c_hashcode.c" 18 | }, 19 | ['ltrie.hashcode'] = "ltrie/lua_hashcode.lua", 20 | ['ltrie.list'] = "ltrie/list.lua", 21 | ['ltrie.subvec'] = "ltrie/subvec.lua", 22 | ['ltrie.vector'] = "ltrie/vector.lua", 23 | ['ltrie.fun'] = "ltrie/fun.lua", 24 | ['ltrie.tablemap'] = "ltrie/tablemap.lua", 25 | ['ltrie.hashmap'] = "ltrie/hashmap.lua" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spec/strict.lua: -------------------------------------------------------------------------------- 1 | local function strict() 2 | local mt = getmetatable(_G) 3 | if mt == nil then 4 | mt = {} 5 | setmetatable(_G, mt) 6 | end 7 | 8 | _G.__STRICT = true 9 | 10 | mt.__declared = { global = true } 11 | 12 | mt.__newindex = function (t, n, v) 13 | if __STRICT and not mt.__declared[n] then 14 | local w = debug.getinfo(2, "S").what 15 | if w ~= "main" and w ~= "C" then 16 | error("assign to undeclared variable '"..n.."'", 2) 17 | end 18 | mt.__declared[n] = true 19 | end 20 | rawset(t, n, v) 21 | end 22 | 23 | mt.__index = function (t, n) 24 | if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then 25 | error("variable '"..n.."' is not declared", 2) 26 | end 27 | return rawget(t, n) 28 | end 29 | 30 | function global(...) 31 | for _, v in ipairs{...} do mt.__declared[v] = true end 32 | end 33 | end 34 | 35 | return setmetatable({ strict = strict }, { __call = strict }) 36 | -------------------------------------------------------------------------------- /spec/list_spec.lua: -------------------------------------------------------------------------------- 1 | require 'spec/strict' () 2 | 3 | global 'bit32' 4 | global 'file' 5 | global 'level' 6 | 7 | describe("list", function() 8 | local fun = require 'ltrie.fun' 9 | local list = require 'ltrie.list' 10 | 11 | it("implements (from)", function() 12 | assert.are.same( 13 | list.from{1, 2, 3}, 14 | list.EMPTY:conj(3):conj(2):conj(1)) 15 | end) 16 | it("implements (get)", function() 17 | assert.is.equal(list.get(list.from{6, 7, 8}, 2), 7) 18 | end) 19 | it("implements (conj)", function() 20 | assert.are.same( 21 | fun.totable(list.conj(list.from{2, 3, 4}, 1)), 22 | {1, 2, 3, 4}) 23 | end) 24 | it("implements (assoc)", function() 25 | local old = list.from{1, 2, 3, 4} 26 | assert.are.same( 27 | fun.totable(list.assoc(old, 1, 10)), 28 | {10, 2, 3, 4}) 29 | assert.are.same( 30 | fun.totable(list.assoc(old, 3, 10)), 31 | {1, 2, 10, 4}) 32 | assert.are.same( 33 | fun.totable(old), 34 | {1, 2, 3, 4}) 35 | end) 36 | end) 37 | -------------------------------------------------------------------------------- /ltrie/lua_hashcode.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- A pure lua implementation of a hashcode. 3 | -- 4 | local function try(...) 5 | local ok, err = pcall(...) 6 | if not ok then return nil end 7 | return err 8 | end 9 | local b = bit32 or try(require, 'bit') or error("No bitop lib found") 10 | 11 | local hash = {} 12 | 13 | local function hashcode(o) 14 | local t = type(o) 15 | if t == 'string' then 16 | local len = #o 17 | local h = len 18 | local step = b.rshift(len, 5) + 1 19 | 20 | for i=len, step, -step do 21 | h = b.bxor(h, b.lshift(h, 5) + b.rshift(h, 2) + string.byte(o, i)) 22 | end 23 | return h 24 | elseif t == 'number' then 25 | local h = math.floor(o) 26 | if h ~= o then 27 | h = b.bxor(o * 0xFFFFFFFF) 28 | end 29 | while o > 0xFFFFFFFF do 30 | o = o / 0xFFFFFFFF 31 | h = b.bxor(h, o) 32 | end 33 | return h 34 | elseif t == 'bool' then 35 | return t and 1 or 2 36 | elseif t == 'table' and o.hashcode then 37 | local n = o:hashcode() 38 | assert(math.floor(n) == n, "hashcode is not an integer") 39 | return n 40 | end 41 | 42 | return nil 43 | end 44 | 45 | return { 46 | hashcode = hashcode 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kyle Mclamb 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/tablemap_spec.lua: -------------------------------------------------------------------------------- 1 | require 'spec/strict' () 2 | 3 | global 'bit32' 4 | global 'file' 5 | global 'level' 6 | 7 | describe("tablemap", function() 8 | local A = require 'ltrie.tablemap' 9 | it("implements from()/pairs()", function() 10 | local cmp = { a = 'a', b = 'c', c = 12 } 11 | local new = A.from(cmp) 12 | for k, v in new:pairs() do 13 | assert.are.equal(v, cmp[k]) 14 | end 15 | cmp.a = 'b' 16 | assert.are_not.equal(new:get('a'), cmp.a) 17 | end) 18 | it("Implements of()/get()", function() 19 | local cmp = { a = 'a', b = 'c', c = 12 } 20 | local new = A.of('a', 'a', 'b', 'c', 'c', 12) 21 | for k, v in pairs(cmp) do 22 | assert.are.equal(v, new:get(k)) 23 | end 24 | end) 25 | it("Implements assoc()/len()", function() 26 | local cmp = { a = 'a', b = 'c', c = 12 } 27 | local new = A.from(cmp) 28 | 29 | assert.is.equal(3, new:len()) 30 | cmp.d = "JEFF" 31 | new = new:assoc('d', "JEFF") 32 | assert.is.equal(4, new:len()) 33 | 34 | assert.are.same(cmp, new.table) 35 | end) 36 | it("Implements dissoc()", function() 37 | local cmp = { a = 'a', b = 'c', c = 12 } 38 | local new = A.from(cmp) 39 | 40 | cmp.c = nil 41 | new = new:dissoc('c') 42 | 43 | assert.is.equal(2, new:len()) 44 | assert.are.same(cmp, new.table) 45 | end) 46 | end) 47 | -------------------------------------------------------------------------------- /spec/fun/indexing_spec.lua: -------------------------------------------------------------------------------- 1 | describe("indexing", function() 2 | local fun = require 'ltrie.fun' 3 | 4 | local T = {} 5 | local function reset() 6 | T = {} 7 | end 8 | local function p(...) 9 | T[#T+1] = {...} 10 | end 11 | local function check(t) 12 | assert.are.same(T, t) 13 | end 14 | 15 | it("index", function() 16 | assert.is.equal(2, fun.index(2, fun.range(5))) 17 | 18 | assert.is_nil(fun.index(10, fun.range(5))) 19 | assert.is_nil(fun.index(2, fun.range(0))) 20 | assert.is.equal(2, fun.index('b', {'a', 'b', 'c', 'd', 'e'})) 21 | assert.is.equal(1, fun.index(1, fun.enumerate{'a', 'b', 'c', 'd', 'e'})) 22 | assert.is.equal(2, fun.index('b', "abcde")) 23 | 24 | assert.is.equal(fun.index_of, fun.index) 25 | assert.is.equal(fun.elem_index, fun.index) 26 | end) 27 | 28 | it("indexes", function() 29 | reset() 30 | fun.each(p, fun.indexes('a', {'a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'a', 'a'})) 31 | check {{1}, {6}, {10}, {11}} 32 | 33 | reset() 34 | fun.each(p, fun.indexes('f', {'a', 'b', 'c', 'd', 'e', 'a', 'b', 'c', 'd', 'a', 'a'})) 35 | check {} 36 | 37 | reset() 38 | fun.each(p, fun.indexes('f', {})) 39 | check {} 40 | 41 | reset() 42 | fun.each(p, fun.indexes(1, fun.enumerate{'a', 'b', 'c', 'd', 'e'})) 43 | check {{1}} 44 | 45 | assert.is.equal(fun.indices, fun.indexes) 46 | assert.is.equal(fun.elem_indexes, fun.indexes) 47 | assert.is.equal(fun.elem_indices, fun.indexes) 48 | end) 49 | end) 50 | 51 | -------------------------------------------------------------------------------- /ltrie/tablemap.lua: -------------------------------------------------------------------------------- 1 | --- A COW Map using lua's native tables, for testing 2 | 3 | local fun = require 'ltrie.fun' 4 | local TMap = {} 5 | local mt = { __index = TMap } 6 | 7 | local function ctor(data) 8 | assert(data.table) 9 | assert(data.count) 10 | return setmetatable(data, mt) 11 | end 12 | 13 | local EMPTY = ctor {table = {}, count = 0} 14 | 15 | --- Map from iterator 16 | function TMap.from(...) 17 | local t = EMPTY 18 | fun.each(function(k, v) 19 | t = t:assoc(k, v) 20 | end, ...) 21 | return t 22 | end 23 | 24 | --- Map from varargs 25 | function TMap.of(...) 26 | local t = EMPTY 27 | for i=1, select('#', ...), 2 do 28 | local k, v = select(i, ...) 29 | t = t:assoc(k, v) 30 | end 31 | return t 32 | end 33 | 34 | --- Map from table 35 | function TMap.wrap_table(tbl) 36 | local i = 0 37 | for _ in pairs(tbl) do i = i + 1 end 38 | return ctor { table = tbl, count = i } 39 | end 40 | 41 | function TMap:len() 42 | return self.count 43 | end 44 | 45 | local function copy(tbl) 46 | local new = {} 47 | for k, v in pairs(tbl) do 48 | new[k] = v 49 | end 50 | return new 51 | end 52 | 53 | function TMap:assoc(k, v) 54 | local newtbl = copy(self.table) 55 | newtbl[k] = v 56 | return TMap.wrap_table(newtbl) 57 | end 58 | 59 | function TMap:dissoc(k) 60 | local newtbl = copy(self.table) 61 | newtbl[k] = nil 62 | return TMap.wrap_table(newtbl) 63 | end 64 | 65 | function TMap:get(k) 66 | return self.table[k] 67 | end 68 | 69 | function TMap:pairs() 70 | return pairs(self.table) 71 | end 72 | 73 | return TMap 74 | -------------------------------------------------------------------------------- /spec/fun/transformations_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Compositions", function() 2 | local fun = require 'ltrie.fun' 3 | 4 | local T = {} 5 | local function reset() 6 | T = {} 7 | end 8 | local function p(...) 9 | T[#T+1] = {...} 10 | end 11 | local function check(t) 12 | assert.are.same(T, t) 13 | end 14 | 15 | it('map', function() 16 | local f = function(...) 17 | return 'map', ... 18 | end 19 | 20 | reset() 21 | fun.each(p, fun.map(f, fun.range(0))) 22 | check {} 23 | 24 | reset() 25 | fun.each(p, fun.map(f, fun.range(4))) 26 | check {{'map', 1}, {'map', 2}, {'map', 3}, {'map', 4}} 27 | 28 | reset() 29 | fun.each(p, fun.map(f, fun.enumerate {'a', 'b', 'c', 'd', 'e'})) 30 | check {{'map', 1, 'a'}, {'map', 2, 'b'}, {'map', 3, 'c'}, {'map', 4, 'd'}, {'map', 5, 'e'}} 31 | 32 | reset() 33 | fun.each(p, fun.map(function(x) 34 | return 2 * x 35 | end, fun.range(4))) 36 | check {{2}, {4}, {6}, {8}} 37 | end) 38 | 39 | it('enumerate', function() 40 | reset() 41 | fun.each(p, fun.enumerate {'a', 'b', 'c', 'd', 'e'}) 42 | check {{1, 'a'}, {2, 'b'}, {3, 'c'}, {4, 'd'}, {5, 'e'}} 43 | 44 | reset() 45 | fun.each(p, fun.enumerate(fun.enumerate(fun.enumerate {'a', 'b', 'c', 'd', 'e'}))) 46 | check { 47 | {1, 1, 1, 'a'}, 48 | {2, 2, 2, 'b'}, 49 | {3, 3, 3, 'c'}, 50 | {4, 4, 4, 'd'}, 51 | {5, 5, 5, 'e'} 52 | } 53 | 54 | reset() 55 | fun.each(p, fun.enumerate(fun.zip( 56 | {'one', 'two', 'three', 'four', 'five'}, 57 | {'a', 'b', 'c', 'd', 'e'}))) 58 | check { 59 | {1, 'one', 'a'}, 60 | {2, 'two', 'b'}, 61 | {3, 'three', 'c'}, 62 | {4, 'four', 'd'}, 63 | {5, 'five', 'e'} 64 | } 65 | end) 66 | 67 | it('intersperse', function() 68 | reset() 69 | fun.each(p, fun.intersperse('x', {})) 70 | check {} 71 | 72 | reset() 73 | fun.each(p, fun.intersperse('x', {'a', 'b', 'c', 'd', 'e'})) 74 | check {{'a'}, {'x'}, {'b'}, {'x'}, {'c'}, {'x'}, {'d'}, {'x'}, {'e'}, {'x'}} 75 | 76 | reset() 77 | fun.each(p, fun.intersperse('x', {'a', 'b', 'c', 'd', 'e', 'f'})) 78 | check {{'a'}, {'x'}, {'b'}, {'x'}, {'c'}, {'x'}, {'d'}, {'x'}, {'e'}, {'x'}, {'f'}, {'x'}} 79 | end) 80 | end) 81 | -------------------------------------------------------------------------------- /spec/fun/composition_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Compositions", function() 2 | local fun = require 'ltrie.fun' 3 | 4 | local T = {} 5 | local function reset() 6 | T = {} 7 | end 8 | local function p(...) 9 | T[#T+1] = {...} 10 | end 11 | local function check(t) 12 | assert.are.same(T, t) 13 | end 14 | 15 | it("zip", function() 16 | reset() 17 | fun.each(p, fun.zip({'a', 'b', 'c', 'd'}, {'one', 'two', 'three'})) 18 | check {{'a', 'one'}, {'b', 'two'}, {'c', 'three'}} 19 | 20 | reset() 21 | fun.each(p, fun.zip()) 22 | check {} 23 | 24 | assert.has_errors(function() fun.each(p, fun.zip(fun.range(0))) end) 25 | assert.has_errors(function() fun.each(p, fun.zip(fun.range(0), fun.range(0))) end) 26 | 27 | reset() 28 | p(fun.nth(10, fun.zip(fun.range(1, 100, 3), fun.range(1, 100, 5), fun.range(1, 100, 7)))) 29 | check {{28, 46, 64}} 30 | 31 | reset() 32 | fun.each(p, fun.zip(fun.partition(function(x) return x > 7 end, fun.range(1, 15, 1)))) 33 | check { {8, 1}, {9, 2}, {10, 3}, {11, 4}, {12, 5}, {13, 6}, {14, 7} } 34 | end) 35 | 36 | it("cycle", function() 37 | reset() 38 | fun.each(p, fun.take(15, fun.cycle{'a', 'b', 'c', 'd', 'e'})) 39 | check { 40 | {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, 41 | {'a'}, {'b'}, {'c'}, {'d'}, {'e'}, 42 | {'a'}, {'b'}, {'c'}, {'d'}, {'e'} 43 | } 44 | 45 | reset() 46 | fun.each(p, fun.take(15, fun.cycle(fun.range(5)))) 47 | check { 48 | {1}, {2}, {3}, {4}, {5}, 49 | {1}, {2}, {3}, {4}, {5}, 50 | {1}, {2}, {3}, {4}, {5} 51 | } 52 | 53 | reset() 54 | fun.each(p, fun.take(15, fun.cycle(fun.zip(fun.range(5), {'a', 'b', 'c', 'd', 'e'})))) 55 | check { 56 | {1, 'a'}, {2, 'b'}, {3, 'c'}, {4, 'd'}, {5, 'e'}, 57 | {1, 'a'}, {2, 'b'}, {3, 'c'}, {4, 'd'}, {5, 'e'}, 58 | {1, 'a'}, {2, 'b'}, {3, 'c'}, {4, 'd'}, {5, 'e'} 59 | } 60 | end) 61 | 62 | it("chain", function() 63 | reset() 64 | fun.each(p, fun.chain(fun.range(2))) 65 | check {{1}, {2}} 66 | 67 | reset() 68 | fun.each(p, fun.chain(fun.range(2), {'a', 'b', 'c'}, {'one', 'two', 'three'})) 69 | check {{1}, {2}, {'a'}, {'b'}, {'c'}, {'one'}, {'two'}, {'three'}} 70 | 71 | reset() 72 | fun.each(p, fun.take(15, fun.cycle(fun.chain(fun.enumerate{'a', 'b', 'c'}, {'one', 'two', 'three'})))) 73 | check { 74 | {1, 'a'}, {2, 'b'}, {3, 'c'}, {'one'}, {'two'}, {'three'}, 75 | {1, 'a'}, {2, 'b'}, {3, 'c'}, {'one'}, {'two'}, {'three'}, 76 | {1, 'a'}, {2, 'b'}, {3, 'c'} 77 | } 78 | 79 | assert.has_errors(function() 80 | fun.each(p, fun.chain(fun.range(0), fun.range(0), fun.range(0))) 81 | end) 82 | 83 | assert.has_errors(function() 84 | fun.each(p, fun.chain(fun.range(0), fun.range(1), fun.range(0))) 85 | end) 86 | end) 87 | end) 88 | -------------------------------------------------------------------------------- /spec/fun/filtering_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Filtering", function() 2 | local fun = require 'ltrie.fun' 3 | 4 | local T = {} 5 | local function reset() 6 | T = {} 7 | end 8 | local function p(...) 9 | T[#T+1] = {...} 10 | end 11 | local function check(t) 12 | assert.are.same(T, t) 13 | end 14 | 15 | it("filter", function() 16 | reset() 17 | fun.each(p, fun.filter(function(x) 18 | return x % 3 == 0 19 | end, fun.range(10))) 20 | check {{3}, {6}, {9}} 21 | 22 | reset() 23 | fun.each(p, fun.take(5, fun.filter(function(i, x) 24 | return x % 3 == 0 25 | end, fun.range(0)))) 26 | check {} 27 | 28 | reset() 29 | fun.each(p, fun.take(5, fun.filter(function(i, x) 30 | return i % 3 == 0 31 | end, fun.enumerate(fun.duplicate('x'))))) 32 | check {{3, 'x'}, {6, 'x'}, {9, 'x'}, {12, 'x'}, {15, 'x'}} 33 | 34 | local function filter_fun(a, b, c) 35 | return a % 16 == 0 36 | end 37 | 38 | local function test3(a, b, c) 39 | return a, c, b 40 | end 41 | 42 | local n = 50 43 | reset() 44 | fun.each(p, fun.filter(filter_fun, 45 | fun.map(test3, 46 | fun.zip(fun.range(0, n, 1), 47 | fun.range(0, n, 2), 48 | fun.range(0, n, 3))))) 49 | check {{0, 0, 0}, {16, 48, 32}} 50 | 51 | assert.is_equal(fun.remove_if, fun.filter) 52 | end) 53 | 54 | it("grep", function() 55 | local lines_to_grep = { 56 | [[Lorem ipsum dolor sit amet, consectetur adipisicing elit, ]], 57 | [[sed do eiusmod tempor incididunt ut labore et dolore magna ]], 58 | [[aliqua. Ut enim ad minim veniam, quis nostrud exercitation ]], 59 | [[ullamco laboris nisi ut aliquip ex ea commodo consequat.]], 60 | [[Duis aute irure dolor in reprehenderit in voluptate velit ]], 61 | [[esse cillum dolore eu fugiat nulla pariatur. Excepteur sint ]], 62 | [[occaecat cupidatat non proident, sunt in culpa qui officia ]], 63 | [[deserunt mollit anim id est laborum.]] 64 | } 65 | reset() 66 | fun.each(p, fun.grep("lab", lines_to_grep)) 67 | check { 68 | {[[sed do eiusmod tempor incididunt ut labore et dolore magna ]]}, 69 | {[[ullamco laboris nisi ut aliquip ex ea commodo consequat.]]}, 70 | {[[deserunt mollit anim id est laborum.]]} 71 | } 72 | 73 | lines_to_grep = { 74 | [[Emily]], 75 | [[Chloe]], 76 | [[Megan]], 77 | [[Jessica]], 78 | [[Emma]], 79 | [[Sarah]], 80 | [[Elizabeth]], 81 | [[Sophie]], 82 | [[Olivia]], 83 | [[Lauren]] 84 | } 85 | reset() 86 | fun.each(p, fun.grep("^Em", lines_to_grep)) 87 | check {{[[Emily]]}, {[[Emma]]}} 88 | end) 89 | 90 | it("partition", function() 91 | reset() 92 | fun.each(p, fun.zip(fun.partition(function(i, x) 93 | return i % 3 == 0 94 | end, fun.range(10)))) 95 | check {{3, 1}, {6, 2}, {9, 4}} 96 | end) 97 | end) 98 | 99 | -------------------------------------------------------------------------------- /spec/fun/generators_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Compositions", function() 2 | local fun = require 'ltrie.fun' 3 | 4 | local T = {} 5 | local function reset() 6 | T = {} 7 | end 8 | local function p(...) 9 | T[#T+1] = {...} 10 | end 11 | local function check(t) 12 | assert.are.same(T, t) 13 | end 14 | 15 | it("range", function() 16 | local function test_range1(e) 17 | reset() 18 | fun.each(p, fun.range(e)) 19 | local t = T 20 | reset() 21 | for i=1, e do p(i) end 22 | check(t) 23 | end 24 | 25 | local function test_range2(b, e) 26 | reset() 27 | fun.each(p, fun.range(b, e)) 28 | local t = T 29 | reset() 30 | for i=b, e do p(i) end 31 | check(t) 32 | end 33 | 34 | local function test_range3(b, e, s) 35 | reset() 36 | fun.each(p, fun.range(b, e, s)) 37 | local t = T 38 | reset() 39 | for i=b, e, s do p(i) end 40 | check(t) 41 | end 42 | 43 | test_range1(0) 44 | test_range2(0, 0) 45 | test_range1(5) 46 | test_range2(0, 5) 47 | test_range3(0, 5, 1) 48 | test_range3(0, 10, 2) 49 | 50 | reset() 51 | fun.each(p, fun.range(-5)) 52 | local t = T 53 | reset() 54 | for i=-1, -5, -1 do p(i) end 55 | check(t) 56 | 57 | test_range3(0, -5, 1) 58 | test_range3(0, -5, -1) 59 | test_range3(0, -10, -2) 60 | test_range3(1.2, 1.6, 0.1) 61 | assert.has_errors(function() 62 | test_range3(0, 5, 0) 63 | end) 64 | end) 65 | 66 | it("duplicate", function() 67 | reset() 68 | fun.each(p, fun.take(5, fun.duplicate(48))) 69 | check {{48}, {48}, {48}, {48}, {48}} 70 | 71 | reset() 72 | fun.each(p, fun.take(5, fun.duplicate(1, 2, 3, 4, 5))) 73 | check { 74 | {1, 2, 3, 4, 5}, 75 | {1, 2, 3, 4, 5}, 76 | {1, 2, 3, 4, 5}, 77 | {1, 2, 3, 4, 5}, 78 | {1, 2, 3, 4, 5} 79 | } 80 | 81 | assert.is.equal(fun.xrepeat, fun.duplicate) 82 | assert.is.equal(fun.replicate, fun.duplicate) 83 | end) 84 | 85 | it("rands", function() 86 | math.randomseed(0xDEADBEEF) 87 | assert.is_true(fun.all(function(x) 88 | return x >= 0 and x < 1 89 | end, fun.take(5, fun.rands()))) 90 | 91 | assert.has_errors(function() 92 | fun.take(5, fun.rands(0)) 93 | end) 94 | 95 | assert.is_true(fun.all(function(x) 96 | return math.floor(x) == x 97 | end, fun.take(5, fun.rands(10)))) 98 | 99 | assert.is_true(fun.all(function(x) 100 | return math.floor(x) == x 101 | end, fun.take(5, fun.rands(1024)))) 102 | 103 | reset() 104 | fun.each(p, fun.take(5, fun.rands(0, 1))) 105 | check {{0}, {0}, {0}, {0}, {0}} 106 | 107 | reset() 108 | fun.each(p, fun.take(5, fun.rands(5, 6))) 109 | check {{5}, {5}, {5}, {5}, {5}} 110 | 111 | assert.is_true(fun.all(function(x) 112 | return x >= 10 and x < 20 113 | end, fun.take(20, fun.rands(10, 20)))) 114 | end) 115 | 116 | it("misc", function() 117 | -- tabulate 118 | reset() 119 | fun.each(p, fun.take(5, fun.tabulate(function(x) 120 | return 2 * x 121 | end))) 122 | check {{0}, {2}, {4}, {6}, {8}} 123 | 124 | -- zeros 125 | reset() 126 | fun.each(p, fun.take(5, fun.zeros())) 127 | check {{0}, {0}, {0}, {0}, {0}} 128 | 129 | -- ones 130 | reset() 131 | fun.each(p, fun.take(5, fun.ones())) 132 | check {{1}, {1}, {1}, {1}, {1}} 133 | end) 134 | end) 135 | 136 | -------------------------------------------------------------------------------- /ltrie/subvec.lua: -------------------------------------------------------------------------------- 1 | --- A subsequence of a vector. Subvecs can only be constructing from an 2 | -- existing, non-transient vector or subvec, and otherwise support everything 3 | -- a normal vector would. 4 | -- @module Subvec 5 | 6 | local Vector = require 'ltrie.vector' 7 | local Subvec = {} 8 | 9 | local mt = {__index = Subvec, __ipairs = Vector.ipairs} 10 | 11 | -- mixins 12 | Subvec.ipairs = Vector.ipairs 13 | Subvec.unpack = Vector.unpack 14 | 15 | 16 | --- Creates a new subvector `s` containing the elements from `vec` within the 17 | -- range `[vecStart, vecEnd]` such that `s:get(1) == vec:get(vecStart)` and 18 | -- `s:get(s:len()) == vec:get(vecEnd)` 19 | -- 20 | -- @param vec the vector 21 | -- @param vecStart the start index of the subvector, inclusive 22 | -- @param vecEnd the end index of the vector, inclusive 23 | -- @usage sv = Subvec.new(Vector.of(1, 2, 3, 4), 2, 4) -- [2, 3, 4] 24 | function Subvec.new(vec, vecStart, vecEnd) 25 | if getmetatable(vec) == mt then 26 | vecStart = vecStart + vec.vecStart 27 | vecEnd = vecEnd + vec.vecEnd 28 | end 29 | return setmetatable({ 30 | vec = vec, 31 | vecStart = vecStart, 32 | vecEnd = vecEnd 33 | }, mt) 34 | end 35 | 36 | --- Returns the number of elements in the subvector. 37 | -- In lua 5.2 and up, `#subvec` can be used as a shortcut. 38 | -- 39 | -- @complexity O(1) 40 | -- @usage sv:len() == 3 41 | function Subvec:len() 42 | return self.vecEnd - self.vecStart + 1 43 | end 44 | mt.__len = Subvec.len 45 | 46 | --- Get a value by index. Vector indexes start at one. 47 | -- 48 | -- @complexity O(log32 n) 49 | -- @tparam int idx the index 50 | -- @return the value, or `nil` if not found 51 | -- @usage sv:get(2) == 3 52 | function Subvec:get(idx) 53 | idx = idx - 1 -- s[1] == v[start] 54 | local subidx = self.vecStart + idx 55 | if idx < 0 or subidx > self.vecEnd then 56 | return nil 57 | end 58 | return self.vec:get(subidx) 59 | end 60 | 61 | --- Returns a new subvector such that `sv:get(idx) == val`. This creates a new 62 | -- parent vector to represent the modification. 63 | -- @complexity O(log32 n) 64 | -- @tparam int idx the index to set 65 | -- @param val the value to set 66 | -- @usage Vector.of(0, 0, 0):assoc(2, 'a') == Vector.of(0, 'a', 0) 67 | function Subvec:assoc(idx, val) 68 | idx = idx - 1 -- s[1] == v[start] 69 | local subidx = idx + self.vecStart 70 | if idx < 0 or subidx > self.vecEnd then 71 | return error("Index out of bounds") 72 | elseif subidx == self.vecEnd then 73 | return self:conj(val) 74 | end 75 | return Subvec.new(self.vec:assoc(subidx, val), self.vecStart, self.vecEnd) 76 | end 77 | 78 | --- Returns a new subvector with val appended to the end. This creates a new 79 | -- parent vector to represent the modification. 80 | -- @complexity O(log32 n) 81 | -- @param val the value to append 82 | -- @usage sv:conj(5) == Subvec.new(Vector.of(1, 2, 3, 4, 5), 2, 5) 83 | function Subvec:conj(val) 84 | return Subvec.new(self.vec:assoc(self.vecEnd + 1, val), 85 | self.vecStart, self.vecEnd + 1) 86 | end 87 | 88 | --- Returns a new vector with the last value removed. 89 | -- @complexity O(1) 90 | -- @usage Vector.of(1, 2, 3):pop() == Vector.of(1, 2) 91 | function Subvec:pop() 92 | if self.vecEnd - 1 == self.vecStart then 93 | return Vector.of() 94 | end 95 | return Subvec.new(self.vec, self.vecStart, self.vecEnd - 1) 96 | end 97 | 98 | return Subvec 99 | -------------------------------------------------------------------------------- /spec/fun/reducing_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Reducing", function() 2 | local fun = require 'ltrie.fun' 3 | 4 | local T = {} 5 | local function reset() 6 | T = {} 7 | end 8 | local function p(...) 9 | T[#T+1] = {...} 10 | end 11 | local function check(t) 12 | assert.are.same(T, t) 13 | end 14 | 15 | it("foldl", function() 16 | assert.is.equal(15, fun.foldl(function(acc, x) 17 | return acc + x 18 | end, 0, fun.range(5))) 19 | assert.is.equal(15, fun.foldl(fun.operator.add, 0, fun.range(5))) 20 | assert.is.equal(20, fun.foldl(function(acc, x, y) 21 | return acc + x * y 22 | end, 0, fun.zip(fun.range(1, 5), {4, 3, 2, 1}))) 23 | assert.is.equal(fun.reduce, fun.foldl) 24 | end) 25 | 26 | it("length", function() 27 | assert.is.equal(5, fun.length {'a', 'b', 'c', 'd', 'e'}) 28 | assert.is.equal(0, fun.length {}) 29 | assert.is.equal(0, fun.length(fun.range(0))) 30 | end) 31 | 32 | it("is_null", function() 33 | assert.is_false(fun.is_null {'a', 'b', 'c', 'd', 'e'}) 34 | assert.is_true(fun.is_null {}) 35 | assert.is_true(fun.is_null(fun.range(0))) 36 | 37 | reset() 38 | local gen, init, state = fun.range(5) 39 | p(fun.is_null(gen, init, state)) 40 | fun.each(p, gen, init, state) 41 | check {{false}, {1}, {2}, {3}, {4}, {5}} 42 | end) 43 | 44 | it("is_prefix_of", function() 45 | assert.is_true(fun.is_prefix_of({'a'}, {'a', 'b', 'c'})) 46 | assert.is_true(fun.is_prefix_of({}, {'a', 'b', 'c'})) 47 | assert.is_true(fun.is_prefix_of({}, {})) 48 | assert.is_false(fun.is_prefix_of({'a'}, {})) 49 | assert.is_true(fun.is_prefix_of(fun.range(5), fun.range(6))) 50 | assert.is_false(fun.is_prefix_of(fun.range(6), fun.range(5))) 51 | end) 52 | 53 | it("all", function() 54 | assert.is_true(fun.all(function(x) return x end, {true, true, true, true})) 55 | assert.is_false(fun.all(function(x) return x end, {true, true, true, false})) 56 | assert.is_true(fun.all(function(x) return x end, {})) 57 | assert.is.equal(fun.every, fun.all) 58 | end) 59 | 60 | it("any", function() 61 | assert.is_false(fun.any(function(x) return x end, {false, false, false, false})) 62 | assert.is_true(fun.any(function(x) return x end, {false, false, false, true})) 63 | assert.is_false(fun.any(function(x) return x end, {})) 64 | end) 65 | 66 | it("sum", function() 67 | assert.is.equal(15, fun.sum(fun.range(1, 5))) 68 | assert.is.equal(27, fun.sum(fun.range(1, 5, 0.5))) 69 | assert.is.equal(0, fun.sum(fun.range(0))) 70 | end) 71 | 72 | it("product", function() 73 | assert.is.equal(120, fun.product(fun.range(1, 5))) 74 | assert.is.equal(7087.5, fun.product(fun.range(1, 5, 0.5))) 75 | assert.is.equal(1, fun.product(fun.range(0))) 76 | end) 77 | 78 | it("min", function() 79 | assert.is.equal(1, fun.min(fun.range(1, 10, 1))) 80 | assert.is.equal('c', fun.min {'f', 'd', 'c', 'd', 'e'}) 81 | assert.has.errors(function() 82 | return min {} 83 | end) 84 | assert.is.equal(fun.minimum, fun.min) 85 | end) 86 | 87 | it("min_by", function() 88 | local function min_cmp(a, b) if -a < -b then return a else return b end end 89 | assert.is.equal(10, fun.min_by(min_cmp, fun.range(1, 10, 1))) 90 | assert.has.errors(function() 91 | return fun.min_by(min_cmp, {}) 92 | end) 93 | assert.is.equal(fun.minimum_by, fun.min_by) 94 | end) 95 | 96 | it("max", function() 97 | assert.is.equal(10, fun.max(fun.range(1, 10, 1))) 98 | assert.is.equal('f', fun.max {'f', 'd', 'c', 'd', 'e'}) 99 | assert.has.errors(function() 100 | return fun.max {} 101 | end) 102 | assert.is.equal(fun.maximum, fun.max) 103 | end) 104 | 105 | it("max_by", function() 106 | local function max_cmp(a, b) if -a > -b then return a else return b end end 107 | assert.is.equal(1, fun.max_by(max_cmp, fun.range(1, 10, 1))) 108 | assert.has.errors(function() 109 | return fun.max_by(max_cmp, {}) 110 | end) 111 | assert.is.equal(fun.maximum_by, fun.maximum_by) 112 | end) 113 | end) 114 | -------------------------------------------------------------------------------- /spec/hash_spec.lua: -------------------------------------------------------------------------------- 1 | local paragraph 2 | 3 | for _, m in ipairs { "ltrie.c_hashcode", "ltrie.lua_hashcode" } do 4 | local hash = require(m).hashcode 5 | describe(m, function() 6 | it("hashes strings", function() 7 | assert.equal(hash("foo"), hash("foo")) 8 | assert.equal( 9 | hash("A reasonably long string"), 10 | hash("A reasonably long string")) 11 | assert.equal(hash(paragraph), hash(paragraph)) 12 | end) 13 | it("hashes numbers", function() 14 | assert.equal(hash(1), hash(1)) 15 | assert.equal(hash(0xDEADBEEF), hash(0xDEADBEEF)) 16 | assert.equal(hash(math.pi), hash(math.pi)) 17 | end) 18 | end) 19 | end 20 | 21 | -- {{{ lipsum 22 | paragraph = [[ 23 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est. 24 | 25 | 26 | Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui. 27 | 28 | 29 | Suspendisse lectus leo, consectetur in tempor sit amet, placerat quis neque. Etiam luctus porttitor lorem, sed suscipit est rutrum non. Curabitur lobortis nisl a enim congue semper. Aenean commodo ultrices imperdiet. Vestibulum ut justo vel sapien venenatis tincidunt. Phasellus eget dolor sit amet ipsum dapibus condimentum vitae quis lectus. Aliquam ut massa in turpis dapibus convallis. Praesent elit lacus, vestibulum at malesuada et, ornare et est. Ut augue nunc, sodales ut euismod non, adipiscing vitae orci. Mauris ut placerat justo. Mauris in ultricies enim. Quisque nec est eleifend nulla ultrices egestas quis ut quam. Donec sollicitudin lectus a mauris pulvinar id aliquam urna cursus. Cras quis ligula sem, vel elementum mi. Phasellus non ullamcorper urna. 30 | 31 | 32 | Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In euismod ultrices facilisis. Vestibulum porta sapien adipiscing augue congue id pretium lectus molestie. Proin quis dictum nisl. Morbi id quam sapien, sed vestibulum sem. Duis elementum rutrum mauris sed convallis. Proin vestibulum magna mi. Aenean tristique hendrerit magna, ac facilisis nulla hendrerit ut. Sed non tortor sodales quam auctor elementum. Donec hendrerit nunc eget elit pharetra pulvinar. Suspendisse id tempus tortor. Aenean luctus, elit commodo laoreet commodo, justo nisi consequat massa, sed vulputate quam urna quis eros. Donec vel. 33 | ]] 34 | -- }}} 35 | 36 | -------------------------------------------------------------------------------- /ltrie/c_hashcode.c: -------------------------------------------------------------------------------- 1 | /* 2 | * A straight copy-paste of Lua's existing hashing capabilities, using Lua's 3 | * public API instead of the internal one. As of 2015/05/04 I have not tested 4 | * this for collisions, performance, etc. so it's probably totally suboptimal. 5 | * Sorry~ 6 | * 7 | * For the original sources, check ltable.c. An online link: 8 | * http://www.lua.org/source/5.1/ltable.c.html 9 | * 10 | * Copyright (c) 1994–2015 Lua.org, PUC-Rio. 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to 14 | * deal in the Software without restriction, including without limitation the 15 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 16 | * sell copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in 20 | * all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 28 | * IN THE SOFTWARE. 29 | * 30 | */ 31 | 32 | #include 33 | #include "lua.h" 34 | #include "lauxlib.h" 35 | 36 | #define LMAX 0x7FFFFFFF 37 | 38 | #define lmod(s,size) ((int)(s) & ((size)-1)) 39 | 40 | #define hashpow2(n) (lmod((n), LMAX)) 41 | #define hashbool(p) hashpow2(p) 42 | #define hashmod(n) ((n) % ((LMAX - 1) | 1)) 43 | #define hashptr(p) hashmod(ptr2int(p)) 44 | 45 | #define numints ((int)sizeof(lua_Number)/sizeof(int)) 46 | static unsigned long ptr2int(const void* ptr) 47 | { 48 | return (unsigned long) ptr; 49 | } 50 | 51 | static int hashnum(lua_Number n) 52 | { 53 | unsigned int a[numints]; 54 | int i; 55 | if (n == 0) { /* avoid problems with -0 */ 56 | return 0; 57 | } 58 | memcpy(a, &n, sizeof(a)); 59 | for (i = 1; i < numints; i++) { 60 | a[0] += a[i]; 61 | } 62 | return hashmod(a[0]); 63 | } 64 | 65 | /* 66 | * This bit is taken from lstring, luaS_newlstr() 67 | */ 68 | static int hashstr(const char* str, size_t len) 69 | { 70 | unsigned int h = (unsigned int) len; 71 | /* if string is too long, don't hash all its chars */ 72 | size_t step = (len>>5) + 1; 73 | size_t i; 74 | for (i = len; i >= step; i -= step) { 75 | h = h ^ ((h<<5) + (h>>2) + (unsigned char) str[i-1]); 76 | } 77 | return (int) h; 78 | } 79 | 80 | static int l_hashcode(lua_State* L) 81 | { 82 | int n; 83 | const char* s; 84 | size_t len; 85 | int type = lua_type(L, 1); 86 | 87 | switch (type) { 88 | case LUA_TNUMBER: 89 | n = hashnum(lua_tonumber(L, 1)); 90 | break; 91 | case LUA_TBOOLEAN: 92 | n = hashbool(lua_toboolean(L, 1)); 93 | break; 94 | case LUA_TNIL: 95 | n = 0; 96 | break; 97 | case LUA_TSTRING: 98 | s = lua_tolstring(L, 1, &len); 99 | n = hashstr(s, len); 100 | break; 101 | default: 102 | n = hashptr(lua_topointer(L, 1)); 103 | break; 104 | } 105 | 106 | lua_pushnumber(L, n); 107 | return 1; 108 | } 109 | 110 | /* 111 | ** Adapted from Lua 5.2.0, and then from compat-5.2 112 | */ 113 | void setfuncs (lua_State *L, const luaL_Reg *l, int nup) { 114 | luaL_checkstack(L, nup+1, "too many upvalues"); 115 | for (; l->name != NULL; l++) { /* fill the table with given functions */ 116 | int i; 117 | lua_pushstring(L, l->name); 118 | for (i = 0; i < nup; i++) /* copy upvalues to the top */ 119 | lua_pushvalue(L, -(nup + 1)); 120 | lua_pushcclosure(L, l->func, nup); /* closure with those upvalues */ 121 | /* table must be below the upvalues, the name and the closure */ 122 | lua_settable(L, -(nup + 3)); 123 | } 124 | lua_pop(L, nup); /* remove upvalues */ 125 | } 126 | 127 | static const struct luaL_Reg tbl [] = { 128 | {"hashcode", l_hashcode}, 129 | {NULL, NULL} 130 | }; 131 | 132 | int luaopen_ltrie_c_hashcode(lua_State* L) 133 | { 134 | lua_newtable(L); 135 | setfuncs(L, tbl, 0); 136 | return 1; 137 | } 138 | -------------------------------------------------------------------------------- /ltrie/list.lua: -------------------------------------------------------------------------------- 1 | --- A naive singly linked list, similar to lisp lists. 2 | -- Like Vector, they are dense, ordered, begin at one, and (at least in this 3 | -- implementation) are immutable. 4 | -- @see Vector 5 | -- @module List 6 | 7 | local fun = require 'ltrie.fun' 8 | 9 | local List = {} 10 | local mt = { __index = List } 11 | 12 | List.EMPTY = setmetatable({}, mt) -- Should this be different? 13 | 14 | local function cons(a, b) 15 | return setmetatable({_car = a, _cdr = b}, mt) 16 | end 17 | 18 | local function from_table(t) 19 | local l = List.EMPTY 20 | for i = #t, 1, -1 do 21 | l = cons(t[i], l) 22 | end 23 | return l 24 | end 25 | 26 | --- Creates a list containing the values provided by the given iterable object. 27 | -- @tparam iterable ... a luafun iterable. 28 | -- @usage List.from({1, 2, 3, 4}) 29 | function List.from(...) 30 | if not fun.nth(1, ...) then 31 | return List.EMPTY 32 | elseif List.is_list(...) then 33 | return ... 34 | else 35 | return from_table(fun.totable(...)) 36 | end 37 | end 38 | 39 | --- Creates a list containing the arguments. 40 | -- @param ... the values to include 41 | -- @usage List.of(1, 2, 3, 4) 42 | function List.of(...) 43 | local l = List.EMPTY 44 | for i=select('#', ...), 1, -1 do 45 | l = cons((select(i, ...)), l) 46 | end 47 | return l 48 | end 49 | 50 | 51 | --- Check to see if the given object is a list 52 | -- @param o anything 53 | -- @treturn bool true if List, false if not 54 | function List.is_list(o) 55 | return getmetatable(o) == mt 56 | end 57 | 58 | 59 | local function car(self) 60 | return rawget(self, '_car') 61 | end 62 | 63 | local function cdr(self) 64 | return rawget(self, '_cdr') 65 | end 66 | 67 | local function decons(self) 68 | return car(self), cdr(self) 69 | end 70 | 71 | local function _ipairs(param, state) 72 | if state == List.EMPTY then 73 | return nil 74 | end 75 | 76 | local head, tail = decons(state) 77 | if not List.is_list(tail) then 78 | return List.EMPTY, head, tail 79 | end 80 | 81 | return tail, head 82 | end 83 | 84 | --- @type List 85 | 86 | --- Returns the number of values contained by the list. 87 | -- In lua 5.2 and up, `#list` can be used as a shortcut. 88 | -- @complexity O(n) 89 | -- @usage List.of(1, 2, 3):len() == 3 90 | function List:len() 91 | return fun.length(self) 92 | end 93 | mt.__len = fun.length 94 | 95 | --- Iterate through the list, from beginning to end. 96 | -- 97 | -- Note that instead of returning `index, value` per iteration like normal 98 | -- `ipairs()`, the value of `index` is implementation-defined. 99 | -- @usage for _it, v in my_list:ipairs() do print(v) end 100 | function List:ipairs() 101 | return _ipairs, self, self 102 | end 103 | mt.__ipairs = List.ipairs 104 | 105 | mt.__eq = function(o1, o2) 106 | return rawequal(o1, o2) or (car(o1) == car(o2) and cdr(o2) == cdr(o2)) 107 | end 108 | 109 | function mt.__tostring(l) 110 | local head, tail = l:decons() 111 | 112 | if List.is_list(tail) then 113 | local inner, sep = "", "" 114 | fun.each(function(v) 115 | inner = inner .. sep .. tostring(v) 116 | sep = " " 117 | end, l) 118 | return "(" .. inner .. ")" 119 | end 120 | 121 | return string.format("(%s . %s)", tostring(head), tostring(tail)) 122 | end 123 | 124 | --- Returns a new list with the value added to the beginning. 125 | --@complexity O(1) 126 | --@param val the value to add 127 | --@usage List.of(2, 3):conj(1) == List.of(1, 2, 3) 128 | function List:conj(val) 129 | return cons(val, self) 130 | end 131 | 132 | --- Returns a new list with the first value removed. 133 | -- @complexity O(1) 134 | -- @usage List.of(1, 2, 3):pop() == List.of(2, 3) 135 | function List:pop() 136 | return cdr(self) 137 | end 138 | 139 | --- Returns a new list with the value at index `idx` replaced with `val`. 140 | -- @complexity O(n) 141 | -- @tparam int idx the index 142 | -- @param val the value 143 | -- @usage List.of(0, 0, 0):assoc(2, 'a') == List.of(0, 'a', 0) 144 | function List:assoc(idx, val) 145 | local l = self 146 | local new_l, i = cdr(l), 1 147 | local tmp = {car(l)} 148 | 149 | while i ~= idx do 150 | table.insert(tmp, (car(new_l))) 151 | new_l = cdr(new_l) 152 | i = i + 1 153 | end 154 | new_l = cons(val, new_l) 155 | 156 | for i=#tmp-1, 1, -1 do 157 | new_l = cons(tmp[i], new_l) 158 | end 159 | return new_l 160 | end 161 | 162 | --- Returns the value at index `idx`. 163 | -- @complexity O(n) 164 | -- @tparam int idx the index 165 | -- @return the value, or `nil` if not found. 166 | -- @usage List.of(1, 2, 3):get(2) == 2 167 | function List:get(idx) 168 | return fun.nth(idx, self) 169 | end 170 | 171 | return List 172 | -------------------------------------------------------------------------------- /spec/hashmap_spec.lua: -------------------------------------------------------------------------------- 1 | require 'spec/strict' () 2 | 3 | global 'bit32' 4 | global 'file' 5 | global 'level' 6 | 7 | describe("hashmaps", function() 8 | local A = require 'ltrie.hashmap' 9 | local function totable(new) 10 | local t = {} 11 | for k, v in new:pairs() do 12 | t[k] = v 13 | end 14 | return t 15 | end 16 | 17 | it("implement from()/get()", function() 18 | local cmp = { a = 'a', b = 'c', c = 12 } 19 | local new = A.from(cmp) 20 | 21 | for k, v in pairs(cmp) do 22 | assert.are.equal(v, new:get(k)) 23 | end 24 | 25 | cmp.a = 'b' 26 | assert.are_not.equal(new:get('a'), cmp.a) 27 | end) 28 | 29 | it("implement of()/pairs()", function() 30 | local cmp = { a = 'a', b = 'c', c = 12 } 31 | local new = A.of('a', 'a', 'b', 'c', 'c', 12) 32 | 33 | for k, v in new:pairs() do 34 | assert.are.equal(v, cmp[k]) 35 | end 36 | end) 37 | 38 | it("implement assoc()/len()", function() 39 | local cmp = { a = 'a', b = 'c', c = 12 } 40 | local new = A.from(cmp) 41 | 42 | assert.is.equal(3, new:len()) 43 | cmp.d = "JEFF" 44 | new = new:assoc('d', "JEFF") 45 | assert.is.equal(4, new:len()) 46 | 47 | assert.are.same(cmp, totable(new)) 48 | end) 49 | 50 | it("can overwrite existing values using assoc()", function() 51 | local t = A.from { a = 1 } 52 | 53 | assert.is.equal(1, t:get('a')) 54 | assert.is.equal(1, t:len()) 55 | 56 | t = t:assoc('a', 1) 57 | assert.is.equal(1, t:get('a')) 58 | assert.is.equal(1, t:len()) 59 | 60 | t = t:assoc('a', 4) 61 | assert.is.equal(4, t:get('a')) 62 | assert.is.equal(1, t:len()) 63 | 64 | 65 | t = A.from {a = 1, b = 2, c = 3} 66 | 67 | assert.is.equal(1, t:get('a')) 68 | assert.is.equal(3, t:len()) 69 | 70 | t = t:assoc('a', 1) 71 | assert.is.equal(1, t:get('a')) 72 | assert.is.equal(3, t:len()) 73 | 74 | t = t:assoc('a', 4) 75 | assert.is.equal(4, t:get('a')) 76 | assert.is.equal(3, t:len()) 77 | end) 78 | 79 | it("can hold/delete 2048 elems", function() 80 | local ELEMS = 4096 81 | local tbl = {} 82 | local full = A.of() 83 | for i=1, ELEMS do 84 | tbl[tostring(i)] = i 85 | full = full:assoc(tostring(i), i) 86 | end 87 | 88 | local empty = full 89 | 90 | assert.are_not.equal(full:get('1929'), full:get('1609')) 91 | 92 | local elen = empty:len() 93 | for k, v in pairs(tbl) do 94 | assert.are.equal(v, empty:get(k)) 95 | empty = empty:dissoc(k) 96 | assert.are.equal(nil, empty:get(k)) 97 | elen = elen - 1 98 | assert.are.equal(elen, empty:len()) 99 | end 100 | 101 | assert.is.equal(empty:len(), 0) 102 | assert.is.equal(full:len(), ELEMS) 103 | end) 104 | 105 | it("implement dissoc()", function() 106 | local cmp = { a = 'a', b = 'c', c = 12 } 107 | local new = A.from(cmp) 108 | 109 | cmp.c = nil 110 | assert.is.equal(12, new:get('c')) 111 | new = new:dissoc('c') 112 | assert.is.equal(2, new:len()) 113 | assert.is.equal(nil, new:get('c')) 114 | end) 115 | end) 116 | 117 | describe("large hashmaps", function() 118 | local A = require 'ltrie.hashmap' 119 | local ELEMS = 4096 120 | 121 | local function make_hmap() 122 | local tbl = {} 123 | local full = A.of() 124 | for i=1, ELEMS do 125 | tbl[tostring(i)] = i 126 | full = full:assoc(tostring(i), i) 127 | end 128 | return full 129 | end 130 | 131 | it("can be created", function() 132 | local t = make_hmap() 133 | 134 | for i=1, ELEMS do 135 | assert.equal(i, t:get(tostring(i))) 136 | end 137 | 138 | end) 139 | 140 | it("can be modified", function() 141 | local t = make_hmap() 142 | 143 | local new_t = t:assoc('420', 69) 144 | assert.not_equal(69, t:get('420')) 145 | for i=1, ELEMS do 146 | local v = i 147 | if i == 420 then 148 | v = 69 149 | end 150 | assert.equal(v, new_t:get(tostring(i))) 151 | end 152 | end) 153 | 154 | it("can be emptied", function() 155 | local full = make_hmap() 156 | local empty = full 157 | local elen = empty:len() 158 | for i=1, ELEMS do 159 | local k, v = tostring(i), i 160 | assert.are.equal(v, empty:get(k)) 161 | empty = empty:dissoc(k) 162 | assert.are.equal(nil, empty:get(k)) 163 | elen = elen - 1 164 | assert.are.equal(elen, empty:len()) 165 | end 166 | 167 | assert.is.equal(empty:len(), 0) 168 | assert.is.equal(full:len(), ELEMS) 169 | 170 | end) 171 | 172 | it("can be iterated over", function() 173 | local t = make_hmap() 174 | local checkboxes = {} 175 | for _it, k, v in t:iter() do 176 | assert.equal(k, tostring(v)) 177 | checkboxes[v] = v 178 | end 179 | 180 | for i=1, ELEMS do 181 | assert.equal(i, checkboxes[i]) 182 | end 183 | 184 | for k, v in t:pairs() do 185 | assert.equal(checkboxes[v], v) 186 | assert.equal(k, tostring(v)) 187 | end 188 | 189 | end) 190 | 191 | end) 192 | -------------------------------------------------------------------------------- /spec/fun/slicing_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Slicing", function() 2 | local fun = require 'ltrie.fun' 3 | 4 | local T = {} 5 | local function reset() 6 | T = {} 7 | end 8 | local function p(...) 9 | T[#T+1] = {...} 10 | end 11 | local function check(t) 12 | assert.are.same(T, t) 13 | end 14 | 15 | it("nth", function() 16 | assert.is.equal(2, fun.nth(2, fun.range(5))) 17 | assert.is_nil(fun.nth(10, fun.range(5))) 18 | assert.is_nil(fun.nth(2, fun.range(0))) 19 | assert.is.equal('b', fun.nth(2, {'a', 'b', 'c', 'd', 'e'})) 20 | assert.are.same({2, 'b'}, {fun.nth(2, fun.enumerate {'a', 'b', 'c', 'd', 'e'})}) 21 | assert.is.equal('b', fun.nth(2, "abcdef")) 22 | end) 23 | 24 | it("head", function() 25 | assert.is.equal('a', fun.head {'a', 'b', 'c', 'd', 'e'}) 26 | assert.has_errors(function() 27 | return fun.head {} 28 | end) 29 | assert.has_errors(function() 30 | return fun.head(fun.range()) 31 | end) 32 | assert.are.same({1, 'a'}, {fun.head(fun.enumerate {'a', 'b'})}) 33 | 34 | assert.is.equal(fun.car, fun.head) 35 | end) 36 | 37 | it('tail', function() 38 | reset() 39 | fun.each(p, fun.tail {'a', 'b', 'c', 'd', 'e'}) 40 | check {{'b'}, {'c'}, {'d'}, {'e'}} 41 | 42 | reset() 43 | fun.each(p, fun.tail {}) 44 | check {} 45 | 46 | reset() 47 | fun.each(p, fun.tail(fun.range(0))) 48 | check {} 49 | 50 | reset() 51 | fun.each(p, fun.tail(fun.enumerate {'a', 'b'})) 52 | check {{2, 'b'}} 53 | 54 | assert.is.equal(fun.cdr, fun.tail) 55 | end) 56 | 57 | it('take_n', function() 58 | reset() 59 | fun.each(p, fun.take_n(0, fun.duplicate(48))) 60 | check {} 61 | 62 | reset() 63 | fun.each(p, fun.take_n(5, fun.range(0))) 64 | check {} 65 | 66 | reset() 67 | fun.each(p, fun.take_n(1, fun.duplicate(48))) 68 | check {{48}} 69 | 70 | reset() 71 | fun.each(p, fun.take_n(5, fun.duplicate(48))) 72 | check {{48}, {48}, {48}, {48}, {48}} 73 | 74 | reset() 75 | fun.each(p, fun.take_n(5, fun.enumerate(fun.duplicate('x')))) 76 | check {{1, 'x'}, {2, 'x'}, {3, 'x'}, {4, 'x'}, {5, 'x'}} 77 | end) 78 | 79 | it('take_while', function() 80 | reset() 81 | fun.each(p, fun.take_while(function(x) 82 | return x < 5 83 | end, fun.range(10))) 84 | check {{1}, {2}, {3}, {4}} 85 | 86 | reset() 87 | fun.each(p, fun.take_while(function(x) 88 | return x < 5 89 | end, fun.range(0))) 90 | check {} 91 | 92 | reset() 93 | fun.each(p, fun.take_while(function(x) 94 | return x > 100 95 | end, fun.range(10))) 96 | check {} 97 | 98 | reset() 99 | fun.each(p, fun.take_while(function(i, a) 100 | return i ~= a 101 | end, fun.enumerate {5, 2, 1, 3, 4})) 102 | check {{1, 5}} 103 | end) 104 | 105 | it('take', function() 106 | reset() 107 | fun.each(p, fun.take(function(x) 108 | return x < 5 109 | end, fun.range(10))) 110 | check {{1}, {2}, {3}, {4}} 111 | 112 | reset() 113 | fun.each(p, fun.take(5, fun.duplicate(48))) 114 | check {{48}, {48}, {48}, {48}, {48}} 115 | end) 116 | 117 | it('drop_n', function() 118 | reset() 119 | fun.each(p, fun.drop_n(5, fun.range(10))) 120 | check {{6}, {7}, {8}, {9}, {10}} 121 | 122 | reset() 123 | fun.each(p, fun.drop_n(0, fun.range(5))) 124 | check {{1}, {2}, {3}, {4}, {5}} 125 | 126 | reset() 127 | fun.each(p, fun.drop_n(5, fun.range(0))) 128 | check {} 129 | 130 | reset() 131 | fun.each(p, fun.drop_n(2, fun.enumerate {'a', 'b', 'c', 'd', 'e'})) 132 | check {{3, 'c'}, {4, 'd'}, {5, 'e'}} 133 | end) 134 | 135 | it('drop_while', function() 136 | reset() 137 | fun.each(p, fun.drop_while(function(x) 138 | return x < 5 139 | end, fun.range(10))) 140 | check {{5}, {6}, {7}, {8}, {9}, {10}} 141 | 142 | reset() 143 | fun.each(p, fun.drop_while(function(x) 144 | return x < 5 145 | end, fun.range(0))) 146 | check {} 147 | 148 | reset() 149 | fun.each(p, fun.drop_while(function(x) 150 | return x > 100 151 | end, fun.range(10))) 152 | check {{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}} 153 | 154 | reset() 155 | fun.each(p, fun.drop_while(function(i, a) 156 | return i ~= a 157 | end, fun.enumerate {5, 2, 1, 3, 4})) 158 | check {{2, 2}, {3, 1}, {4, 3}, {5, 4}} 159 | 160 | reset() 161 | fun.each(p, fun.drop_while(function(i, a) 162 | return i ~= a 163 | end, fun.zip({1, 2, 3, 4, 5}, {5, 4, 3, 2, 1}))) 164 | check {{3, 3}, {4, 2}, {5, 1}} 165 | end) 166 | 167 | it('drop', function() 168 | reset() 169 | fun.each(p, fun.drop(5, fun.range(10))) 170 | check {{6}, {7}, {8}, {9}, {10}} 171 | 172 | reset() 173 | fun.each(p, fun.drop(function(x) 174 | return x < 5 175 | end, fun.range(10))) 176 | check {{5}, {6}, {7}, {8}, {9}, {10}} 177 | end) 178 | 179 | it('span', function() 180 | reset() 181 | fun.each(p, fun.zip(fun.span(function(x) 182 | return x < 5 183 | end, fun.range(10)))) 184 | check {{1, 5}, {2, 6}, {3, 7}, {4, 8}} 185 | 186 | reset() 187 | fun.each(p, fun.zip(fun.span(5, fun.range(10)))) 188 | check {{1, 6}, {2, 7}, {3, 8}, {4, 9}, {5, 10}} 189 | 190 | reset() 191 | fun.each(p, fun.zip(fun.span(function(x) 192 | return x < 5 193 | end, fun.range(0)))) 194 | check {} 195 | 196 | reset() 197 | fun.each(p, fun.zip(fun.span(function(x) 198 | return x < 5 199 | end, fun.range(5)))) 200 | check {{1, 5}} 201 | 202 | assert.is.equal(fun.split, fun.span) 203 | assert.is.equal(fun.split_at, fun.span) 204 | end) 205 | end) 206 | 207 | -------------------------------------------------------------------------------- /spec/fun/operators_spec.lua: -------------------------------------------------------------------------------- 1 | describe("Operators", function() 2 | local fun = require 'ltrie.fun' 3 | local op = fun.operator 4 | 5 | it("comparisons", function() 6 | local o = op.le 7 | assert.is_true (o(0, 1)) 8 | assert.is_false (o(1, 0)) 9 | assert.is_true (o(0, 0)) 10 | assert.is_true (o('abc', 'cde')) 11 | assert.is_false (o('cde', 'abc')) 12 | assert.is_true (o('abc', 'abc')) 13 | 14 | local o = op.lt 15 | assert.is_true (o(0, 1)) 16 | assert.is_false (o(1, 0)) 17 | assert.is_false (o(0, 0)) 18 | assert.is_true (o('abc', 'cde')) 19 | assert.is_false (o('cde', 'abc')) 20 | assert.is_false (o('abc', 'abc')) 21 | 22 | local o = op.eq 23 | assert.is_false (o(0, 1)) 24 | assert.is_false (o(1, 0)) 25 | assert.is_true (o(0, 0)) 26 | assert.is_false (o('abc', 'cde')) 27 | assert.is_false (o('cde', 'abc')) 28 | assert.is_true (o('abc', 'abc')) 29 | 30 | local o = op.ne 31 | assert.is_true (o(0, 1)) 32 | assert.is_true (o(1, 0)) 33 | assert.is_false (o(0, 0)) 34 | assert.is_true (o('abc', 'cde')) 35 | assert.is_true (o('cde', 'abc')) 36 | assert.is_false (o('abc', 'abc')) 37 | 38 | local o = op.ge 39 | assert.is_false (o(0, 1)) 40 | assert.is_true (o(1, 0)) 41 | assert.is_true (o(0, 0)) 42 | assert.is_false (o('abc', 'cde')) 43 | assert.is_true (o('cde', 'abc')) 44 | assert.is_true (o('abc', 'abc')) 45 | 46 | local o = op.gt 47 | assert.is_false (o(0, 1)) 48 | assert.is_true (o(1, 0)) 49 | assert.is_false (o(0, 0)) 50 | assert.is_false (o('abc', 'cde')) 51 | assert.is_true (o('cde', 'abc')) 52 | assert.is_false (o('abc', 'abc')) 53 | end) 54 | 55 | it("arithmetic", function() 56 | assert.is.equal(0, op.add(-1.0, 1.0)) 57 | assert.is.equal(0, op.add(0, 0)) 58 | assert.is.equal(14, op.add(12, 2)) 59 | 60 | local function is_close(state, args) 61 | local a, b = unpack(args) 62 | return math.abs(a - b) <= 1e-8 63 | end 64 | local say = require 'say' 65 | say:set("assert.is_close.positive", "Expected %s to be close to %s") 66 | say:set("assert.is_close.negative", "Expected %s to be far from %s") 67 | assert:register('assertion', 'close', is_close, 68 | "assert.is_close.positive", "assert.is_close.negative") 69 | 70 | assert.is.equal(5, op.div(10, 2)) 71 | assert.is.close(3.3333333333333, op.div(10, 3)) 72 | assert.is.close(-3.3333333333333, op.div(-10, 3)) 73 | 74 | assert.is.equal(3, op.floordiv(10, 3)) 75 | assert.is.equal(3, op.floordiv(11, 3)) 76 | assert.is.equal(4, op.floordiv(12, 3)) 77 | assert.is.equal(-4, op.floordiv(-10, 3)) 78 | assert.is.equal(-4, op.floordiv(-11, 3)) 79 | assert.is.equal(-4, op.floordiv(-12, 3)) 80 | 81 | assert.is.equal(3, op.intdiv(10, 3)) 82 | assert.is.equal(3, op.intdiv(11, 3)) 83 | assert.is.equal(4, op.intdiv(12, 3)) 84 | assert.is.equal(-3, op.intdiv(-10, 3)) 85 | assert.is.equal(-3, op.intdiv(-11, 3)) 86 | assert.is.equal(-4, op.intdiv(-12, 3)) 87 | 88 | assert.is.close(3.3333333333333, op.truediv(10, 3)) 89 | assert.is.close(3.6666666666667, op.truediv(11, 3)) 90 | assert.is.equal(4, op.truediv(12, 3)) 91 | assert.is.close(-3.3333333333333, op.truediv(-10, 3)) 92 | assert.is.close(-3.6666666666667, op.truediv(-11, 3)) 93 | assert.is.equal(-4, op.truediv(-12, 3)) 94 | 95 | assert.is.equal(0, op.mod(10, 2)) 96 | assert.is.equal(1, op.mod(10, 3)) 97 | assert.is.equal(2, op.mod(-10, 3)) 98 | 99 | assert.is.equal(1, op.mul(10, 0.1)) 100 | assert.is.equal(0, op.mul(0, 0)) 101 | assert.is.equal(1, op.mul(-1, -1)) 102 | 103 | assert.is.equal(-1, op.neq(1)) 104 | assert.is.equal(true, op.neq(0) == 0) 105 | assert.is.equal(true, op.neq(-0) == 0) 106 | assert.is.equal(1, op.neq(-1)) 107 | 108 | assert.is.equal(-1, op.unm(1)) 109 | assert.is.equal(true, op.unm(0) == 0) 110 | assert.is.equal(true, op.unm(-0) == 0) 111 | assert.is.equal(1, op.unm(-1)) 112 | 113 | assert.is.equal(8, op.pow(2, 3)) 114 | assert.is.equal(0, op.pow(0, 10)) 115 | assert.is.equal(1, op.pow(2, 0)) 116 | 117 | assert.is.equal(-1, op.sub(2, 3)) 118 | assert.is.equal(-10, op.sub(0, 10)) 119 | assert.is.equal(0, op.sub(2, 2)) 120 | end) 121 | 122 | it("logical", function() 123 | local o = op.land 124 | assert.is.equal(true, o(true, true)) 125 | assert.is.equal(false, o(true, false)) 126 | assert.is.equal(false, o(false, true)) 127 | assert.is.equal(false, o(false, false)) 128 | assert.is.equal(0, o(1, 0)) 129 | assert.is.equal(1, o(0, 1)) 130 | assert.is.equal(1, o(1, 1)) 131 | assert.is.equal(0, o(0, 0)) 132 | 133 | local o = op.lor 134 | assert.is.equal(true, o(true, true)) 135 | assert.is.equal(true, o(true, false)) 136 | assert.is.equal(true, o(false, true)) 137 | assert.is.equal(false, o(false, false)) 138 | assert.is.equal(1, o(1, 0)) 139 | assert.is.equal(0, o(0, 1)) 140 | assert.is.equal(1, o(1, 1)) 141 | assert.is.equal(0, o(0, 0)) 142 | 143 | assert.is.equal(false, op.lnot(true)) 144 | assert.is.equal(true, op.lnot(false)) 145 | assert.is.equal(1, op.lor(1)) 146 | assert.is.equal(0, op.lor(0)) 147 | 148 | assert.is.equal(true, op.truth(true)) 149 | assert.is.equal(false, op.truth(false)) 150 | assert.is.equal(true, op.truth(1)) 151 | assert.is.equal(true, op.truth(0)) 152 | assert.is.equal(false, op.truth(nil)) 153 | assert.is.equal(true, op.truth("")) 154 | assert.is.equal(true, op.truth({})) 155 | end) 156 | end) 157 | 158 | -------------------------------------------------------------------------------- /spec/vector_spec.lua: -------------------------------------------------------------------------------- 1 | require 'spec/strict' () 2 | global 'bit32' 3 | global 'file' 4 | global 'level' 5 | 6 | describe("Persistent Vectors", function() 7 | local Vector = require 'ltrie.vector' 8 | 9 | local _tbl = {} 10 | for i=1, 2048 do 11 | table.insert(_tbl, 2048 - i) 12 | end 13 | 14 | local _vec = Vector.from(ipairs(_tbl)) 15 | 16 | local vec, tbl, tl 17 | before_each(function() 18 | tbl = setmetatable({}, {__index = _tbl}) 19 | tl = #_tbl 20 | vec = _vec 21 | end) 22 | 23 | it("implements get()", function() 24 | for i, v in vec:ipairs() do 25 | assert.equal(tbl[i], vec:get(i)) 26 | end 27 | end) 28 | 29 | it("implements len()", function() 30 | assert.equal(tl, vec:len()) 31 | end) 32 | 33 | it("implements conj()", function() 34 | local top = vec:len() 35 | for _, v in ipairs {'a', 'b', 'c', 'd'} do 36 | tl = tl + 1 37 | tbl[tl] = v 38 | vec = vec:conj(v) 39 | end 40 | 41 | for i = top, vec:len() do 42 | assert.equal(tbl[i], vec:get(i)) 43 | end 44 | end) 45 | 46 | it("implements assoc()", function() 47 | local l = vec:len() 48 | local mergeIn = {{l, "TOP"}} 49 | for i=1, 100 do 50 | table.insert(mergeIn, {math.random(l), "new#" .. i}) 51 | end 52 | for _, v in ipairs(mergeIn) do 53 | local car, cdr = unpack(v) 54 | tbl[car] = cdr 55 | vec = vec:assoc(car, cdr) 56 | end 57 | 58 | for i, v in vec:ipairs() do 59 | assert.equal(tbl[i], vec:get(i)) 60 | end 61 | end) 62 | 63 | it("implements pop()", function() 64 | local l = vec:len() 65 | assert.not_nil(vec:get(l)) 66 | local v2 = vec:pop() 67 | assert.is_nil(v2:get(l)) 68 | assert.equal(l - 1, v2:len()) 69 | end) 70 | 71 | it("implements withMutations()", function() 72 | local v2 = vec:withMutations(function(v) 73 | return v:conj('c') 74 | end) 75 | 76 | assert.equal('c', v2:get(v2:len())) 77 | assert.not_equal(v2:get(v2:len()), vec:get(vec:len())) 78 | end) 79 | 80 | it("implements unpack()", function() 81 | local v = Vector.of(3, 2, 1) 82 | assert.same({v:unpack()}, {3, 2, 1}) 83 | end) 84 | end) 85 | 86 | describe("Transient vectors", function() 87 | local Vector = require 'ltrie.vector' 88 | 89 | local _tbl = {} 90 | for i=1, 2048 do 91 | table.insert(_tbl, 2048 - i) 92 | end 93 | local _vec = Vector.from(ipairs(_tbl)) 94 | 95 | local vec, tbl, tl 96 | before_each(function() 97 | tbl = setmetatable({}, {__index = _tbl}) 98 | tl = #_tbl 99 | vec = _vec 100 | end) 101 | 102 | it("can conj()", function() 103 | local add = { 'a', 'b', 'c', 'd', 'e' } 104 | local e = vec:len() 105 | local v2 = vec:withMutations(function(v) 106 | for i, _v in ipairs(add) do 107 | v = v:conj(_v) 108 | end 109 | return v 110 | end) 111 | for i, v in ipairs(add) do 112 | assert.equal(v, v2:get(e + i)) 113 | end 114 | end) 115 | 116 | it("can assoc()", function() 117 | local l = vec:len() 118 | local mergeIn = {{l, "TOP"}} 119 | for i=1, 100 do 120 | table.insert(mergeIn, {math.random(l), "new#" .. i}) 121 | end 122 | local v2 = vec:withMutations(function(v) 123 | for _, val in ipairs(mergeIn) do 124 | local car, cdr = unpack(val) 125 | tbl[car] = cdr 126 | v = v:assoc(car, cdr) 127 | end 128 | return v 129 | end) 130 | 131 | for _, pair in ipairs(mergeIn) do 132 | local i, v = unpack(pair) 133 | assert.equal(tbl[i], v2:get(i)) 134 | assert.not_equal(v2:get(i), vec:get(i)) 135 | end 136 | end) 137 | 138 | it("can pop()", function() 139 | local v2 = vec:withMutations(function(v) 140 | for i=1, 20 do 141 | v:pop() 142 | end 143 | return v 144 | end) 145 | assert.equal(vec:len() - 20, v2:len()) 146 | end) 147 | end) 148 | 149 | describe("subvec", function() 150 | local Vector = require 'ltrie.vector' 151 | local Subvec = require 'ltrie.subvec' 152 | local fun = require 'ltrie.fun' 153 | 154 | local vec = Vector.of() 155 | for i=1, 50 do 156 | vec = vec:conj(i) 157 | end 158 | 159 | it("implements len()", function() 160 | local sv = Subvec.new(vec, 1, 2) 161 | assert.equal(sv:len(), 2) 162 | 163 | local sv = Subvec.new(vec, 2, 2) 164 | assert.equal(sv:len(), 1) 165 | 166 | local sv = Subvec.new(vec, 2, 50) 167 | assert.equal(sv:len(), 49) 168 | end) 169 | 170 | it("implements get()", function() 171 | local sv = Subvec.new(vec, 1, 20) 172 | assert.equal(sv:len(), 20) 173 | for i=1, 20 do 174 | assert.equal(sv:get(i), vec:get(i)) 175 | end 176 | 177 | sv = Subvec.new(vec, 21, 40) 178 | for i=1, 20 do 179 | assert.equal(sv:get(i), vec:get(i + 20)) 180 | end 181 | end) 182 | 183 | it("implements ipairs()", function() 184 | local sv = Subvec.new(vec, 11, 20) 185 | assert.equal(sv:len(), 10) 186 | for i, v in sv:ipairs() do 187 | assert.equal(v, vec:get(i+10)) 188 | end 189 | end) 190 | 191 | it("is iterable", function() 192 | local sv = Subvec.new(vec, 11, 20) 193 | assert.equal(sv:len(), 10) 194 | fun.each(function(i, v) 195 | assert.equal(v, vec:get(i+10)) 196 | end, fun.enumerate(sv)) 197 | end) 198 | 199 | it("implements conj() and assoc()", function() 200 | local sv = Subvec.new(vec, 1, 5) 201 | 202 | sv = sv:conj('a') 203 | assert.equal(sv:get(6), 'a') 204 | assert.not_equal(vec:get(6), 'a') 205 | 206 | sv = sv:assoc(2, 'b') 207 | assert.equal(sv:get(2), 'b') 208 | assert.not_equal(vec:get(2), 'b') 209 | end) 210 | 211 | it("implements pop()", function() 212 | local sv = Subvec.new(vec, 1, 5) 213 | assert.equal(sv:len(), 5) 214 | 215 | sv = sv:pop() 216 | assert.equal(sv:len(), 4) 217 | end) 218 | end) 219 | -------------------------------------------------------------------------------- /spec/fun/basic_spec.lua: -------------------------------------------------------------------------------- 1 | require 'spec/strict' () 2 | 3 | global 'bit32' 4 | global 'file' 5 | global 'level' 6 | 7 | describe("Basic", function() 8 | local fun = require 'ltrie.fun' 9 | 10 | local T = {} 11 | local function reset() 12 | T = {} 13 | end 14 | local function p(...) 15 | T[#T+1] = {...} 16 | end 17 | local function check(t) 18 | assert.are.same(T, t) 19 | end 20 | 21 | it("Arrays", function() 22 | reset() 23 | for _it, a in fun.iter{1, 2, 3} do p(a) end 24 | check {{1}, {2}, {3}} 25 | 26 | reset() 27 | for _it, a in fun.iter(fun.iter(fun.iter{1, 2, 3})) do p(a) end 28 | check {{1}, {2}, {3}} 29 | 30 | reset() 31 | for _it, a in fun.wrap(fun.wrap(fun.iter{1, 2, 3})) do p(a) end 32 | check {{1}, {2}, {3}} 33 | 34 | reset() 35 | for _it, a in fun.wrap(fun.wrap(ipairs{1, 2, 3})) do p(a) end 36 | check {{1}, {2}, {3}} 37 | 38 | reset() 39 | for _it, a in fun.iter{} do p(a) end 40 | check {} 41 | 42 | reset() 43 | for _it, a in fun.iter(fun.iter(fun.iter{})) do p(a) end 44 | check {} 45 | 46 | reset() 47 | for _it, a in fun.wrap(fun.wrap(fun.iter{})) do p(a) end 48 | check {} 49 | 50 | reset() 51 | for _it, a in fun.wrap(fun.wrap(ipairs{})) do p(a) end 52 | check {} 53 | 54 | -- check that iter() is equivalent to ipairs() 55 | local t = {1, 2, 3} 56 | local t1 = {fun.iter(t):unwrap()} 57 | local t2 = {ipairs(t)} 58 | assert.are.same(t1, t2) 59 | 60 | -- check that wrap() does nothing to wrapped iterators 61 | local t1 = {fun.iter{1, 2, 3}} 62 | local t2 = {fun.wrap(unpack(t1)):unwrap()} 63 | assert.are.same(t1, t2) 64 | end) 65 | 66 | it("Maps", function() 67 | reset() 68 | t = {} 69 | for _it, k, v in fun.iter{a = 1, b = 2, c = 3} do 70 | t[#t + 1] = k 71 | end 72 | table.sort(t) 73 | for _it, v in fun.iter(t) do 74 | p(v) 75 | end 76 | check {{'a'}, {'b'}, {'c'}} 77 | 78 | reset() 79 | t = {} 80 | for _it, k, v in fun.iter(fun.iter(fun.iter{a = 1, b = 2, c = 3})) do 81 | t[#t + 1] = k 82 | end 83 | table.sort(t) 84 | for _it, v in fun.iter(t) do 85 | p(v) 86 | end 87 | check {{'a'}, {'b'}, {'c'}} 88 | 89 | 90 | reset() 91 | t = {} 92 | for _it, k, v in fun.iter{} do 93 | p(k, v) 94 | end 95 | check {} 96 | 97 | reset() 98 | t = {} 99 | for _it, k, v in fun.iter(fun.iter(fun.iter{})) do 100 | p(k, v) 101 | end 102 | check {} 103 | end) 104 | 105 | it("Strings", function() 106 | reset() 107 | for _it, a in fun.iter("abcd") do p(a) end 108 | check {{'a'}, {'b'}, {'c'}, {'d'}} 109 | 110 | reset() 111 | for _it, a in fun.iter(fun.iter(fun.iter("abcd"))) do p(a) end 112 | check {{'a'}, {'b'}, {'c'}, {'d'}} 113 | 114 | reset() 115 | for _it, a in fun.iter("") do p(a) end 116 | check {} 117 | 118 | reset() 119 | for _it, a in fun.iter(fun.iter(fun.iter(""))) do p(a) end 120 | check {} 121 | end) 122 | 123 | it("Custom Generators", function() 124 | local function mypairs_gen(max, state) 125 | if state >= max then 126 | return nil 127 | end 128 | return state + 1, state + 1 129 | end 130 | 131 | local function mypairs(max) 132 | return mypairs_gen, max, 0 133 | end 134 | 135 | reset() 136 | for _it, a in fun.iter(mypairs(10)) do p(a) end 137 | check {{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10}} 138 | 139 | assert.has_errors(function() 140 | reset() 141 | for _it, a in fun.iter(1) do p(a) end 142 | end) 143 | 144 | assert.has_errors(function() 145 | reset() 146 | for _it, a in fun.iter(1, 2, 3) do p(a) end 147 | end) 148 | end) 149 | 150 | it("each", function() 151 | reset() 152 | fun.each(p, {1, 2, 3}) 153 | check {{1}, {2}, {3}} 154 | 155 | reset() 156 | fun.each(p, fun.iter{1, 2, 3}) 157 | check {{1}, {2}, {3}} 158 | 159 | reset() 160 | fun.each(p, fun.iter{}) 161 | check {} 162 | 163 | local ks, vs = {}, {} 164 | fun.each(function(k, v) table.insert(ks, k) table.insert(vs, v) end, 165 | {a = 1, b = 2, c = 3}) 166 | reset() 167 | table.sort(ks) 168 | fun.each(p, ks) 169 | check({{'a'}, {'b'}, {'c'}}) 170 | reset() 171 | table.sort(vs) 172 | fun.each(p, vs) 173 | check({{1}, {2}, {3}}) 174 | 175 | reset() 176 | fun.each(p, "abc") 177 | check({{'a'}, {'b'}, {'c'}}) 178 | 179 | reset() 180 | fun.each(p, fun.iter "abc") 181 | check({{'a'}, {'b'}, {'c'}}) 182 | 183 | assert.is.equal(fun.each, fun.for_each) 184 | assert.is.equal(fun.each, fun.foreach) 185 | end) 186 | 187 | it("totable", function() 188 | local tab = fun.totable(fun.range(5)) 189 | assert.is.equal(type(tab), "table") 190 | assert.is.equal(#tab, 5) 191 | reset() 192 | fun.each(p, tab) 193 | check {{1}, {2}, {3}, {4}, {5}} 194 | 195 | tab = fun.totable(fun.range(0)) 196 | assert.is.equal(type(tab), "table") 197 | assert.is.equal(#tab, 0) 198 | reset() 199 | fun.each(p, tab) 200 | check {} 201 | 202 | tab = fun.totable("abcdef") 203 | assert.is.equal(type(tab), "table") 204 | assert.is.equal(#tab, 6) 205 | reset() 206 | fun.each(p, tab) 207 | check {{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}} 208 | 209 | tab = fun.totable {'a', {'b', 'c'}, {'d', 'e', 'f'}} 210 | assert.is.equal(type(tab), "table") 211 | assert.is.equal(#tab, 3) 212 | reset() 213 | fun.each(p, tab[1]) 214 | fun.each(p, fun.map(unpack, fun.drop(1, tab))) 215 | check {{'a'}, {'b', 'c'}, {'d', 'e','f'}} 216 | end) 217 | 218 | it("tomap", function() 219 | local tab = fun.tomap{a = 1, b = 2, c = 3} 220 | assert.is.equal(type(tab), "table") 221 | assert.is.equal(#tab, 0) 222 | local t = {} 223 | for _it, k, v in fun.iter(tab) do t[v] = k end 224 | table.sort(t) 225 | reset() 226 | for k, v in ipairs(t) do p(k, v) end 227 | check {{1, 'a'}, {2, 'b'}, {3, 'c'}} 228 | 229 | local tab = fun.tomap(fun.enumerate("abcdef")) 230 | assert.is.equal(type(tab), "table") 231 | assert.is.equal(#tab, 6) 232 | reset() 233 | fun.each(p, tab) 234 | check {{'a'}, {'b'}, {'c'}, {'d'}, {'e'}, {'f'}} 235 | end) 236 | end) 237 | 238 | -------------------------------------------------------------------------------- /ltrie/vector.lua: -------------------------------------------------------------------------------- 1 | --- An immutable vector datatype, modeled after Clojure's PersistentVector. 2 | -- It can be used to store sequential data, much like Lua tables: 3 | -- 4 | -- local my_vector = Vector.from('a', 'b', 'c') 5 | -- print(my_vector:get(2)) -- 'c' 6 | -- 7 | -- But because they are persistent, modifications create a new vector instead 8 | -- of changing the old one: 9 | -- 10 | -- local my_new_vector = my_vector:set(2, 'd') 11 | -- print(my_new_vector:get(2)) -- 'd' 12 | -- print(my_vector:get(2)) -- still 'c' 13 | -- 14 | -- Vectors are dense, ordered, and indexed-by-one, meaning a vector will never 15 | -- have an element whose index doesn't fall in between `[1 .. v:len()]`. 16 | -- 17 | -- `nil` is a valid element, however, so a vector like: 18 | -- 19 | -- Vector.of(1, nil, nil, 4) 20 | -- 21 | -- has a size of four, and iterating through it 22 | -- will include the nil values. 23 | -- 24 | -- @module Vector 25 | 26 | -- Implementation Notes 27 | -- ==================== 28 | -- 29 | -- A good blogpost explaining the structure's implementation: 30 | -- 31 | -- 32 | -- 33 | -- My reference code was this particular commit: 34 | -- 35 | -- 36 | -- 37 | -- To match Lua's index-by-one semantics I had to introduce an offset or two. 38 | -- Every line I've done that on is marked with a +1 or -1 comment. 39 | 40 | local function try(...) 41 | local ok, err = pcall(...) 42 | if not ok then return nil end 43 | return err 44 | end 45 | local b = try(require, 'bit32') or try(require, 'bit') or error("No bitop lib found") 46 | 47 | local BITS = 5 48 | local WIDTH = 32 49 | local MASK = 31 50 | 51 | local Vector = {} 52 | 53 | local mt = { 54 | __index = Vector, 55 | } 56 | 57 | local function Vec(data) 58 | assert(data.count) 59 | assert(data.shift) 60 | assert(data.root) 61 | assert(data.tail) 62 | return setmetatable(data, mt) 63 | end 64 | 65 | local EMPTY = Vec { 66 | count = 0, 67 | shift = BITS, 68 | root = {}, 69 | tail = {}, 70 | } 71 | 72 | --- Creates a vector containing the elements provided by the given iterator. 73 | -- @tparam iterator genparamstate the iterator 74 | -- @usage Vector.from(ipairs {1, 2, 3, 4}) 75 | function Vector.from(...) 76 | local r = EMPTY:asMutable() 77 | for _, v in ... do 78 | r = r:conj(v) 79 | end 80 | r:asImmutable() 81 | return r 82 | end 83 | 84 | --- Creates a vector containing the arguments. 85 | -- @param ... the arguments to include 86 | -- @usage Vector.of(1, 2, 3, 4) 87 | function Vector.of(...) 88 | local r = EMPTY:asMutable() 89 | for i=1, select('#', ...) do 90 | r = r:conj(select(i, ...)) 91 | end 92 | r:asImmutable() 93 | return r 94 | end 95 | 96 | --- Checks to see if given object is a vector. 97 | -- @param o anything 98 | -- @treturn bool `true` if List, false if not 99 | function Vector.is_vector(o) 100 | return getmetatable(o) == mt 101 | end 102 | 103 | --- @type Vector 104 | 105 | --- Returns the number of elements contained in the vector. 106 | -- In lua 5.2 and up, `#vector` can be used as a shortcut. 107 | -- 108 | -- @complexity O(1) 109 | -- @usage Vector.of(1, 2, 3):len() == 3 110 | function Vector:len() 111 | return self.count 112 | end 113 | mt.__len = Vector.len 114 | 115 | local function mask(i) 116 | return b.band(i, MASK) 117 | end 118 | 119 | local function tailoff(trie) 120 | if trie.count < WIDTH then 121 | return 0 122 | else 123 | local last_idx = trie.count - 1 124 | return b.lshift(b.rshift(last_idx, BITS), BITS) 125 | end 126 | end 127 | 128 | local OOB = {} 129 | 130 | local function arrayFor(trie, idx) 131 | if not(idx >= 0 and idx < trie.count) then 132 | return OOB 133 | end 134 | 135 | if idx >= tailoff(trie) then 136 | return trie.tail 137 | end 138 | 139 | local node = trie.root 140 | local level = trie.shift 141 | while level > 0 do 142 | local newidx = mask(b.rshift(idx, level)) + 1 -- +1 143 | node = node[newidx] 144 | level = level - BITS 145 | end 146 | return node 147 | end 148 | 149 | --- Get a value by index. Vector indexes start at one. 150 | -- 151 | -- @complexity O(log32 n) 152 | -- @tparam int idx the index 153 | -- @return the value, or `nil` if not found 154 | -- @usage Vector.of(1, 2, 3):get(1) == 1 155 | function Vector:get(idx) 156 | idx = idx - 1 -- -1 157 | local node = arrayFor(self, idx) 158 | if node == OOB then return nil end 159 | return node[mask(idx) + 1] -- +1 160 | end 161 | -- mt.__index = Vector.get 162 | 163 | local function iter(param, state) 164 | local vec, idx = param, state 165 | idx = idx + 1 166 | local val = vec:get(idx) 167 | if val then 168 | return idx, val 169 | end 170 | end 171 | 172 | --- Iterate through the vector, from beginning to end. 173 | -- 174 | -- Note that instead of returning `index, value` per iteration like normal 175 | -- `ipairs()`, the value of `index` is implementation-defined. 176 | -- @usage for _it, v in my_vector:ipairs() do print(v) end 177 | function Vector:ipairs() 178 | return iter, self, 0 179 | end 180 | mt.__ipairs = Vector.ipairs 181 | 182 | function Vector:unpack() 183 | local l = self:len() 184 | local function loop(i) 185 | if i >= l then 186 | return self:get(i) 187 | end 188 | return self:get(i), loop(i+1) 189 | end 190 | 191 | return loop(1) 192 | end 193 | 194 | local function newPath(level, node) 195 | if level == 0 then 196 | return node 197 | end 198 | local r = {} 199 | r[1] = newPath(level - BITS, node) 200 | return r 201 | end 202 | 203 | local function copy(owner, tbl) 204 | local set_dirty = false 205 | if owner._mutate then 206 | if tbl._mutate == owner._mutate then 207 | return tbl 208 | else 209 | set_dirty = true 210 | end 211 | end 212 | 213 | local t = {} 214 | local mt = getmetatable(tbl) 215 | for k, v in pairs(tbl) do 216 | t[k] = v 217 | end 218 | 219 | if set_dirty then 220 | t._mutate = owner._mutate 221 | end 222 | return setmetatable(t, mt) 223 | end 224 | 225 | local function pushTail(self, level, parent, tailNode) 226 | local subidx = mask(b.rshift(self.count - 1, level)) + 1 -- +1 227 | local r = copy(self, parent) 228 | 229 | local nodeToInsert 230 | if level == BITS then -- is parent leaf? 231 | nodeToInsert = tailNode 232 | elseif parent[subidx] then -- does tailNode map to an existing child? 233 | nodeToInsert = pushTail(self, level - BITS, parent[subidx], tailNode) 234 | else 235 | nodeToInsert = newPath(level - BITS, tailNode) 236 | end 237 | 238 | r[subidx] = nodeToInsert 239 | return r 240 | end 241 | 242 | --- Returns a new vector with val appended to the end. 243 | -- @complexity O(1) 244 | -- @param val the value to append 245 | -- @usage Vector.of(1, 2):conj(3) == Vector.of(1, 2, 3) 246 | function Vector:conj(val) 247 | local idx = self.count 248 | -- Is there room in the tail? 249 | if self.count - tailoff(self) < WIDTH then 250 | local newTail = copy(self, self.tail) 251 | table.insert(newTail, val) 252 | 253 | local r = copy(self, self) 254 | 255 | r.count = self.count + 1 256 | r.tail = newTail 257 | 258 | return r 259 | end 260 | 261 | local newRoot 262 | local tailNode = copy(self, self.tail) 263 | local newShift = self.shift 264 | 265 | -- will root overflow? 266 | if b.rshift(self.count, BITS) > b.lshift(1, self.shift) then 267 | newRoot = {} 268 | newRoot[1] = self.root 269 | newRoot[2] = newPath(self.shift, tailNode) 270 | newShift = self.shift + BITS 271 | else 272 | newRoot = pushTail(self, self.shift, self.root, tailNode) 273 | end 274 | 275 | local r = copy(self, self) 276 | 277 | r.count = self.count + 1 278 | r.shift = newShift 279 | r.root = newRoot 280 | r.tail = {val} 281 | 282 | return r 283 | end 284 | 285 | local function doAssoc(self, level, node, idx, val) 286 | local r = copy(self, node) 287 | if level == 0 then 288 | r[mask(idx) + 1] = val -- +1 289 | else 290 | local subidx = mask(b.rshift(idx, level)) + 1 -- +1 291 | r[subidx] = doAssoc(self, level - BITS, node[subidx], idx, val) 292 | end 293 | return r 294 | end 295 | 296 | --- Returns a new vector such that `vector:get(idx) == val` 297 | -- @complexity O(log32 n) 298 | -- @tparam int idx the index to set 299 | -- @param val the value to set 300 | -- @usage Vector.of(0, 0, 0):assoc(2, 'a') == Vector.of(0, 'a', 0) 301 | function Vector:assoc(idx, val) 302 | idx = idx - 1 -- -1 303 | if idx == self.count then 304 | return self:conj(va) 305 | end 306 | if not(idx >= 0 and idx < self.count) then 307 | error("Index out of bounds") 308 | end 309 | if idx >= tailoff(self) then 310 | local newTail = copy(self, self.tail) 311 | newTail[mask(idx) + 1] = val -- +1 312 | local r = copy(self, self) 313 | r.tail = newTail 314 | return r 315 | end 316 | local r = copy(self, self) 317 | r.root = doAssoc(self, self.shift, self.root, idx, val) 318 | 319 | return r 320 | end 321 | 322 | local function popTail(level, node) 323 | local subidx = mask(b.rshift(self.count - 2, level)) + 1 -- +1 324 | if level > BITS then 325 | local newChild = popTail(level - BITS, node[subidx]) 326 | if newChild == nil and subidx == 1 then 327 | return nil 328 | else 329 | local r = copy(self, node) 330 | r[subidx] = newChild 331 | return r 332 | end 333 | elseif subidx == 1 then 334 | return nil 335 | else 336 | local r = copy(self, node) 337 | r[subidx] = nil 338 | return r 339 | end 340 | end 341 | 342 | --- Returns a new vector with the last value removed. 343 | -- @complexity O(1) 344 | -- @usage Vector.of(1, 2, 3):pop() == Vector.of(1, 2) 345 | function Vector:pop() 346 | if self.count == 0 then 347 | return error("Can't pop from empty vector") 348 | elseif self.count == 1 then 349 | return EMPTY 350 | elseif self.count - tailoff(self) > 1 then 351 | local newTail = copy(self, self.tail) 352 | table.remove(newTail) 353 | local r = copy(self, self) 354 | r.count = self.count - 1 355 | r.tail = newTail 356 | 357 | return r 358 | end 359 | 360 | local newTail = arrayFor(self, self.count - 2) 361 | local newRoot = popTail(shift, root) 362 | local newShift = shift 363 | if newRoot == nil then 364 | newRoot = EMPTY_NODE 365 | elseif self.shift > BITS and newRoot[2] == nil then 366 | newRoot = newRoot[1] 367 | newShift = newShift - 1 368 | end 369 | 370 | local r = copy(self,self) 371 | r.count = self.count - 1 372 | r.shift = newShift 373 | r.root = newRoot 374 | r.tail = newTail 375 | return r 376 | end 377 | 378 | function Vector:asMutable() 379 | return copy({_mutate = {}}, self) 380 | end 381 | 382 | function Vector:asImmutable() 383 | self._mutate = nil 384 | end 385 | --- Returns the result of passing `fn()` a transient copy of the current 386 | -- vector. A transient vector behaves like a normal vector where old copies 387 | -- of the vector are put into an undefined state after modification. Use it 388 | -- to create cheap batch modifications. 389 | -- @tparam function fn the function that performs the mutation 390 | -- @return A persistent vector with fn applied 391 | function Vector:withMutations(fn) 392 | local mut = self:asMutable() 393 | local immut = fn(mut) 394 | if immut then 395 | immut:asImmutable() 396 | end 397 | return immut 398 | end 399 | 400 | return Vector 401 | -------------------------------------------------------------------------------- /ltrie/hashmap.lua: -------------------------------------------------------------------------------- 1 | --- An immutable hashmap, modelled after PersistentHashMap in Clojure. 2 | -- Hashmaps much like lua tables in that they map keys to values: 3 | -- 4 | -- local my_map = Hashmap.from { foo = 'bar', [2] = 'baz' } 5 | -- print(my_map:get('foo')) -- 'bar' 6 | -- 7 | -- But because they are persistent, modifications create a new Hashmap instead 8 | -- of changing the old one: 9 | -- 10 | -- local my_new_map = my_map:assoc('foo', 42) 11 | -- print(my_new_map:get('foo')) -- 42 12 | -- print(my_map:get('foo')) -- still 'bar' 13 | -- 14 | -- It is internally implemented as a Hash Array Mapped Trie (HAMT), which you 15 | -- can learn more about from the [Wikipedia article][1]. 16 | -- 17 | -- [1]: https://en.wikipedia.org/wiki/Hash_array_mapped_trie 18 | -- 19 | -- @module Hashmap 20 | -- 21 | 22 | -- Implementation Details: 23 | -- ======================= 24 | -- For my own sanity, I used as my model. 25 | -- To match Lua's index-by-one semantics I had to introduce an offset or two. 26 | -- Every line I've done that on is marked with a +1 or -1 comment. 27 | -- TODO 28 | -- * Take a closer look at immutable.js and its blamelog 29 | -- * Implement merges 30 | -- * store locals for PUC Lua improvements 31 | 32 | local function try(...) 33 | local ok, err = pcall(...) 34 | if not ok then return nil end 35 | return err 36 | end 37 | local fun = require 'ltrie.fun' 38 | local b = try(require, 'bit32') or try(require, 'bit') or error("No bitop lib found") 39 | local hashcode = require 'ltrie.hashcode'.hashcode 40 | 41 | local Hash = {} 42 | local mt = { __index = Hash } 43 | 44 | local function Hmap(data) 45 | assert(data.count) 46 | assert(data.root) 47 | return setmetatable(data, mt) 48 | end 49 | 50 | --- Creates a hashmap containing the key-value pairs provided by the given 51 | -- iterator. If keys are repeated then later pairs take precedence. 52 | -- @tparam iterator genparamstate the associative iterator. @see luafun. 53 | -- @usage Hash.from(pairs {a = 1, b = 2, c = 3}) 54 | function Hash.from(...) 55 | local r = Hash.EMPTY:asMutable() 56 | fun.each(function(k, v) 57 | r = r:assoc(k, v) 58 | end, ...) 59 | r:asImmutable() 60 | return r 61 | end 62 | 63 | --- Creates a hashmap containing the arguments. pairs are specified by 64 | -- including key, then value in groups of two. 65 | -- @param k,v,... the arguments 66 | -- @usage Hash.of('a', 1, 'b', 2, 'c', 3) 67 | function Hash.of(...) 68 | local r = Hash.EMPTY:asMutable() 69 | for i=1, select('#', ...), 2 do 70 | local k, v = select(i, ...) 71 | r = r:assoc(k, v) 72 | end 73 | r:asImmutable() 74 | return r 75 | end 76 | 77 | -- {{{ Nodes 78 | local function implements_node(o) 79 | assert(o.assoc) 80 | assert(o.without) 81 | assert(o.find) 82 | -- assert(o.iter) 83 | end 84 | 85 | local BITS = 5 86 | local WIDTH = 32 87 | local MASK = 31 88 | 89 | local function mask(hash, shift) 90 | return b.lshift(1, b.band(b.rshift(hash, shift), MASK)) 91 | end 92 | 93 | -- popcount for 32bit integers {{{ 94 | local m1 = 0x55555555 95 | local m2 = 0x33333333 96 | local m4 = 0x0f0f0f0f 97 | local h01 = 0x01010101 98 | local function popCount(x) 99 | x = x - b.band(b.rshift(x, 1), m1) 100 | x = b.band(x, m2) + b.band(b.rshift(x, 2), m2) 101 | x = b.band(x + b.rshift(x, 4), m4) 102 | x = x + b.rshift(x, 8) 103 | x = x + b.rshift(x, 16) 104 | return b.band(x, 0x7f) 105 | end 106 | -- }}} 107 | 108 | local function idxFor(bitmap, bit) 109 | local r = popCount(b.band(bitmap, bit - 1)) + 1 110 | return r 111 | end 112 | 113 | local Node = {} -- {{{ 114 | local Node_mt = { name = "Node.", __index = Node} 115 | 116 | local function NodeC(data) 117 | assert(data.bitmap, "no bitmap") 118 | assert(data.nodes, "no nodes") 119 | assert(data.shift, "no shift") 120 | return setmetatable(data, Node_mt) 121 | end 122 | 123 | 124 | function Node.create(shift, leaf, hash, key, val) 125 | return NodeC { 126 | bitmap = mask(leaf.hash, shift), 127 | nodes = {leaf}, 128 | shift = shift 129 | }:assoc (shift, hash, key, val) 130 | end 131 | 132 | local function copy(owner, tbl) 133 | assert(tbl ~= nil) 134 | local set_dirty = false 135 | if owner._mutate then 136 | if tbl._mutate == owner._mutate then 137 | return tbl 138 | else 139 | set_dirty = true 140 | end 141 | end 142 | 143 | local t = {} 144 | local mt = getmetatable(tbl) 145 | for k, v in pairs(tbl) do 146 | t[k] = v 147 | end 148 | 149 | if set_dirty then 150 | t._mutate = owner._mutate 151 | end 152 | return setmetatable(t, mt) 153 | end 154 | 155 | local LeafC 156 | function Node:assoc(shift, hash, key, val) 157 | local bit = mask(hash, shift) 158 | local idx = idxFor(self.bitmap, bit) 159 | if b.band(self.bitmap, bit) ~= 0 then -- collision 160 | local n, upd = self.nodes[idx]:assoc(shift + BITS, hash, key, val) 161 | if n == self.nodes[idx] then 162 | return self, upd 163 | else 164 | local newNodes = copy(self, self.nodes) 165 | newNodes[idx] = n 166 | local r = copy(self, self) 167 | 168 | r.bitmap = self.bitmap 169 | r.nodes = newNodes 170 | r.shift = shift 171 | 172 | return r, upd 173 | end 174 | else 175 | local newNodes = copy(self, self.nodes) 176 | -- Shift forward old nodes 177 | for i=#self.nodes, idx, -1 do 178 | newNodes[i+1] = newNodes[i] 179 | end 180 | newNodes[idx] = LeafC(hash, key, val) 181 | local r = copy(self, self) 182 | r.bitmap = b.bor(self.bitmap, bit) 183 | r.nodes = newNodes 184 | r.shift = shift 185 | 186 | return r 187 | end 188 | end 189 | 190 | function Node:without(hash, key) 191 | 192 | local bit = mask(hash, self.shift) 193 | if b.band(self.bitmap, bit) == 0 then 194 | return self 195 | end 196 | 197 | local idx = idxFor(self.bitmap, bit) 198 | local N = self.nodes[idx] 199 | local n = N and N:without(hash, key) 200 | 201 | if n == N then 202 | return self 203 | end 204 | 205 | if n == nil then 206 | if self.bitmap == bit then 207 | return nil 208 | end 209 | local newNodes = copy(self, self.nodes) 210 | for i=idx, #self.nodes do 211 | newNodes[i] = newNodes[i+1] 212 | end 213 | local r = copy(self, self) 214 | 215 | r.bitmap = b.band(self.bitmap, b.bnot(bit)) 216 | r.nodes = newNodes 217 | 218 | return r 219 | end 220 | 221 | local newNodes = copy(self, self.nodes) 222 | newNodes[idx] = n 223 | local r = copy(self, self) 224 | r.nodes = newNodes 225 | return r 226 | end 227 | 228 | function Node:find(hash, key) 229 | local bit = mask(hash, self.shift) 230 | if b.band(self.bitmap, bit) ~= 0 then 231 | local idx = idxFor(self.bitmap, bit) 232 | local node = self.nodes[idx] 233 | return node:find(hash, key) 234 | end 235 | return nil 236 | end 237 | 238 | local function node_iter(self, state) 239 | if not state.state then 240 | local leaf 241 | state.it, leaf = next(self.nodes, state.it) 242 | 243 | if leaf == nil then return nil end 244 | state.gen, state.param, state.state = leaf:iter() 245 | end 246 | 247 | local inner_state, k, v = state.gen(state.param, state.state) 248 | state.state = inner_state 249 | if inner_state == nil then 250 | return node_iter(self, state) 251 | end 252 | 253 | return state, k, v 254 | end 255 | 256 | function Node:iter() 257 | return node_iter, self, {it = nil} 258 | end 259 | 260 | implements_node(Node) -- }}} 261 | 262 | local Leaf = {} -- {{{ 263 | local Leaf_mt = { name="K/V leaf", __index = Leaf} 264 | function LeafC(hash, key, val) 265 | return setmetatable({hash = hash, key = key, val = val}, Leaf_mt) 266 | end 267 | 268 | local CLeafC 269 | function Leaf:assoc(shift, hash, key, val) 270 | if hash == self.hash then 271 | if key == self.key then 272 | if val == self.val then 273 | return self 274 | else 275 | return LeafC(hash, key, val), true 276 | end 277 | end 278 | return CLeafC(hash, {self, LeafC(hash, key, val)}) 279 | end 280 | return Node.create(shift, self, hash, key, val) 281 | end 282 | 283 | function Leaf:without(hash, key) 284 | if hash == self.hash and key == self.key then 285 | return nil 286 | end 287 | return self 288 | end 289 | 290 | function Leaf:find(hash, key) 291 | if hash == self.hash and key == self.key then 292 | return self 293 | end 294 | return nil 295 | end 296 | 297 | local function leaf_it(self, state) 298 | if state then 299 | return false, self.key, self.val 300 | end 301 | return nil 302 | end 303 | 304 | function Leaf:iter() 305 | return leaf_it, self, true 306 | end 307 | 308 | implements_node(Leaf) -- }}} 309 | 310 | local CLeaf = {} -- {{{ 311 | local CLeaf_mt = { name="Collision leaf", __index = CLeaf } 312 | 313 | function CLeafC(hash, leaves) 314 | return setmetatable({hash = hash, leaves = leaves}, CLeaf_mt) 315 | end 316 | 317 | function CLeaf:assoc(shift, hash, key, val) 318 | if hash == self.hash then 319 | local idx = findIdx(self.leaves, hash, key) 320 | if idx ~= -1 then 321 | return self:assoc(shift, hash, key, val) 322 | end 323 | local newLeaves = copy(self, self.leaves) 324 | table.insert(newLeaves, LeafC(hash, key, val)) 325 | return CLeafC(hash, newLeaves) 326 | end 327 | return Node.create(shift, self, hash, key, val) 328 | end 329 | 330 | function CLeaf:without(hash, key) 331 | local idx = findIdx(self.leaves, hash, key) 332 | if idx == -1 then 333 | return self 334 | end 335 | 336 | local len = #self.leaves 337 | if len == 2 then 338 | if idx == 1 then 339 | return self.leaves[2] 340 | else 341 | return self.leaves[1] 342 | end 343 | end 344 | 345 | local newLeaves = copy(self, self.leaves) 346 | for i=idx, len do 347 | newLeaves[i] = newLeaves[i + 1] 348 | end 349 | 350 | return CLeafC(hash, newLeaves) 351 | end 352 | 353 | function CLeaf:find(hash, key) 354 | local idx = findIdx(self.leaves, hash, key) 355 | if idx == -1 then 356 | return nil 357 | end 358 | return self.leaves[idx] 359 | end 360 | 361 | local function nested_iter(self, state) 362 | if not state.state then 363 | state.i = state.i + 1 364 | local leaf = self.leaves[state.i] 365 | if leaf == nil then return nil end 366 | state.gen, state.param, state.state = leaf:iter() 367 | end 368 | 369 | local inner_state, k, v = state.gen(state.param, state.state) 370 | state.state = inner_state 371 | if inner_state == nil then 372 | return nested_iter(self, state) 373 | end 374 | 375 | return state, k, v 376 | end 377 | 378 | function CLeaf:iter() 379 | return nested_iter, self, {i = 0} 380 | end 381 | 382 | function findIdx(leaves, hash, key) 383 | for i, v in ipairs(leaves) do 384 | if v:find(hash, key) ~= nil then 385 | return i 386 | end 387 | end 388 | return -1 389 | end 390 | 391 | implements_node(CLeaf) -- }}} 392 | 393 | -- }}} 394 | 395 | --- @type Hash 396 | 397 | --- Returns the number of key-value pairs contained in the hashmap. 398 | -- @complexity O(1) 399 | -- @usage Hash.from({a = 1, b = 2}):len() == 2 400 | function Hash:len() 401 | return self.count 402 | end 403 | mt.__len = Hash.len 404 | 405 | --- Returns a new hashmap such that `hmap:get(key) == val` 406 | -- @param key the key to set 407 | -- @param val the value to set 408 | -- @usage Vector.from({a = 1, b = 2}):assoc('a', 3):get('a') == 3 409 | function Hash:assoc(key, val) 410 | local newRoot, isUpdate = self.root:assoc(0, hashcode(key), key, val) 411 | if newRoot == self.root then 412 | return self 413 | end 414 | local newCount = self.count + (isUpdate and 0 or 1) 415 | local r = Hmap {count = newCount, root = newRoot} 416 | assert(r:get(key) == val) 417 | return r 418 | end 419 | 420 | --- Gets a value by key. Keys behave as they do in lua tables. 421 | -- @param key the key 422 | -- @return the value, or `nil` if not found 423 | -- @usage Hash.from({a = 1, b = 2}):get('a') == 1 424 | function Hash:get(key) 425 | local entry = self.root:find(hashcode(key), key) 426 | return entry and entry.val 427 | end 428 | 429 | --- Returns a new hashmap without the value associated with the key. 430 | -- @param key the key to dissoc 431 | -- @usage Hash.from({a = 1, b = 2}):dissoc('a'):get('a') == nil 432 | function Hash:dissoc(key) 433 | local hc = hashcode(key) 434 | local newRoot = self.root:without(hc, key) 435 | if newRoot == self.root then 436 | return self 437 | elseif newRoot == nil then 438 | return Hash.EMPTY 439 | end 440 | return Hmap {count = self.count - 1, root = newRoot} 441 | end 442 | 443 | --- Iterate through the hashmap, returning iter-key-value values. Both the 444 | -- value of iter, and the order of iterations are implementation-defined. 445 | -- @usage for _it, k, v in myhash:iter() do print(k, v) end 446 | function Hash:iter() 447 | return self.root:iter() 448 | end 449 | mt.__ipairs = Hash.iter 450 | 451 | --- Iterate through the hashmap, returning key-value pairs. This iterator 452 | -- hides the iteration table, but is more ergonomic to use with the naive 453 | -- for-loop. 454 | -- @usage for k, v in myhash:pairs() do print(k, v) end 455 | function Hash:pairs() 456 | local gen, param, state = self.root:iter() 457 | local function iter() 458 | -- since we use the same mutable state table we can ignore _it 459 | local _it, k, v = gen(param, state) 460 | return k, v 461 | end 462 | 463 | return iter, param, state 464 | end 465 | mt.__pairs = Hash.pairs 466 | 467 | --- Constructs a read-only view of the current Hashmap, supporting the 468 | -- tradititional table interface using metatables. Use sparingly, this function 469 | -- produces 2 tables and 3 closures per-call. 470 | -- @usage print(Hash.from({foo = "bar"}):asTable().foo) -- "bar" 471 | function Hash:asTable() 472 | return setmetatable({}, { 473 | __newindex = function() error("Read-only table") end, 474 | __index = function(_, k) return self:get(k) end, 475 | __pairs = function() return self:pairs() end, 476 | }) 477 | end 478 | 479 | function Hash:asMutable() 480 | return copy({_mutate = {}}, self) 481 | end 482 | 483 | function Hash:asImmutable() 484 | self._mutate = nil 485 | end 486 | 487 | --- Returns the result of passing `fn()` a transient copy of the current 488 | -- hashmap. A transient hashmap behaves like a normal hashmap where old copies 489 | -- of the hashmap are put into an undefined state after modification. Use it 490 | -- to create cheap batch modifications. 491 | -- @tparam function fn the function that performs the mutation 492 | -- @return A persistent hashmap with fn applied 493 | function Hash:withMutations(fn) 494 | local mut = self:asMutable() 495 | local immut = fn(mut) 496 | if immut then 497 | immut:asImmutable() 498 | end 499 | return immut 500 | end 501 | 502 | Hash.EMPTY = Hmap {count = 0, root = { 503 | assoc = function(self, shift, hash, key, val) 504 | return LeafC(hash, key, val) 505 | end, 506 | without = function(self, hash, key) 507 | return self 508 | end, 509 | find = function(...) 510 | return nil 511 | end, 512 | iter = function(...) 513 | return nil, nil, nil 514 | end 515 | } 516 | } 517 | 518 | return Hash 519 | -------------------------------------------------------------------------------- /ltrie/fun.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Lua Fun - a high-performance functional programming library for LuaJIT 3 | -- 4 | ------------------------------------------------------------------------------- 5 | -- 6 | -- **Lua Fun** source code, logo and documentation are distributed under the 7 | -- **[MIT License]** - same as Lua and LuaJIT. 8 | -- 9 | -- Copyright (c) 2013-2014 Roman Tsisyk 10 | -- 11 | -- Permission is hereby granted, free of charge, to any person obtaining a 12 | -- copy of this software and associated documentation files (the "Software"), 13 | -- to deal in the Software without restriction, including without limitation 14 | -- the rights to use, copy, modify, merge, publish, distribute, sublicense, 15 | -- and/or sell copies of the Software, and to permit persons to whom the 16 | -- Software is furnished to do so, subject to the following conditions: 17 | -- 18 | -- The above copyright notice and this permission notice shall be included in 19 | -- all copies or substantial portions of the Software. 20 | -- 21 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | -- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | -- DEALINGS IN THE SOFTWARE. 28 | -- 29 | -- [MIT License]: http://www.opensource.org/licenses/mit-license.php 30 | -- 31 | ------------------------------------------------------------------------------- 32 | -- 33 | -- Modified for ltrie. Changes from upstream luafun are: 34 | -- * iterators implement `__len()` 35 | -- * `fun.numargs(iter)` is exported for external use 36 | -- * `iter:find(pred)` is like `index()`, but uses a predicate function. 37 | -- * `fun.some()` is now an alias for `fun.find()` instead of `fun.any()`. 38 | -- * `iter:get()` is an alias for `nth()`. method-only. 39 | -- * `fun.xunpack(iter)` functions like `unpack(table)`. 40 | -- * `iter:unpack()` is an alias for `xunpack()`. method-only. 41 | -- * `iter:decons()` returns `iter:head(), iter:tail()`. convenience function. 42 | -- * name export mistakes, see GH rtsisyk/luafun#12 43 | 44 | local exports = {} 45 | local methods = {} 46 | 47 | -------------------------------------------------------------------------------- 48 | -- Tools 49 | -------------------------------------------------------------------------------- 50 | 51 | local return_if_not_empty = function(state_x, ...) 52 | if state_x == nil then 53 | return nil 54 | end 55 | return ... 56 | end 57 | 58 | local call_if_not_empty = function(fun, state_x, ...) 59 | if state_x == nil then 60 | return nil 61 | end 62 | return state_x, fun(...) 63 | end 64 | 65 | local function deepcopy(orig) -- used by cycle() 66 | local orig_type = type(orig) 67 | local copy 68 | if orig_type == 'table' then 69 | copy = {} 70 | for orig_key, orig_value in next, orig, nil do 71 | copy[deepcopy(orig_key)] = deepcopy(orig_value) 72 | end 73 | else 74 | copy = orig 75 | end 76 | return copy 77 | end 78 | 79 | local iterator_mt = { 80 | -- usually called by for-in loop 81 | __call = function(self, param, state) 82 | return self.gen(param, state) 83 | end; 84 | __tostring = function(self) 85 | return '' 86 | end; 87 | -- add all exported methods 88 | __index = methods; 89 | } 90 | 91 | local wrap = function(gen, param, state) 92 | return setmetatable({ 93 | gen = gen, 94 | param = param, 95 | state = state 96 | }, iterator_mt), param, state 97 | end 98 | exports.wrap = wrap 99 | 100 | local unwrap = function(self) 101 | return self.gen, self.param, self.state 102 | end 103 | methods.unwrap = unwrap 104 | 105 | -------------------------------------------------------------------------------- 106 | -- Basic Functions 107 | -------------------------------------------------------------------------------- 108 | 109 | local nil_gen = function(_param, _state) 110 | return nil 111 | end 112 | 113 | local string_gen = function(param, state) 114 | state = state + 1 115 | if state > #param then 116 | return nil 117 | end 118 | local r = string.sub(param, state, state) 119 | return state, r 120 | end 121 | 122 | local pairs_gen = pairs({ a = 0 }) -- get the generating function from pairs 123 | local map_gen = function(tab, key) 124 | local value 125 | key, value = pairs_gen(tab, key) 126 | return key, key, value 127 | end 128 | 129 | local rawiter = function(obj, param, state) 130 | assert(obj ~= nil, "invalid iterator") 131 | if type(obj) == "table" then 132 | local mt = getmetatable(obj); 133 | if mt ~= nil then 134 | if mt == iterator_mt then 135 | return obj.gen, obj.param, obj.state 136 | elseif mt.__ipairs ~= nil then 137 | return mt.__ipairs(obj) 138 | elseif mt.__pairs ~= nil then 139 | return mt.__pairs(obj) 140 | end 141 | end 142 | if #obj > 0 then 143 | -- array 144 | return ipairs(obj) 145 | else 146 | -- hash 147 | return map_gen, obj, nil 148 | end 149 | elseif (type(obj) == "function") then 150 | return obj, param, state 151 | elseif (type(obj) == "string") then 152 | if #obj == 0 then 153 | return nil_gen, nil, nil 154 | end 155 | return string_gen, obj, 0 156 | end 157 | error(string.format('object %s of type "%s" is not iterable', 158 | obj, type(obj))) 159 | end 160 | 161 | local iter = function(obj, param, state) 162 | return wrap(rawiter(obj, param, state)) 163 | end 164 | exports.iter = iter 165 | 166 | local method0 = function(fun) 167 | return function(self) 168 | return fun(self.gen, self.param, self.state) 169 | end 170 | end 171 | 172 | local method1 = function(fun) 173 | return function(self, arg1) 174 | return fun(arg1, self.gen, self.param, self.state) 175 | end 176 | end 177 | 178 | local method2 = function(fun) 179 | return function(self, arg1, arg2) 180 | return fun(arg1, arg2, self.gen, self.param, self.state) 181 | end 182 | end 183 | 184 | local export0 = function(fun) 185 | return function(gen, param, state) 186 | return fun(rawiter(gen, param, state)) 187 | end 188 | end 189 | 190 | local export1 = function(fun) 191 | return function(arg1, gen, param, state) 192 | return fun(arg1, rawiter(gen, param, state)) 193 | end 194 | end 195 | 196 | local export2 = function(fun) 197 | return function(arg1, arg2, gen, param, state) 198 | return fun(arg1, arg2, rawiter(gen, param, state)) 199 | end 200 | end 201 | 202 | local each = function(fun, gen, param, state) 203 | repeat 204 | state = call_if_not_empty(fun, gen(param, state)) 205 | until state == nil 206 | end 207 | methods.each = method1(each) 208 | exports.each = export1(each) 209 | methods.for_each = methods.each 210 | exports.for_each = exports.each 211 | methods.foreach = methods.each 212 | exports.foreach = exports.each 213 | 214 | -------------------------------------------------------------------------------- 215 | -- Generators 216 | -------------------------------------------------------------------------------- 217 | 218 | local range_gen = function(param, state) 219 | local stop, step = param[1], param[2] 220 | state = state + step 221 | if state > stop then 222 | return nil 223 | end 224 | return state, state 225 | end 226 | 227 | local range_rev_gen = function(param, state) 228 | local stop, step = param[1], param[2] 229 | state = state + step 230 | if state < stop then 231 | return nil 232 | end 233 | return state, state 234 | end 235 | 236 | local range = function(start, stop, step) 237 | if step == nil then 238 | if stop == nil then 239 | if start == 0 then 240 | return nil_gen, nil, nil 241 | end 242 | stop = start 243 | start = stop > 0 and 1 or -1 244 | end 245 | step = start <= stop and 1 or -1 246 | end 247 | 248 | assert(type(start) == "number", "start must be a number") 249 | assert(type(stop) == "number", "stop must be a number") 250 | assert(type(step) == "number", "step must be a number") 251 | assert(step ~= 0, "step must not be zero") 252 | 253 | if (step > 0) then 254 | return wrap(range_gen, {stop, step}, start - step) 255 | elseif (step < 0) then 256 | return wrap(range_rev_gen, {stop, step}, start - step) 257 | end 258 | end 259 | exports.range = range 260 | 261 | local natural_gen = function(param, state) 262 | return state + 1, state + 1 263 | end 264 | 265 | local naturals = function() 266 | return wrap(natural_gen, 0, 0) 267 | end 268 | 269 | 270 | exports.naturals = naturals 271 | 272 | local duplicate_table_gen = function(param_x, state_x) 273 | return state_x + 1, unpack(param_x) 274 | end 275 | 276 | local duplicate_fun_gen = function(param_x, state_x) 277 | return state_x + 1, param_x(state_x) 278 | end 279 | 280 | local duplicate_gen = function(param_x, state_x) 281 | return state_x + 1, param_x 282 | end 283 | 284 | local duplicate = function(...) 285 | if select('#', ...) <= 1 then 286 | return wrap(duplicate_gen, select(1, ...), 0) 287 | else 288 | return wrap(duplicate_table_gen, {...}, 0) 289 | end 290 | end 291 | exports.duplicate = duplicate 292 | exports.replicate = duplicate 293 | exports.xrepeat = duplicate 294 | 295 | local tabulate = function(fun) 296 | assert(type(fun) == "function") 297 | return wrap(duplicate_fun_gen, fun, 0) 298 | end 299 | exports.tabulate = tabulate 300 | 301 | local zeros = function() 302 | return wrap(duplicate_gen, 0, 0) 303 | end 304 | exports.zeros = zeros 305 | 306 | local ones = function() 307 | return wrap(duplicate_gen, 1, 0) 308 | end 309 | exports.ones = ones 310 | 311 | local rands_gen = function(param_x, _state_x) 312 | return 0, math.random(param_x[1], param_x[2]) 313 | end 314 | 315 | local rands_nil_gen = function(_param_x, _state_x) 316 | return 0, math.random() 317 | end 318 | 319 | local rands = function(n, m) 320 | if n == nil and m == nil then 321 | return wrap(rands_nil_gen, 0, 0) 322 | end 323 | assert(type(n) == "number", "invalid first arg to rands") 324 | if m == nil then 325 | m = n 326 | n = 0 327 | else 328 | assert(type(m) == "number", "invalid second arg to rands") 329 | end 330 | assert(n < m, "empty interval") 331 | return wrap(rands_gen, {n, m - 1}, 0) 332 | end 333 | exports.rands = rands 334 | 335 | -------------------------------------------------------------------------------- 336 | -- Slicing 337 | -------------------------------------------------------------------------------- 338 | 339 | local nth = function(n, gen_x, param_x, state_x) 340 | assert(n > 0, "invalid first argument to nth") 341 | -- An optimization for arrays and strings 342 | if gen_x == ipairs then 343 | return param_x[n] 344 | elseif gen_x == string_gen then 345 | if n < #param_x then 346 | return string.sub(param_x, n, n) 347 | else 348 | return nil 349 | end 350 | end 351 | for i=1,n-1,1 do 352 | state_x = gen_x(param_x, state_x) 353 | if state_x == nil then 354 | return nil 355 | end 356 | end 357 | return return_if_not_empty(gen_x(param_x, state_x)) 358 | end 359 | methods.nth = method1(nth) 360 | methods.get = methods.nth 361 | exports.nth = export1(nth) 362 | 363 | local head_call = function(state, ...) 364 | if state == nil then 365 | error("head: iterator is empty") 366 | end 367 | return ... 368 | end 369 | 370 | local head = function(gen, param, state) 371 | return head_call(gen(param, state)) 372 | end 373 | methods.head = method0(head) 374 | exports.head = export0(head) 375 | exports.car = exports.head 376 | methods.car = methods.head 377 | 378 | local tail = function(gen, param, state) 379 | state = gen(param, state) 380 | if state == nil then 381 | return wrap(nil_gen, nil, nil) 382 | end 383 | return wrap(gen, param, state) 384 | end 385 | methods.tail = method0(tail) 386 | exports.tail = export0(tail) 387 | exports.cdr = exports.tail 388 | methods.cdr = methods.tail 389 | 390 | local decons = function(gen, param, state) 391 | local new_state, val = gen(param, state) 392 | if new_state == nil then 393 | return val, wrap(nil_gen, nil, nil) 394 | end 395 | return val, wrap(gen, param, new_state) 396 | end 397 | methods.decons = method0(decons) 398 | exports.decons = export0(decons) 399 | 400 | local take_n_gen_x = function(i, state_x, ...) 401 | if state_x == nil then 402 | return nil 403 | end 404 | return {i, state_x}, ... 405 | end 406 | 407 | local take_n_gen = function(param, state) 408 | local n, gen_x, param_x = param[1], param[2], param[3] 409 | local i, state_x = state[1], state[2] 410 | if i >= n then 411 | return nil 412 | end 413 | return take_n_gen_x(i + 1, gen_x(param_x, state_x)) 414 | end 415 | 416 | local take_n = function(n, gen, param, state) 417 | assert(n >= 0, "invalid first argument to take_n") 418 | return wrap(take_n_gen, {n, gen, param}, {0, state}) 419 | end 420 | methods.take_n = method1(take_n) 421 | exports.take_n = export1(take_n) 422 | 423 | local take_while_gen_x = function(fun, state_x, ...) 424 | if state_x == nil or not fun(...) then 425 | return nil 426 | end 427 | return state_x, ... 428 | end 429 | 430 | local take_while_gen = function(param, state_x) 431 | local fun, gen_x, param_x = param[1], param[2], param[3] 432 | return take_while_gen_x(fun, gen_x(param_x, state_x)) 433 | end 434 | 435 | local take_while = function(fun, gen, param, state) 436 | assert(type(fun) == "function", "invalid first argument to take_while") 437 | return wrap(take_while_gen, {fun, gen, param}, state) 438 | end 439 | methods.take_while = method1(take_while) 440 | exports.take_while = export1(take_while) 441 | 442 | local take = function(n_or_fun, gen, param, state) 443 | if type(n_or_fun) == "number" then 444 | return take_n(n_or_fun, gen, param, state) 445 | else 446 | return take_while(n_or_fun, gen, param, state) 447 | end 448 | end 449 | methods.take = method1(take) 450 | exports.take = export1(take) 451 | 452 | local function xunpack(gen, param, state) 453 | local next_state, val = gen(param, state) 454 | if next_state == nil then 455 | return -- none does not add to varargs 456 | end 457 | return val, xunpack(gen, param, next_state) 458 | end 459 | methods.xunpack = method0(xunpack) 460 | methods.unpack = methods.xunpack 461 | exports.xunpack = export0(xunpack) 462 | 463 | local drop_n = function(n, gen, param, state) 464 | assert(n >= 0, "invalid first argument to drop_n") 465 | local i 466 | for i=1,n,1 do 467 | state = gen(param, state) 468 | if state == nil then 469 | return wrap(nil_gen, nil, nil) 470 | end 471 | end 472 | return wrap(gen, param, state) 473 | end 474 | methods.drop_n = method1(drop_n) 475 | exports.drop_n = export1(drop_n) 476 | 477 | local drop_while_x = function(fun, state_x, ...) 478 | if state_x == nil or not fun(...) then 479 | return state_x, false 480 | end 481 | return state_x, true, ... 482 | end 483 | 484 | local drop_while = function(fun, gen_x, param_x, state_x) 485 | assert(type(fun) == "function", "invalid first argument to drop_while") 486 | local cont, state_x_prev 487 | repeat 488 | state_x_prev = deepcopy(state_x) 489 | state_x, cont = drop_while_x(fun, gen_x(param_x, state_x)) 490 | until not cont 491 | if state_x == nil then 492 | return wrap(nil_gen, nil, nil) 493 | end 494 | return wrap(gen_x, param_x, state_x_prev) 495 | end 496 | methods.drop_while = method1(drop_while) 497 | exports.drop_while = export1(drop_while) 498 | 499 | local drop = function(n_or_fun, gen_x, param_x, state_x) 500 | if type(n_or_fun) == "number" then 501 | return drop_n(n_or_fun, gen_x, param_x, state_x) 502 | else 503 | return drop_while(n_or_fun, gen_x, param_x, state_x) 504 | end 505 | end 506 | methods.drop = method1(drop) 507 | exports.drop = export1(drop) 508 | 509 | local split = function(n_or_fun, gen_x, param_x, state_x) 510 | return take(n_or_fun, gen_x, param_x, state_x), 511 | drop(n_or_fun, gen_x, param_x, state_x) 512 | end 513 | methods.split = method1(split) 514 | exports.split = export1(split) 515 | methods.split_at = methods.split 516 | exports.split_at = exports.split 517 | methods.span = methods.split 518 | exports.span = exports.split 519 | 520 | -------------------------------------------------------------------------------- 521 | -- Indexing 522 | -------------------------------------------------------------------------------- 523 | 524 | local find = function(pred, gen, param, state) 525 | for _k, v in gen, param, state do 526 | local r = pred(v) 527 | if r then 528 | return r 529 | end 530 | end 531 | return nil 532 | end 533 | methods.find = method1(find) 534 | exports.find = export1(find) 535 | methods.some = methods.find 536 | exports.some = exports.find 537 | 538 | local index = function(x, gen, param, state) 539 | local i = 1 540 | for _k, r in gen, param, state do 541 | if r == x then 542 | return i 543 | end 544 | i = i + 1 545 | end 546 | return nil 547 | end 548 | methods.index = method1(index) 549 | exports.index = export1(index) 550 | methods.index_of = methods.index 551 | exports.index_of = exports.index 552 | methods.elem_index = methods.index 553 | exports.elem_index = exports.index 554 | 555 | local indexes_gen = function(param, state) 556 | local x, gen_x, param_x = param[1], param[2], param[3] 557 | local i, state_x = state[1], state[2] 558 | local r 559 | while true do 560 | state_x, r = gen_x(param_x, state_x) 561 | if state_x == nil then 562 | return nil 563 | end 564 | i = i + 1 565 | if r == x then 566 | return {i, state_x}, i 567 | end 568 | end 569 | end 570 | 571 | local indexes = function(x, gen, param, state) 572 | return wrap(indexes_gen, {x, gen, param}, {0, state}) 573 | end 574 | methods.indexes = method1(indexes) 575 | exports.indexes = export1(indexes) 576 | methods.elem_indexes = methods.indexes 577 | exports.elem_indexes = exports.indexes 578 | methods.indices = methods.indexes 579 | exports.indices = exports.indexes 580 | methods.elem_indices = methods.indexes 581 | exports.elem_indices = exports.indexes 582 | 583 | -------------------------------------------------------------------------------- 584 | -- Filtering 585 | -------------------------------------------------------------------------------- 586 | 587 | local filter1_gen = function(fun, gen_x, param_x, state_x, a) 588 | while true do 589 | if state_x == nil or fun(a) then break; end 590 | state_x, a = gen_x(param_x, state_x) 591 | end 592 | return state_x, a 593 | end 594 | 595 | -- call each other 596 | local filterm_gen 597 | local filterm_gen_shrink = function(fun, gen_x, param_x, state_x) 598 | return filterm_gen(fun, gen_x, param_x, gen_x(param_x, state_x)) 599 | end 600 | 601 | filterm_gen = function(fun, gen_x, param_x, state_x, ...) 602 | if state_x == nil then 603 | return nil 604 | end 605 | if fun(...) then 606 | return state_x, ... 607 | end 608 | return filterm_gen_shrink(fun, gen_x, param_x, state_x) 609 | end 610 | 611 | local filter_detect = function(fun, gen_x, param_x, state_x, ...) 612 | if select('#', ...) < 2 then 613 | return filter1_gen(fun, gen_x, param_x, state_x, ...) 614 | else 615 | return filterm_gen(fun, gen_x, param_x, state_x, ...) 616 | end 617 | end 618 | 619 | local filter_gen = function(param, state_x) 620 | local fun, gen_x, param_x = param[1], param[2], param[3] 621 | return filter_detect(fun, gen_x, param_x, gen_x(param_x, state_x)) 622 | end 623 | 624 | local filter = function(fun, gen, param, state) 625 | return wrap(filter_gen, {fun, gen, param}, state) 626 | end 627 | methods.filter = method1(filter) 628 | exports.filter = export1(filter) 629 | methods.remove_if = methods.filter 630 | exports.remove_if = exports.filter 631 | 632 | local grep = function(fun_or_regexp, gen, param, state) 633 | local fun = fun_or_regexp 634 | if type(fun_or_regexp) == "string" then 635 | fun = function(x) return string.find(x, fun_or_regexp) ~= nil end 636 | end 637 | return filter(fun, gen, param, state) 638 | end 639 | methods.grep = method1(grep) 640 | exports.grep = export1(grep) 641 | 642 | local partition = function(fun, gen, param, state) 643 | local neg_fun = function(...) 644 | return not fun(...) 645 | end 646 | return filter(fun, gen, param, state), 647 | filter(neg_fun, gen, param, state) 648 | end 649 | methods.partition = method1(partition) 650 | exports.partition = export1(partition) 651 | 652 | -------------------------------------------------------------------------------- 653 | -- Reducing 654 | -------------------------------------------------------------------------------- 655 | 656 | local foldl_call = function(fun, start, state, ...) 657 | if state == nil then 658 | return nil, start 659 | end 660 | return state, fun(start, ...) 661 | end 662 | 663 | local foldl = function(fun, start, gen_x, param_x, state_x) 664 | while true do 665 | state_x, start = foldl_call(fun, start, gen_x(param_x, state_x)) 666 | if state_x == nil then 667 | break; 668 | end 669 | end 670 | return start 671 | end 672 | methods.foldl = method2(foldl) 673 | exports.foldl = export2(foldl) 674 | methods.reduce = methods.foldl 675 | exports.reduce = exports.foldl 676 | 677 | local length = function(gen, param, state) 678 | if gen == ipairs or gen == string_gen then 679 | return #param 680 | end 681 | local len = 0 682 | repeat 683 | state = gen(param, state) 684 | len = len + 1 685 | until state == nil 686 | return len - 1 687 | end 688 | methods.length = method0(length) 689 | exports.length = export0(length) 690 | iterator_mt.__len = methods.length 691 | 692 | local is_null = function(gen, param, state) 693 | return gen(param, deepcopy(state)) == nil 694 | end 695 | methods.is_null = method0(is_null) 696 | exports.is_null = export0(is_null) 697 | 698 | local is_prefix_of = function(iter_x, iter_y) 699 | local gen_x, param_x, state_x = iter(iter_x) 700 | local gen_y, param_y, state_y = iter(iter_y) 701 | 702 | local r_x, r_y 703 | for i=1,10,1 do 704 | state_x, r_x = gen_x(param_x, state_x) 705 | state_y, r_y = gen_y(param_y, state_y) 706 | if state_x == nil then 707 | return true 708 | end 709 | if state_y == nil or r_x ~= r_y then 710 | return false 711 | end 712 | end 713 | end 714 | methods.is_prefix_of = is_prefix_of 715 | exports.is_prefix_of = is_prefix_of 716 | 717 | local all = function(fun, gen_x, param_x, state_x) 718 | local r 719 | repeat 720 | state_x, r = call_if_not_empty(fun, gen_x(param_x, state_x)) 721 | until state_x == nil or not r 722 | return state_x == nil 723 | end 724 | methods.all = method1(all) 725 | exports.all = export1(all) 726 | methods.every = methods.all 727 | exports.every = exports.all 728 | 729 | local any = function(fun, gen_x, param_x, state_x) 730 | local r 731 | repeat 732 | state_x, r = call_if_not_empty(fun, gen_x(param_x, state_x)) 733 | until state_x == nil or r 734 | return not not r 735 | end 736 | methods.any = method1(any) 737 | exports.any = export1(any) 738 | 739 | local sum = function(gen, param, state) 740 | local s = 0 741 | local r = 0 742 | repeat 743 | s = s + r 744 | state, r = gen(param, state) 745 | until state == nil 746 | return s 747 | end 748 | methods.sum = method0(sum) 749 | exports.sum = export0(sum) 750 | 751 | local product = function(gen, param, state) 752 | local p = 1 753 | local r = 1 754 | repeat 755 | p = p * r 756 | state, r = gen(param, state) 757 | until state == nil 758 | return p 759 | end 760 | methods.product = method0(product) 761 | exports.product = export0(product) 762 | 763 | local min_cmp = function(m, n) 764 | if n < m then return n else return m end 765 | end 766 | 767 | local max_cmp = function(m, n) 768 | if n > m then return n else return m end 769 | end 770 | 771 | local min = function(gen, param, state) 772 | local state, m = gen(param, state) 773 | if state == nil then 774 | error("min: iterator is empty") 775 | end 776 | 777 | local cmp 778 | if type(m) == "number" then 779 | -- An optimization: use math.min for numbers 780 | cmp = math.min 781 | else 782 | cmp = min_cmp 783 | end 784 | 785 | for _, r in gen, param, state do 786 | m = cmp(m, r) 787 | end 788 | return m 789 | end 790 | methods.min = method0(min) 791 | exports.min = export0(min) 792 | methods.minimum = methods.min 793 | exports.minimum = exports.min 794 | 795 | local min_by = function(cmp, gen_x, param_x, state_x) 796 | local state_x, m = gen_x(param_x, state_x) 797 | if state_x == nil then 798 | error("min: iterator is empty") 799 | end 800 | 801 | for _, r in gen_x, param_x, state_x do 802 | m = cmp(m, r) 803 | end 804 | return m 805 | end 806 | methods.min_by = method1(min_by) 807 | exports.min_by = export1(min_by) 808 | methods.minimum_by = methods.min_by 809 | exports.minimum_by = exports.min_by 810 | 811 | local max = function(gen_x, param_x, state_x) 812 | local state_x, m = gen_x(param_x, state_x) 813 | if state_x == nil then 814 | error("max: iterator is empty") 815 | end 816 | 817 | local cmp 818 | if type(m) == "number" then 819 | -- An optimization: use math.max for numbers 820 | cmp = math.max 821 | else 822 | cmp = max_cmp 823 | end 824 | 825 | for _, r in gen_x, param_x, state_x do 826 | m = cmp(m, r) 827 | end 828 | return m 829 | end 830 | methods.max = method0(max) 831 | exports.max = export0(max) 832 | methods.maximum = methods.max 833 | exports.maximum = exports.max 834 | 835 | local max_by = function(cmp, gen_x, param_x, state_x) 836 | local state_x, m = gen_x(param_x, state_x) 837 | if state_x == nil then 838 | error("max: iterator is empty") 839 | end 840 | 841 | for _, r in gen_x, param_x, state_x do 842 | m = cmp(m, r) 843 | end 844 | return m 845 | end 846 | methods.max_by = method1(max_by) 847 | exports.max_by = export1(max_by) 848 | methods.maximum_by = methods.max_by 849 | exports.maximum_by = exports.max_by 850 | 851 | local totable = function(gen_x, param_x, state_x) 852 | local tab, key, val = {} 853 | while true do 854 | state_x, val = gen_x(param_x, state_x) 855 | if state_x == nil then 856 | break 857 | end 858 | table.insert(tab, val) 859 | end 860 | return tab 861 | end 862 | methods.totable = method0(totable) 863 | exports.totable = export0(totable) 864 | 865 | local tomap = function(gen_x, param_x, state_x) 866 | local tab, key, val = {} 867 | while true do 868 | state_x, key, val = gen_x(param_x, state_x) 869 | if state_x == nil then 870 | break 871 | end 872 | tab[key] = val 873 | end 874 | return tab 875 | end 876 | methods.tomap = method0(tomap) 877 | exports.tomap = export0(tomap) 878 | 879 | -------------------------------------------------------------------------------- 880 | -- Transformations 881 | -------------------------------------------------------------------------------- 882 | 883 | local map_gen = function(param, state) 884 | local gen_x, param_x, fun = param[1], param[2], param[3] 885 | return call_if_not_empty(fun, gen_x(param_x, state)) 886 | end 887 | 888 | local map = function(fun, gen, param, state) 889 | return wrap(map_gen, {gen, param, fun}, state) 890 | end 891 | methods.map = method1(map) 892 | exports.map = export1(map) 893 | 894 | local enumerate_gen_call = function(state, i, state_x, ...) 895 | if state_x == nil then 896 | return nil 897 | end 898 | return {i + 1, state_x}, i, ... 899 | end 900 | 901 | local enumerate_gen = function(param, state) 902 | local gen_x, param_x = param[1], param[2] 903 | local i, state_x = state[1], state[2] 904 | return enumerate_gen_call(state, i, gen_x(param_x, state_x)) 905 | end 906 | 907 | local enumerate = function(gen, param, state) 908 | return wrap(enumerate_gen, {gen, param}, {1, state}) 909 | end 910 | methods.enumerate = method0(enumerate) 911 | exports.enumerate = export0(enumerate) 912 | 913 | local intersperse_call = function(i, state_x, ...) 914 | if state_x == nil then 915 | return nil 916 | end 917 | return {i + 1, state_x}, ... 918 | end 919 | 920 | local intersperse_gen = function(param, state) 921 | local x, gen_x, param_x = param[1], param[2], param[3] 922 | local i, state_x = state[1], state[2] 923 | if i % 2 == 1 then 924 | return {i + 1, state_x}, x 925 | else 926 | return intersperse_call(i, gen_x(param_x, state_x)) 927 | end 928 | end 929 | 930 | -- TODO: interperse must not add x to the tail 931 | local intersperse = function(x, gen, param, state) 932 | return wrap(intersperse_gen, {x, gen, param}, {0, state}) 933 | end 934 | methods.intersperse = method1(intersperse) 935 | exports.intersperse = export1(intersperse) 936 | 937 | -------------------------------------------------------------------------------- 938 | -- Compositions 939 | -------------------------------------------------------------------------------- 940 | 941 | local function zip_gen_r(param, state, state_new, ...) 942 | if #state_new == #param / 2 then 943 | return state_new, ... 944 | end 945 | 946 | local i = #state_new + 1 947 | local gen_x, param_x = param[2 * i - 1], param[2 * i] 948 | local state_x, r = gen_x(param_x, state[i]) 949 | if state_x == nil then 950 | return nil 951 | end 952 | table.insert(state_new, state_x) 953 | return zip_gen_r(param, state, state_new, r, ...) 954 | end 955 | 956 | local zip_gen = function(param, state) 957 | return zip_gen_r(param, state, {}) 958 | end 959 | 960 | -- A special hack for zip/chain to skip last two state, if a wrapped iterator 961 | -- has been passed 962 | local numargs = function(...) 963 | local n = select('#', ...) 964 | if n >= 3 then 965 | -- Fix last argument 966 | local it = select(n - 2, ...) 967 | if type(it) == 'table' and getmetatable(it) == iterator_mt and 968 | it.param == select(n - 1, ...) and it.state == select(n, ...) then 969 | return n - 2 970 | end 971 | end 972 | return n 973 | end 974 | exports.numargs = numargs 975 | 976 | local zip = function(...) 977 | local n = numargs(...) 978 | if n == 0 then 979 | return wrap(nil_gen, nil, nil) 980 | end 981 | local param = { [2 * n] = 0 } 982 | local state = { [n] = 0 } 983 | 984 | local i, gen_x, param_x, state_x 985 | for i=1,n,1 do 986 | local it = select(n - i + 1, ...) 987 | gen_x, param_x, state_x = rawiter(it) 988 | param[2 * i - 1] = gen_x 989 | param[2 * i] = param_x 990 | state[i] = state_x 991 | end 992 | 993 | return wrap(zip_gen, param, state) 994 | end 995 | methods.zip = zip 996 | exports.zip = zip 997 | 998 | local cycle_gen_call = function(param, state_x, ...) 999 | if state_x == nil then 1000 | local gen_x, param_x, state_x0 = param[1], param[2], param[3] 1001 | return gen_x(param_x, deepcopy(state_x0)) 1002 | end 1003 | return state_x, ... 1004 | end 1005 | 1006 | local cycle_gen = function(param, state_x) 1007 | local gen_x, param_x, state_x0 = param[1], param[2], param[3] 1008 | return cycle_gen_call(param, gen_x(param_x, state_x)) 1009 | end 1010 | 1011 | local cycle = function(gen, param, state) 1012 | return wrap(cycle_gen, {gen, param, state}, deepcopy(state)) 1013 | end 1014 | methods.cycle = method0(cycle) 1015 | exports.cycle = export0(cycle) 1016 | 1017 | -- call each other 1018 | local chain_gen_r1 1019 | local chain_gen_r2 = function(param, state, state_x, ...) 1020 | if state_x == nil then 1021 | local i = state[1] 1022 | i = i + 1 1023 | if i > #param / 3 then 1024 | return nil 1025 | end 1026 | local state_x = param[3 * i] 1027 | return chain_gen_r1(param, {i, state_x}) 1028 | end 1029 | return {state[1], state_x}, ... 1030 | end 1031 | 1032 | chain_gen_r1 = function(param, state) 1033 | local i, state_x = state[1], state[2] 1034 | local gen_x, param_x = param[3 * i - 2], param[3 * i - 1] 1035 | return chain_gen_r2(param, state, gen_x(param_x, state[2])) 1036 | end 1037 | 1038 | local chain = function(...) 1039 | local n = numargs(...) 1040 | if n == 0 then 1041 | return wrap(nil_gen, nil, nil) 1042 | end 1043 | 1044 | local param = { [3 * n] = 0 } 1045 | local i, gen_x, param_x, state_x 1046 | for i=1,n,1 do 1047 | local elem = select(i, ...) 1048 | gen_x, param_x, state_x = iter(elem) 1049 | param[3 * i - 2] = gen_x 1050 | param[3 * i - 1] = param_x 1051 | param[3 * i] = state_x 1052 | end 1053 | 1054 | return wrap(chain_gen_r1, param, {1, param[3]}) 1055 | end 1056 | methods.chain = chain 1057 | exports.chain = chain 1058 | 1059 | -------------------------------------------------------------------------------- 1060 | -- Operators 1061 | -------------------------------------------------------------------------------- 1062 | 1063 | local operator = { 1064 | ---------------------------------------------------------------------------- 1065 | -- Comparison operators 1066 | ---------------------------------------------------------------------------- 1067 | lt = function(a, b) return a < b end, 1068 | le = function(a, b) return a <= b end, 1069 | eq = function(a, b) return a == b end, 1070 | ne = function(a, b) return a ~= b end, 1071 | ge = function(a, b) return a >= b end, 1072 | gt = function(a, b) return a > b end, 1073 | 1074 | ---------------------------------------------------------------------------- 1075 | -- Arithmetic operators 1076 | ---------------------------------------------------------------------------- 1077 | add = function(a, b) return a + b end, 1078 | div = function(a, b) return a / b end, 1079 | floordiv = function(a, b) return math.floor(a/b) end, 1080 | intdiv = function(a, b) 1081 | local q = a / b 1082 | if a >= 0 then return math.floor(q) else return math.ceil(q) end 1083 | end, 1084 | mod = function(a, b) return a % b end, 1085 | mul = function(a, b) return a * b end, 1086 | neq = function(a) return -a end, 1087 | unm = function(a) return -a end, -- an alias 1088 | pow = function(a, b) return a ^ b end, 1089 | sub = function(a, b) return a - b end, 1090 | truediv = function(a, b) return a / b end, 1091 | 1092 | ---------------------------------------------------------------------------- 1093 | -- String operators 1094 | ---------------------------------------------------------------------------- 1095 | concat = function(a, b) return a..b end, 1096 | len = function(a) return #a end, 1097 | length = function(a) return #a end, -- an alias 1098 | 1099 | ---------------------------------------------------------------------------- 1100 | -- Logical operators 1101 | ---------------------------------------------------------------------------- 1102 | land = function(a, b) return a and b end, 1103 | lor = function(a, b) return a or b end, 1104 | lnot = function(a) return not a end, 1105 | truth = function(a) return not not a end, 1106 | } 1107 | exports.operator = operator 1108 | methods.operator = operator 1109 | exports.op = operator 1110 | methods.op = operator 1111 | 1112 | -------------------------------------------------------------------------------- 1113 | -- module definitions 1114 | -------------------------------------------------------------------------------- 1115 | 1116 | -- a special syntax sugar to export all functions to the global table 1117 | setmetatable(exports, { 1118 | __call = function(t) 1119 | if not _G._LUAFUN then 1120 | for k, v in pairs(t) do _G[k] = v end 1121 | _G._LUAFUN = t 1122 | else 1123 | error("Luafun already imported") 1124 | end 1125 | end, 1126 | }) 1127 | 1128 | return exports 1129 | --------------------------------------------------------------------------------