├── .busted ├── .gh-pages ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── config.ld ├── itertools.lua ├── luarocks ├── itertools-0.1-1.rockspec ├── itertools-0.2-1.rockspec └── itertools-scm-0.rockspec └── spec └── itertools_spec.lua /.busted: -------------------------------------------------------------------------------- 1 | -- vim:set ft=lua: 2 | return { 3 | _all = { 4 | verbose = true, 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gh-pages: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | bail () { 4 | echo "$*" 5 | exit 0 6 | } 7 | 8 | [[ ${TRAVIS_TEST_RESULT} = 0 ]] || bail "Skipping docs for failed build" 9 | [[ ${TRAVIS_BRANCH} = master ]] || bail "Skipping docs for non-master branch" 10 | [[ ${DOCS} = 1 ]] || bail "Skipping non-documentation build" 11 | 12 | cd doc 13 | git init 14 | git add . 15 | git commit --author='nobody ' -m"Updated $(date)" 16 | git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:gh-pages &> /dev/null 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | language: python 3 | 4 | env: 5 | global: 6 | - GH_REF: github.com/aperezdc/lua-itertools 7 | - secure: >- 8 | gysrVCjOCEJSg+dQALOSrxPMnTdie0VtsuBMraff6chCCH2c6UaKQG79zewNdHdWl/Gs3xxZSaTUh9fe0xd4AOqur1Koik+kLnKDGD1uS5xdcu72kAhPWKy3d5kKq358myZuoQrO42WYBMpruYPCbtsm+iL2k1af6hFDuy3Bv6Oc/p0fXzyDPhDeM2ihAhIjpHGWs5qOBLkow1YADHBb5enOYIUlKonf2cIfjctqMB2W8/4vdt44eGo8xjwAyuCLOm6+OtVJJ9iyV/nLOAc0RbgQXe2X/Gi05RnzByB5x+D0cv0B9Ivn8yZYXoCNM41RgqXc0IhN4gc9anZJrTQHHN7K6qgS7a3+XVkhYanvh1t2h+BVZPK+AuL9GSQXO7DpxmcnKjoxAj63TxTm/ONeonFoVTkq2esajIcWlSZnbCFA8HGhBK6A0pleUqgdUXTht2Q7Sirmm6um/qhkPWtdF3LTY+OWZ+2BnfYNEiM3vDK+eW4voIDrdqVLDpMDCcDdvMKspqi3XPXGE+IOjYtxNmE0bEgC5mQUfNNCPB7jKvKLawdtHImkW7QnGyPCqTs1WuPbAR+gSjgpkqHlTO3WZ5lLwIWgvsvlMu/OCYhZexVCsJWsZyNgDrGJ0rpYEwjej7nYNLEDPqpdQ+UASvhUDInZDpNVALZGaj6onLdZOK4= 9 | matrix: 10 | - LUA="lua 5.1" 11 | - LUA="lua 5.2" 12 | - LUA="lua 5.3" DOCS=1 13 | - LUA="luajit 2.0" 14 | - LUA="luajit 2.1" 15 | 16 | before_install: 17 | - pip install hererocks 18 | - hererocks here -r^ --$LUA 19 | - export PATH=$PATH:$PWD/here/bin 20 | - eval `luarocks path --bin` 21 | - lua -v 22 | 23 | install: 24 | - luarocks install luacov-coveralls 25 | - luarocks install cluacov 26 | - luarocks install busted 27 | - luarocks install ldoc 28 | 29 | script: 30 | - busted -c 31 | - ldoc . 32 | 33 | after_success: 34 | - luacov-coveralls -i itertools.lua -e spec/ -e here/ 35 | - bash -e .gh-pages 36 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [Unreleased] 6 | 7 | ## [0.2] - 2020.02.26 8 | ### Added 9 | - New `product()` function, which implements a cartesian product iterator. 10 | 11 | ## 0.1 12 | ### Added 13 | - Initial version. 14 | 15 | [Unreleased]: https://github.com/aperezdc/lua-wcwidth/compare/v0.2...HEAD 16 | [0.2]: https://github.com/aperezdc/lua-wcwidth/compare/v0.1...v0.2 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lua-itertools 2 | ============= 3 | 4 | [![Build Status](https://travis-ci.org/aperezdc/lua-itertools.svg?branch=master)](https://travis-ci.org/aperezdc/lua-itertools) 5 | [![Coverage Status](https://coveralls.io/repos/github/aperezdc/lua-itertools/badge.svg?branch=master)](https://coveralls.io/github/aperezdc/lua-itertools?branch=master) 6 | [![Documentation](https://img.shields.io/badge/doc-api-blue.png)](https://aperezdc.github.io/lua-itertools) 7 | 8 | 9 | Example 10 | ------- 11 | 12 | ```lua 13 | -- Import the module. 14 | local itertools = require "itertools" 15 | 16 | -- Create an infinite iterator which produces numbers starting at 100 17 | local iterable = itertools.count(100) 18 | 19 | -- Filter (select) the numbers which are divisible by three. 20 | iterable = itertools.filter(function (x) return x % 3 == 0 end, iterable) 21 | 22 | -- Pick only 10 of the numbers. 23 | iterable = itertools.islice(iterable, 1, 10) 24 | 25 | -- Calculate the square of each number 26 | iterable = itertools.map(function (x) return x * x end, iterable) 27 | 28 | -- Print them using a for-loop. 29 | for item in iterable do print(item) end 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /config.ld: -------------------------------------------------------------------------------- 1 | project = "itertools" 2 | format = "markdown" 3 | file = "itertools.lua" 4 | -------------------------------------------------------------------------------- /itertools.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- itertools.lua 3 | -- Copyright (C) 2016 Adrian Perez 4 | -- 5 | -- Distributed under terms of the MIT license. 6 | -- 7 | 8 | --- Functional iteration utilities using coroutines. 9 | -- 10 | -- Iterators 11 | -- --------- 12 | -- 13 | -- An **iterator** is a coroutine which yields values of a sequence. Unless 14 | -- specified otherwise, iterators use a constant amount of memory, and 15 | -- yielding the a value takes a constant *O(1)* amount of time. 16 | -- 17 | -- Typically iterator implementations use the following pattern: 18 | -- 19 | -- function iter (...) 20 | -- -- Do one-time initialization tasks. 21 | -- local finished_iterating = false 22 | -- -- Return a coroutine. 23 | -- return coroutine.wrap(function () 24 | -- while not finished_iterating do 25 | -- local value = calculate_next_value() 26 | -- coroutine.yield(value) 27 | -- end 28 | -- end) 29 | -- end 30 | -- 31 | -- Consuming an iterator is most conveniently done using a `for`-loop: 32 | -- 33 | -- for element in iterable do 34 | -- -- Do something with the element. 35 | -- end 36 | -- 37 | -- 38 | -- Credits 39 | -- ------- 40 | -- 41 | -- This module is loosely based on [Python's itertools 42 | -- module](https://docs.python.org/3/library/itertools.html), plus some 43 | -- other of Python's built-ins like [map()](https://docs.python.org/3/library/functions.html?highlight=map#map) 44 | -- and [filter()](https://docs.python.org/3/library/functions.html?highlight=filter#filter). 45 | -- 46 | -- @module itertools 47 | -- 48 | 49 | local pairs, ipairs, select = pairs, ipairs, select 50 | local t_sort, t_unpack = table.sort, table.unpack 51 | local co_yield, co_wrap = coroutine.yield, coroutine.wrap 52 | local co_resume = coroutine.resume 53 | 54 | local _ENV = nil 55 | 56 | 57 | local itertools = {} 58 | 59 | --- Iterate over the keys of a table. 60 | -- 61 | -- Given a `table`, returns an iterator over its keys, as returned by 62 | -- `pairs`. 63 | -- 64 | -- @param table A dictionary-like table. 65 | -- @treturn coroutine An iterator over the table keys. 66 | -- 67 | function itertools.keys (table) 68 | return co_wrap(function () 69 | for k, _ in pairs(table) do 70 | co_yield(k) 71 | end 72 | end) 73 | end 74 | 75 | --- Iterate over the values of a table. 76 | -- 77 | -- Given a `table`, returns an iterator over its values, as returned by 78 | -- `pairs`. 79 | -- 80 | -- @param table A dictionary-like table. 81 | -- @treturn coroutine An iterator over the table values. 82 | -- 83 | function itertools.values (table) 84 | return co_wrap(function () 85 | for _, v in pairs(table) do 86 | co_yield(v) 87 | end 88 | end) 89 | end 90 | 91 | --- Iterate over the key and value pairs of a table. 92 | -- 93 | -- Given a `table`, returns an iterator over its keys and values, as returned 94 | -- by `pairs`. Each yielded element is a two-element *{ key, value }* 95 | -- array-like table. 96 | -- 97 | -- Note that yielded array-like tables are not guaranteed be be unique, and if 98 | -- you need to save a copy of it you must create a new table yourself: 99 | -- 100 | -- local tpairs = {} 101 | -- for pair in iterable do 102 | -- table.insert(tpairs, { pair[1], pair[2] }) 103 | -- end 104 | -- 105 | -- @param table A dictionary-like table. 106 | -- @treturn coroutine An iterator over *{ key, value }* pairs. 107 | -- 108 | function itertools.items (table) 109 | return co_wrap(function () 110 | -- Reuse the same table to avoid table creation (and GC) in the loop. 111 | local pair = {} 112 | for k, v in pairs(table) do 113 | pair[1], pair[2] = k, v 114 | co_yield(pair) 115 | end 116 | end) 117 | end 118 | 119 | --- Iterate over each value of an array-like table. 120 | -- 121 | -- Given an array-like `table`, returns an iterator over its values, as 122 | -- returned by `ipairs`. 123 | -- 124 | -- @param table An array-like table. 125 | -- @treturn coroutine An iterator over the table values. 126 | -- 127 | function itertools.each (table) 128 | return co_wrap(function () 129 | for _, v in ipairs(table) do 130 | co_yield(v) 131 | end 132 | end) 133 | end 134 | 135 | --- Consume an iterable and collect its elements into an array-like table. 136 | -- 137 | -- Note that this function runs in *O(n)* time and memory usage because it 138 | -- needs to store all the elements yielded by the iterable. 139 | -- 140 | -- @tparam coroutine iterable A non-infinite iterator. 141 | -- @treturn table Array-like table with the collected elements. 142 | -- @treturn integer Number of elements collected. 143 | -- 144 | function itertools.collect (iterable) 145 | local t, n = {}, 0 146 | for element in iterable do 147 | n = n + 1 148 | t[n] = element 149 | end 150 | return t, n 151 | end 152 | 153 | --- Iterate over an infinite sequence of consecutive numbers. 154 | -- 155 | -- Returns an iterable which produces an infinite sequence of numbers starting 156 | -- at `n`, adding `step` to it in each iteration. Let `i` be the current 157 | -- iteration, starting with `i = 0`, the sequence generated would be: 158 | -- 159 | -- n + step * 0, n + step * 1, n + step * 2, ..., n + step * i 160 | -- 161 | -- @tparam[opt] number n First value in the sequence. 162 | -- @tparam[opt] number step Increment added in each iteration. 163 | -- @treturn coroutine An iterator over the sequence of numbers. 164 | -- 165 | function itertools.count (n, step) 166 | if n == nil then n = 1 end 167 | if step == nil then step = 1 end 168 | return co_wrap(function () 169 | while true do 170 | co_yield(n) 171 | n = n + step 172 | end 173 | end) 174 | end 175 | 176 | --- Iterate over a sequence of elements repeatedly. 177 | -- 178 | -- Returns an iterable which produces an infinite sequence of elements: first, 179 | -- the elements from `iterable`, then the sequence is repeated indefinitely. 180 | -- 181 | -- Note that this may store in memory up to as much elements as provided by 182 | -- `iterable`. 183 | -- 184 | -- @tparam coroutine iterable An iterator. 185 | -- @treturn coroutine An infinite iterator repeating elements from `iterable`. 186 | -- 187 | function itertools.cycle (iterable) 188 | local saved = {} 189 | local nitems = 0 190 | return co_wrap(function () 191 | for element in iterable do 192 | co_yield(element) 193 | nitems = nitems + 1 194 | saved[nitems] = element 195 | end 196 | while nitems > 0 do 197 | for i = 1, nitems do 198 | co_yield(saved[i]) 199 | end 200 | end 201 | end) 202 | end 203 | 204 | --- Iterate over the same value repeatedly. 205 | -- 206 | -- Returns an iterator which always produces the same `value`, indefinitely or 207 | -- up to a given number of `times`. 208 | -- 209 | -- @param value The value to produce. 210 | -- @tparam[opt] integer times Number of repetitions. 211 | -- @treturn coroutine An iterator which always produces `value`. 212 | -- 213 | function itertools.value (value, times) 214 | if times then 215 | return co_wrap(function () 216 | while times > 0 do 217 | times = times - 1 218 | co_yield(value) 219 | end 220 | end) 221 | else 222 | return co_wrap(function () 223 | while true do co_yield(value) end 224 | end) 225 | end 226 | end 227 | 228 | --- Iterate over selected values of an iterable. 229 | -- 230 | -- If `start` is specified, the returned iterator will skip all preceding 231 | -- elements; otherwise `start` defaults to `1`. The elements with indexes 232 | -- between `start` and `stop` (inclusive) will be yielded. If `stop` is 233 | -- not specified, the default is to yield all elements from `iterable` 234 | -- until it is exhausted. 235 | -- 236 | -- For example, using only `stop` can be used to limit the amount of elements 237 | -- yielded by an indefinite iterator. A `range()` iterator similar to Python's 238 | -- could be implemented as follows: 239 | -- 240 | -- function range (n) 241 | -- return itertools.islice(itertools.count(), nil, n) 242 | -- end 243 | -- 244 | -- @tparam coroutine iterable An iterator. 245 | -- @tparam[opt] integer start Index of the first element, by default `1`. 246 | -- @tparam[opt] integer stop Index of the last element, by default undefined. 247 | -- @treturn coroutine An iterator which selects values. 248 | -- 249 | function itertools.islice (iterable, start, stop) 250 | if start == nil then 251 | start = 1 252 | end 253 | return co_wrap(function () 254 | if stop ~= nil and stop - start < 1 then 255 | return 256 | end 257 | 258 | local current = 0 259 | for element in iterable do 260 | current = current + 1 261 | if stop ~= nil and current > stop then 262 | return 263 | end 264 | if current >= start then 265 | co_yield(element) 266 | end 267 | end 268 | end) 269 | end 270 | 271 | --- Iterate over values while a predicate is true. 272 | -- 273 | -- The returned iterator returns successive elements from an `iterable` as 274 | -- long as the `predicate` evaluates to `true` for each element. 275 | -- 276 | -- @tparam function predicate Function which checks the predicate. 277 | -- @tparam coroutine iterable An iterator. 278 | -- @treturn coroutine An iterator which yield values while the predicate is true. 279 | -- 280 | function itertools.takewhile (predicate, iterable) 281 | return co_wrap(function () 282 | for element in iterable do 283 | if predicate(element) then 284 | co_yield(element) 285 | else 286 | break 287 | end 288 | end 289 | end) 290 | end 291 | 292 | --- Iterate over elements applying a function to them 293 | -- 294 | -- @tparam function func function Function applied to each element. 295 | -- @tparam coroutine iterable An iterator. 296 | -- @treturn coroutine An iterator which yields the results of applying the 297 | -- function to the elements of `iterable`. 298 | -- 299 | function itertools.map (func, iterable) 300 | return co_wrap(function () 301 | for element in iterable do 302 | co_yield(func(element)) 303 | end 304 | end) 305 | end 306 | 307 | --- Iterate elements, filtering them according to a predicate. 308 | -- 309 | -- Returns an iterator over the elements another `iterable` which yields only 310 | -- the elements for which the `predicate` function return `true`. 311 | -- 312 | -- For example, the following returns an indefinite iterator over the even 313 | -- natural numbers: 314 | -- 315 | -- function even_naturals () 316 | -- return itertools.filter(function (x) return x % 2 == 1 end, 317 | -- itertools.count()) 318 | -- end 319 | -- 320 | -- @tparam function predicate 321 | -- @tparam coroutine iterable An iterator. 322 | -- @treturn coroutine An iterator over the elements which satisfy the predicate. 323 | -- 324 | function itertools.filter (predicate, iterable) 325 | return co_wrap(function () 326 | for element in iterable do 327 | if predicate(element) then 328 | co_yield(element) 329 | end 330 | end 331 | end) 332 | end 333 | 334 | local function make_comp_func(key) 335 | if key == nil then 336 | return nil 337 | end 338 | return function (a, b) 339 | return key(a) < key(b) 340 | end 341 | end 342 | 343 | local _collect = itertools.collect 344 | 345 | --- Iterate over the sorted elements from an iterable. 346 | -- 347 | -- A custom `key` function can be supplied, and it will be applied to each 348 | -- element being compared to obtain a sorting key, which will be the values 349 | -- used for comparisons when sorting. The `reverse` flag can be set to sort 350 | -- the elements in descending order. 351 | -- 352 | -- Note that `iterable` must be consumed before sorting, so the returned 353 | -- iterator runs in *O(n)* memory space. Sorting is done internally using 354 | -- `table.sort`. 355 | -- 356 | -- @tparam coroutine iterable An iterator. 357 | -- @tparam[opt] function key Function used to retrieve the sorting key used 358 | -- to compare elements. 359 | -- @tparam[opt] boolean reverse Whether to yield the elements in reverse 360 | -- (descending) order. If not supplied, defaults to `false`. 361 | -- @treturn coroutine An iterator over the sorted elements. 362 | -- 363 | function itertools.sorted (iterable, key, reverse) 364 | local t, n = _collect(iterable) 365 | t_sort(t, make_comp_func(key)) 366 | if reverse then 367 | return co_wrap(function () 368 | for i = n, 1, -1 do co_yield(t[i]) end 369 | end) 370 | else 371 | return co_wrap(function () 372 | for i = 1, n do co_yield(t[i]) end 373 | end) 374 | end 375 | end 376 | 377 | 378 | local _value = itertools.value 379 | 380 | --- Cartesian product of iterables. 381 | -- 382 | -- This is equivalent to nested for-loops, with the leftmost iterators 383 | -- being the outermost for-loops, so the yielded results cycle in a 384 | -- manner similar to an odometer, with the rightmost element changing 385 | -- on every iteration. 386 | -- 387 | -- @tparam coroutine iterable An iterator. 388 | -- @tparam[opt] coroutine ... Additional iterators. 389 | -- @treturn coroutine An iterator over tuples of product elements. 390 | -- 391 | function itertools.product (...) 392 | local niterables = select('#', ...) 393 | local items = {} 394 | for i = 1, niterables do 395 | items[i] = _collect(select(i, ...)) 396 | end 397 | return co_wrap(function () 398 | local indices, _ = _collect(_value(1, niterables)) 399 | while true do 400 | -- Assemble and yield a result using the current indices. 401 | local result = {} 402 | for i = 1, niterables do 403 | result[i] = items[i][indices[i]] 404 | end 405 | co_yield(t_unpack(result)) 406 | 407 | -- Update the indices, right to left, advancing to the 408 | -- next column when the previous one has rolled over. 409 | local last_rolled = nil 410 | for i = niterables, 1, -1 do 411 | indices[i] = indices[i] + 1 412 | if indices[i] > #items[i] then 413 | -- Roll over. 414 | last_rolled = i 415 | indices[i] = 1 416 | result[i] = items[i][indices[i]] 417 | else 418 | -- No roll over. 419 | result[i] = items[i][indices[i]] 420 | break 421 | end 422 | end 423 | -- All the columns rolled over: stop. 424 | if last_rolled == 1 then 425 | return 426 | end 427 | end 428 | end) 429 | end 430 | 431 | return itertools 432 | -------------------------------------------------------------------------------- /luarocks/itertools-0.1-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "itertools" 2 | version = "0.1-1" 3 | source = { 4 | url = "git://github.com/aperezdc/lua-itertools", 5 | tag = "v0.1" 6 | } 7 | description = { 8 | maintainer = "Adrián Pérez de Castro ", 9 | summary = "Functional iteration using coroutines", 10 | homepage = "https://github.com/aperezdc/lua-itertools", 11 | license = "MIT/X11" 12 | } 13 | dependencies = { 14 | "lua >= 5.1" 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | itertools = "itertools.lua" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /luarocks/itertools-0.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "itertools" 2 | version = "0.2-1" 3 | source = { 4 | url = "git://github.com/aperezdc/lua-itertools", 5 | tag = "v0.2" 6 | } 7 | description = { 8 | maintainer = "Adrián Pérez de Castro ", 9 | summary = "Functional iteration using coroutines", 10 | homepage = "https://github.com/aperezdc/lua-itertools", 11 | license = "MIT/X11" 12 | } 13 | dependencies = { 14 | "lua >= 5.1" 15 | } 16 | build = { 17 | type = "builtin", 18 | modules = { 19 | itertools = "itertools.lua" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /luarocks/itertools-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "itertools" 2 | version = "scm-0" 3 | source = { 4 | url = "git://github.com/aperezdc/lua-itertools" 5 | } 6 | description = { 7 | maintainer = "Adrián Pérez de Castro ", 8 | summary = "Functional iteration using coroutines", 9 | homepage = "https://github.com/aperezdc/lua-itertools", 10 | license = "MIT/X11" 11 | } 12 | dependencies = { 13 | "lua >= 5.1" 14 | } 15 | build = { 16 | type = "builtin", 17 | modules = { 18 | itertools = "itertools.lua" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spec/itertools_spec.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- itertools_spec.lua 3 | -- Copyright (C) 2016 Adrian Perez 4 | -- 5 | -- Distributed under terms of the MIT license. 6 | -- 7 | 8 | local iter = require "itertools" 9 | 10 | describe("itertools.map", function () 11 | it("iterates", function () 12 | local input = { 1, 2, 3, 4, 5 } 13 | local l = iter.collect(iter.map(function (x) return x + 1 end, 14 | iter.each(input))) 15 | for i = 1, #l do 16 | assert.equal(i + 1, l[i]) 17 | end 18 | end) 19 | end) 20 | 21 | describe("itertools.keys", function () 22 | it("iterates over table keys", function () 23 | local t = { foo = 1, bar = 2, baz = 3 } 24 | local l = { } 25 | for k in iter.keys(t) do 26 | table.insert(l, k) 27 | end 28 | assert.equal(3, #l) 29 | assert.truthy(t[l[1]]) 30 | assert.truthy(t[l[2]]) 31 | assert.truthy(t[l[3]]) 32 | local m = { } 33 | for k, _ in pairs(t) do 34 | table.insert(m, k) 35 | end 36 | assert.equal(#m, #l) 37 | table.sort(m) 38 | table.sort(l) 39 | for i = 1, #m do 40 | assert.equal(m[i], l[i]) 41 | end 42 | end) 43 | end) 44 | 45 | describe("itertools.items", function () 46 | it("iterates over k/v pairs", function () 47 | local data = { foo = 1, bar = 2, baz = 3 } 48 | local count = 0 49 | for pair in iter.items(data) do 50 | count = count + 1 51 | local k, v = pair[1], pair[2] 52 | assert.equal(data[k], v) 53 | end 54 | assert.equal(3, count) 55 | end) 56 | end) 57 | 58 | describe("itertools.count", function () 59 | it("counts ad infinitum", function () 60 | local nextvalue = iter.count() 61 | for i = 1, 10 do 62 | assert.equal(i, nextvalue()) 63 | end 64 | end) 65 | it("counts from a given value", function () 66 | local nextvalue = iter.count(10) 67 | for i = 10, 20 do 68 | assert.equal(i, nextvalue()) 69 | end 70 | end) 71 | it("counts using a step", function () 72 | local nextvalue = iter.count(nil, 2) 73 | for i = 1, 10, 2 do 74 | assert.equal(i, nextvalue()) 75 | end 76 | end) 77 | it("counts from a given value using a step", function () 78 | local nextvalue = iter.count(10, 5) 79 | for i = 10, 30, 5 do 80 | assert.equal(i, nextvalue()) 81 | end 82 | end) 83 | it("accepts a negative step", function () 84 | local nextvalue = iter.count(10, -1) 85 | for i = 10, 1, -1 do 86 | assert.equal(i, nextvalue()) 87 | end 88 | end) 89 | end) 90 | 91 | describe("itertools.cycle", function () 92 | it("repeats", function () 93 | local nextvalue = iter.cycle(iter.values { "foo", "bar" }) 94 | for i = 1, 10 do 95 | assert.equal("foo", nextvalue()) 96 | assert.equal("bar", nextvalue()) 97 | end 98 | end) 99 | end) 100 | 101 | describe("itertools.value", function () 102 | it("always returns the same value", function () 103 | local value = { foo = "bar" } 104 | local nextvalue = iter.value(value) 105 | for i = 1, 10 do 106 | assert.same(value, nextvalue()) 107 | end 108 | end) 109 | it("accepts a number of times", function () 110 | local value = { foo = "bar" } 111 | local result = iter.collect(iter.value(value, 15)) 112 | assert.equal(15, #result) 113 | for i = 1, #result do 114 | assert.same(value, result[i]) 115 | end 116 | end) 117 | end) 118 | 119 | describe("itertools.islice", function () 120 | local function check(result, nextvalue) 121 | local count = 0 122 | for _, v in ipairs(result) do 123 | assert.equal(v, nextvalue()) 124 | count = count + 1 125 | end 126 | assert.equal(count, #result) 127 | end 128 | 129 | local input = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } 130 | 131 | it("skips elements at the beginning", function () 132 | check({ 5, 6, 7, 8, 9, 10 }, 133 | iter.islice(iter.values(input), 5)) 134 | end) 135 | it("skips elements at the end", function () 136 | check({ 1, 2, 3, 4 }, 137 | iter.islice(iter.values(input), nil, 5)) 138 | end) 139 | it("skips elements at both ends", function () 140 | check({ 4, 5, 6, 7 }, 141 | iter.islice(iter.values(input), 4, 7)) 142 | end) 143 | it("returns one element for start==stop", function () 144 | check({ 5 }, iter.islice(iter.values(input), 5, 6)) 145 | end) 146 | it("returns no elements for an empty slice", function () 147 | check({}, iter.islice(iter.values(input), 7, 3)) 148 | end) 149 | end) 150 | 151 | describe("itertools.takewhile", function () 152 | it("filters elements", function () 153 | local data = { 1, 1, 1, 1, -1, 1, -1, 1, 1 } 154 | local result = iter.collect(iter.takewhile(function (x) return x > 0 end, 155 | iter.values(data))) 156 | assert.equal(4, #result) 157 | for _, v in ipairs(result) do 158 | assert.equal(1, v) 159 | end 160 | end) 161 | end) 162 | 163 | describe("itertools.filter", function () 164 | it("filters elements", function () 165 | local data = { 6, 1, 2, 3, 4, 5, 6 } 166 | local result = iter.collect(iter.filter(function (x) return x < 4 end, 167 | iter.values(data))) 168 | assert.equal(3, #result) 169 | for i, v in ipairs(result) do 170 | assert.equal(i, v) 171 | end 172 | end) 173 | end) 174 | 175 | describe("itertools.sorted", function () 176 | it("iterates over sorted items", function () 177 | local data = { 1, 45, 9, 2, -2, 42, 0, 42 } 178 | local sorted = iter.collect(iter.sorted(iter.values(data))) 179 | assert.equal(#data, #sorted) 180 | table.sort(data) 181 | for i = 1, #data do 182 | assert.equal(data[i], sorted[i]) 183 | end 184 | end) 185 | 186 | it("can sort in reverse order", function () 187 | local data = { 1, 45, 9, 2, -2, 42, 0, 42 } 188 | local sorted = iter.collect(iter.sorted(iter.values(data), nil, true)) 189 | assert.equal(#data, #sorted) 190 | table.sort(data, function (a, b) return a >= b end) 191 | for i = 1, #data do 192 | assert.equal(data[i], sorted[i]) 193 | end 194 | end) 195 | 196 | it("accepts a 'sort key' function", function () 197 | local data = { { z = 1 }, { z = 0 }, { z = 42 }, { z = -1 } } 198 | local sorted = iter.collect(iter.sorted(iter.values(data), 199 | function (v) return v.z end)) 200 | assert.equal(#data, #sorted) 201 | table.sort(data, function (a, b) return a.z < b.z end) 202 | for i = 1, #data do 203 | assert.equal(data[i], sorted[i]) 204 | end 205 | end) 206 | end) 207 | 208 | describe("itertools.product", function () 209 | it("iterates over a single iterator", function () 210 | local data = { 1, 43, 36, 12 } 211 | local result = iter.collect(iter.product(iter.values(data))) 212 | assert.equal(#data, #result) 213 | for i = 1, #data do 214 | assert.equal(data[i], result[i]) 215 | end 216 | end) 217 | 218 | it("makes a product out of two iterators", function () 219 | local i1 = iter.values {1, 2, 3} 220 | local i2 = iter.values {'a', 'b'} 221 | local result = {{1, 'a'}, {1, 'b'}, {2, 'a'}, {2, 'b'}, {3, 'a'}, {3, 'b'}} 222 | local ri = 0 223 | for l, r in iter.product(i1, i2) do 224 | ri = ri + 1 225 | assert.equal(result[ri][1], l) 226 | assert.equal(result[ri][2], r) 227 | end 228 | assert.equal(#result, ri) 229 | end) 230 | end) 231 | --------------------------------------------------------------------------------