├── LICENSE ├── README.md ├── examples.lua ├── stream.lua └── tests.lua /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lua Stream API - A Fluent Stream API for Lua 2 | ================================================= 3 | Author: Michael Karneim 4 | 5 | Project Homepage: http://github.com/mkarneim/lua-stream-api 6 | 7 | About 8 | ----- 9 | The Lua Stream API brings the benefits of the stream-based functional programming style to 10 | the [Lua language](http://lua.org). 11 | It provides a function called ```stream``` that produces a sequential stream of elements taken from 12 | an array or an iterator function. The stream object gives you the power of composing several 13 | stream operations into a single stream pipeline. 14 | 15 | For example, a simple stream pipeline could look like this: 16 | 17 | ```lua 18 | stream({3,4,5,1,2,3,4,4,4}).distinct().sort().foreach(print) 19 | ``` 20 | which emits the following output: 21 | ```lua 22 | 1 23 | 2 24 | 3 25 | 4 26 | 5 27 | ``` 28 | 29 | License 30 | ------- 31 | The source code of the Lua Stream API is in the PUBLIC DOMAIN. 32 | For more information please read the [LICENSE](http://github.com/mkarneim/lua-stream-api/blob/master/LICENSE) file. 33 | 34 | Supported Functions 35 | ------------------- 36 | 37 | ### Creating a Stream 38 | * ```stream()``` 39 | * ```stream(array)``` 40 | * ```stream(iter_func)``` 41 | 42 | ### Intermediate Operations 43 | * ```concat(streams...) -> stream``` 44 | * ```distinct() -> stream``` 45 | * ```filter(predicate) -> stream``` 46 | * ```flatmap(func) -> stream``` 47 | * ```flatten() -> stream``` 48 | * ```limit(maxnum) -> stream``` 49 | * ```map(func) -> stream``` 50 | * ```peek(consumer) -> stream``` 51 | * ```reverse() -> stream``` 52 | * ```skip(n) -> stream``` 53 | * ```sort(comparator) -> stream``` 54 | * ```split(func) -> stream, stream``` 55 | 56 | ### Terminal Operations 57 | * ```allmatch(predicate) -> boolean``` 58 | * ```anymatch(predicate) -> boolean``` 59 | * ```avg() -> number``` 60 | * ```collect(collector) -> any``` 61 | * ```count() -> number``` 62 | * ```foreach(c) -> nil``` 63 | * ```group(func) -> table``` 64 | * ```iter() -> function``` 65 | * ```last() -> any``` 66 | * ```max(comparator) -> any``` 67 | * ```min(comparator) -> any``` 68 | * ```next() -> any``` 69 | * ```nonematch(predicate) -> boolean``` 70 | * ```reduce(init, op) -> any``` 71 | * ```sum() -> number``` 72 | * ```toarray() -> table``` 73 | 74 | Getting Started 75 | --------------- 76 | The Lua Stream API consists of a single file called ```stream.lua```. Just [download it](https://raw.githubusercontent.com/mkarneim/lua-stream-api/master/stream.lua) into 77 | your project folder and include it into your program with ```require "stream"```. 78 | 79 | ### Creating a new stream from an array 80 | You can create a new stream from any *Lua table*, provided that the table is an array *indexed with consecutive numbers from 1 to n*, containing no ```nil``` values (or, to be more precise, only as trailing elements. ```nil``` values can never be part of the stream). 81 | 82 | Here is an example: 83 | ```lua 84 | a = {} 85 | a[1] = 100.23 86 | a[2] = -12 87 | a[3] = "42" 88 | 89 | st = stream(a) 90 | ``` 91 | 92 | Of course, you can do it also *inline*: 93 | ```lua 94 | st = stream({100.23, -12, "42"}) 95 | ``` 96 | 97 | To print the contents to screen you can use ```foreach(print)```: 98 | ```lua 99 | st.foreach(print) 100 | ``` 101 | This will produce the following output: 102 | ```lua 103 | 100.23 104 | -12 105 | 42 106 | ``` 107 | Later we will go into more details of the ```foreach()``` operation. 108 | 109 | For now, just let's have a look into another powerful alternative to create a stream. 110 | 111 | ### Creating a new stream form an iterator function 112 | Internally each stream works with a [Lua iterator function](https://www.lua.org/pil/7.1.html). 113 | This is a parameterless function that produces a new element for each call. 114 | 115 | You can create a new stream from any such function: 116 | ```lua 117 | function zeros() 118 | return 0 119 | end 120 | 121 | st = stream(zeros) 122 | ``` 123 | Please note, that this creates an infinite stream of zeros. When you append 124 | a [terminal operation](#terminal-operations) to the end of the pipeline it will 125 | actually never terminate: 126 | ```lua 127 | stream(zeros).foreach(print) 128 | 0 129 | 0 130 | 0 131 | 0 132 | . 133 | . 134 | . 135 | Arrrgh! 136 | ``` 137 | To prevent this from happening you could ```limit``` the number of elements: 138 | 139 | ```lua 140 | st.limit(100) 141 | ``` 142 | 143 | For example, this produces an array of 100 random numbers: 144 | ```lua 145 | numbers = stream(math.random).limit(100).toarray() 146 | ``` 147 | Please note that ```toarray()```, like ```foreach()```, is a [terminal operation](#terminal-operations), which 148 | means that it consumes elements from the stream. After this call the stream is 149 | completely empty. 150 | 151 | Another option to limit the number of elements is by limiting the iterator function itself. 152 | This can be done by returning a ```nil``` value when the production is finished. 153 | 154 | Here is an example. The ```range()``` function is an *iterator factory* that returns an iterator function 155 | which produces consecutive numbers in a specified range: 156 | ```lua 157 | function range(s,e,step) 158 | step = step or 1 159 | local next = s 160 | -- return an iterator function for numbers from s to e 161 | return function() 162 | if next > e then 163 | -- this should stop any consumer from doing more calls 164 | return nil 165 | end 166 | local current = next 167 | next = next + step 168 | return current 169 | end 170 | end 171 | 172 | numbers = stream(range(100,200)).toarray() 173 | ``` 174 | This produces an array with all integer numbers between 100 and 200 and assigns it to the ```numbers``` variable. 175 | 176 | So far, so good. Now that you know how to create a stream, let's see what we can do with it. 177 | 178 | ### Looping over the elements using the iter() operation 179 | Further above you have seen that you can print all elements by using the ```forach()``` operation. 180 | But this is not the only way to do it. 181 | 182 | Since internally the stream alyways maintains an iterator function, you can also use it to process its content. 183 | You can access it by calling ```iter()```. 184 | 185 | The following example shows how to process all elements with a standard Lua ```for ... in ... do``` loop: 186 | ```lua 187 | for i in st.iter() do 188 | -- do something with i, e.g. print it 189 | print(i) 190 | end 191 | ``` 192 | This prints all elements of the stream to the output. 193 | 194 | Please note that although ```iter()``` is a [terminal operation](#terminal-operations), it does not 195 | consume all elements immediately. Instead it does it lazily - element by element - whenever the produced iterator function is called. 196 | So, if you break from the loop before all elements are consumed, there will be elements left on the stream. 197 | 198 | ### Looping over the elements using the next() operation 199 | If you don't want to consume all elements at once but rather getting the first element of the stream, you may want to use the ```next()``` operation. 200 | 201 | ```lua 202 | st = stream({1,2,3}) 203 | print(st.next()) 204 | print(st.next()) 205 | ``` 206 | This produces the following output: 207 | ```lua 208 | 1 209 | 2 210 | ``` 211 | ### Getting the last element of a stream 212 | The ```last()``` operation returns the last element of the stream. 213 | ```lua 214 | st = stream({1,2,3}) 215 | print(st.last()) 216 | ``` 217 | In contrast to ```next()``` this can only be called once, since it consumes all elements from the stream in order to find the last one. Subsequent calls will simply return ```nil```. 218 | 219 | ### Looping over the elements with a consumer function 220 | Another option for getting all elements of the stream is the ```foreach()``` operation. 221 | We have used it already when we called it with the standard Lua ```print``` function in the examples above. 222 | 223 | By using the ```foreach(consumer)``` operation you can loop over the stream's content by calling it with a *consumer function*. 224 | This is any function with a single parameter. 225 | It will be called repeatedly for each element until the stream is empty. 226 | 227 | The following code prints all elements to the output: 228 | ```lua 229 | st.foreach(function(e) print(e) end) 230 | ``` 231 | 232 | Or, even shorter, as we already have seen, just use the reference to Lua's built-in ```print()``` function: 233 | ```lua 234 | st.foreach(print) 235 | ``` 236 | 237 | Now that we know how to access the elements of the stream, let's see how we can modify it. 238 | ### Filtering Elements 239 | Element-filtering is, besides *element-mapping*, one of the most used applications of stream pipelines. 240 | 241 | It belongs to the group of [intermediate operations](#intermediate-operations). That means, when you append one of those to a stream, you actually are creating a new stream that is lazily backed by the former one, and which extends the pipeline by one more step. Not until you call a terminal operation on the last part of the pipeline it will actually pull elements from upstream, going through all intermediate operations that are placed in between. 242 | 243 | By appending a ```filter(predicate)``` operation holding a *predicate function*, you can specify which elements should be passed downstream. 244 | A predicate function is any function with a single parameter. It should return ```true``` if the argument should be passed down the stream, ```false``` otherwise. 245 | 246 | Here is an example: 247 | ```lua 248 | function is_even(x) 249 | return x % 2 == 0 250 | end 251 | 252 | stream({1,2,3,4,5,6,7,8,9}).filter(is_even).foreach(print) 253 | ``` 254 | This prints a stream of only even elements to the output: 255 | ```lua 256 | 2 257 | 4 258 | 6 259 | 8 260 | ``` 261 | 262 | *... More to come ...* 263 | 264 | In the meanwhile you might want to browse the [examples](http://github.com/mkarneim/lua-stream-api/blob/master/examples.lua). 265 | -------------------------------------------------------------------------------- /examples.lua: -------------------------------------------------------------------------------- 1 | -- Demonstration of the Lua Stream API 2 | require "stream" 3 | 4 | -- Here are some helper functions: 5 | function isEven(x) 6 | return x % 2 == 0 7 | end 8 | function square(x) 9 | return x*x 10 | end 11 | function ident(x) 12 | return x 13 | end 14 | function myavg(iter) 15 | local sum = 0 16 | local count = 0; 17 | for e in iter do 18 | count = count + 1 19 | sum = sum + e 20 | end 21 | if count == 0 then 22 | return nil 23 | else 24 | return sum/count 25 | end 26 | end 27 | function range(s,e) 28 | local count = 0 29 | return function() 30 | local result = s+count 31 | if result<=e then 32 | count=count+1 33 | return result 34 | else 35 | return nil 36 | end 37 | end 38 | end 39 | function mult(f) 40 | return function(x) 41 | return f*x 42 | end 43 | end 44 | function countdown(x) 45 | local result = {} 46 | for i=1,x+1 do 47 | result[i]=x+1-i 48 | end 49 | return result 50 | end 51 | function fibbon(x) 52 | local f = {} 53 | if x > 0 then 54 | f[1] = 1 55 | if x > 1 then 56 | f[2] = 1 57 | for i=3,x do 58 | f[i] = f[i-2] + f[i-1] 59 | end 60 | end 61 | end 62 | return f 63 | end 64 | function num(i) 65 | return function() 66 | return i 67 | end 68 | end 69 | function sum(a,b) 70 | return a+b 71 | end 72 | function printarray(a) 73 | print(unpack(a)) 74 | end 75 | function sumarray(a) 76 | local r = 0 77 | for i=1,#a do 78 | r=r+a[i] 79 | end 80 | return r 81 | end 82 | function 83 | -- Here starts the demo: 84 | 85 | print("iter") 86 | for i in stream({1,2,3,4,5}).iter() do 87 | print(i) 88 | end 89 | 90 | print("foreach") 91 | stream({1,2,3,4,5}).foreach(print) 92 | 93 | print("filter") 94 | stream({1,2,3,4,5,6,7,8,9}).filter(isEven).foreach(print) 95 | 96 | print("reverse") 97 | stream({1,2,3,4,5}).reverse().foreach(print) 98 | 99 | print("sort") 100 | stream({5,7,6,3,4,1,2,8,9}).sort().foreach(print) 101 | 102 | print("map") 103 | stream({1,2,3,4,5}).map(square).foreach(print) 104 | 105 | print("next") 106 | local s1 = stream({1,2,3,4,5}) 107 | local first = s1.next() 108 | print(first) 109 | local second = s1.next() 110 | print(second) 111 | 112 | print("last") 113 | local last = stream({1,2,3,4,5}).last() 114 | print(last) 115 | 116 | print("max") 117 | local max = stream({5,7,6,3,4,1,2,8,9}).max() 118 | print(max) 119 | 120 | print("min") 121 | local min = stream({5,7,6,3,4,1,2,8,9}).min() 122 | print(min) 123 | 124 | print("sum") 125 | local _sum = stream({1,2,3,4,5}).sum() 126 | print(_sum) 127 | 128 | print("avg") 129 | local avg = stream({5,7,6,3,4,1,2,8,9}).avg() 130 | print(avg) 131 | 132 | print("collect(myavg)") 133 | local _myavg = stream({5,7,6,3,4,1,2,8,9}).collect(myavg) 134 | print(_myavg) 135 | 136 | print("toarray") 137 | local array = stream({1,2,3,4,5}).toarray() 138 | for i=1,#array do 139 | print(array[i]) 140 | end 141 | 142 | print("range") 143 | stream(range(1,5)).foreach(print) 144 | 145 | print("limit") 146 | stream(math.random).limit(5).foreach(print) 147 | 148 | print("count") 149 | local count = stream({1,2,3,4,5}).count() 150 | print(count) 151 | 152 | print("skip") 153 | stream({1,2,3,4,5,6,7,8,9}).skip(5).foreach(print) 154 | 155 | print("reverse") 156 | stream({1,2,3,4,5}).reverse().foreach(print) 157 | 158 | print("distinct") 159 | stream({1,2,3,2,4,2,5,2,5,1}).distinct().foreach(print) 160 | 161 | print("peek") 162 | stream({1,2,3,4}).peek(print).last() 163 | 164 | print("allmatch") 165 | local allmatch = stream({2,4,6,8}).allmatch(isEven) 166 | print(allmatch) 167 | 168 | print("anymatch") 169 | local anymatch = stream({1,2,3}).anymatch(isEven) 170 | print(anymatch) 171 | 172 | print("nonematch") 173 | local nonematch = stream({1,3,5,7}).nonematch(isEven) 174 | print(nonematch) 175 | 176 | print("flatmap") 177 | stream({0,4,5}).flatmap(fibbon).foreach(print) 178 | 179 | print("flatten") 180 | stream({{1,2,3},{4,5},{6},{},{7,8,9}}).flatten().foreach(print) 181 | 182 | print("concat 1") 183 | stream({1,2,3,4,5}).concat(stream({6,7,8,9})).foreach(print) 184 | 185 | print("concat 2") 186 | stream({1,2,3}).concat(stream({4,5,6}),stream({7,8,9})).foreach(print) 187 | 188 | print("group") 189 | local group = stream({1,2,3,4,5,6,7,8,9}).group(isEven) 190 | stream(group[true]).foreach(print) 191 | stream(group[false]).foreach(print) 192 | 193 | print("split") 194 | local even,odd = stream(range(1,10)).split(isEven) 195 | even.foreach(print) 196 | odd.foreach(print) 197 | 198 | print("reduce") 199 | local _sum = stream({1,2,3,4,5}).reduce(0,sum) 200 | print(_sum) 201 | 202 | print("merge") 203 | local s1 = stream({1,2,3}) 204 | local s2 = stream({5,6,7,8}) 205 | local s3 = stream({9}) 206 | s1.merge(s2,s3).foreach(print) 207 | -------------------------------------------------------------------------------- /stream.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Lua Stream API 1.0.0 3 | Created 2016 by Michael Karneim. 4 | For detailed documentation on the Lua Stream API please see . 5 | 6 | This is free and unencumbered software released into the public domain. 7 | 8 | Anyone is free to copy, modify, publish, use, compile, sell, or 9 | distribute this software, either in source code form or as a compiled 10 | binary, for any purpose, commercial or non-commercial, and by any 11 | means. 12 | 13 | In jurisdictions that recognize copyright laws, the author or authors 14 | of this software dedicate any and all copyright interest in the 15 | software to the public domain. We make this dedication for the benefit 16 | of the public at large and to the detriment of our heirs and 17 | successors. We intend this dedication to be an overt act of 18 | relinquishment in perpetuity of all present and future rights to this 19 | software under copyright law. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 24 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 25 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 26 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | For more information, please refer to 30 | 31 | This function returns a sequential stream with the provided input as its source. 32 | The input parameter must be nil or one of type table, boolean, number, string, or function. 33 | * If input is of type table, then the the new stream is created from the elements of the table, 34 | assuming that the table is an array indexed with consecutive numbers from 1 to n, containing 35 | no nil values. 36 | * If input is a single value of type boolean, number, or string, then the new stream contains 37 | just this value as its only element. 38 | * If input if of type function, then the new stream is created with this function as its 39 | iterator function, which must be a parameterless function that produces the "next" element on 40 | each call. 41 | * If input is nil (or not provided at all), then the new stream is empty. 42 | --]] 43 | function stream(input) 44 | 45 | -- The following _* functions are internal functions that implement the stream's behaviour based 46 | -- on iterator functions. 47 | -- The documentation of these functions is obmitted since it can be easiy deduced from the 48 | -- documentation of the corresponding stream function at the end of this file. 49 | 50 | local function _iterator(input) 51 | if input == nil then 52 | error("input must be of type table, but was nil") 53 | elseif type(input)~="table" then 54 | error("input must be of type table, but was a "..type(input)..": "..input) 55 | end 56 | local len = #input 57 | local i = 0 58 | local result = function() 59 | i = i + 1 60 | if i > len then 61 | return nil 62 | else 63 | return input[i] 64 | end 65 | end 66 | return result 67 | end 68 | 69 | local function _next(iter) 70 | return iter() 71 | end 72 | 73 | local function _concat(itarr) 74 | local len = #itarr 75 | local i = 1 76 | local it = itarr[i] 77 | local result = function() 78 | if i > len then 79 | return nil 80 | else 81 | while true do 82 | local e = it() 83 | if e ~= nil then 84 | return e 85 | else 86 | i = i + 1 87 | if i > len then 88 | return nil 89 | else 90 | it = itarr[i] 91 | end 92 | end 93 | end 94 | end 95 | end 96 | return result 97 | end 98 | 99 | local function _peek(iter,c) 100 | if c == nil then 101 | error("c must be of type function, but was nil") 102 | end 103 | if type(c)~="function" then 104 | error("c must be of type function, but was a "..type(c)) 105 | end 106 | local result = function() 107 | local e = iter() 108 | if e ~= nil then 109 | c(e) 110 | end 111 | return e 112 | end 113 | return result 114 | end 115 | 116 | local function _filter(iter,p) 117 | if p == nil then 118 | error("p must be of type function, but was nil") 119 | end 120 | if type(p)~="function" then 121 | error("p must be of type function, but was a "..type(p)) 122 | end 123 | local result = function() 124 | local e = iter() 125 | while e ~= nil do 126 | if p(e) then 127 | return e 128 | else 129 | e = iter() 130 | end 131 | end 132 | return nil 133 | end 134 | return result 135 | end 136 | 137 | local function _pack(iter,n) 138 | return function() 139 | local result = nil 140 | for i=1,n do 141 | local e = iter() 142 | if e == nil then 143 | return result 144 | else 145 | if result == nil then 146 | result = {} 147 | end 148 | table.insert(result,e) 149 | end 150 | end 151 | return result 152 | end 153 | end 154 | 155 | local function _map(iter,f) 156 | if f == nil then 157 | error("f must be of type function, but was nil") 158 | end 159 | if type(f)~="function" then 160 | error("f must be of type function, but was a "..type(f)) 161 | end 162 | local result = function() 163 | local e = iter() 164 | if e ~= nil then 165 | return f(e) 166 | else 167 | return nil 168 | end 169 | end 170 | return result 171 | end 172 | 173 | local function _flatmap(iter,f) 174 | if f == nil then 175 | error("f must be of type function, but was nil") 176 | end 177 | if type(f)~="function" then 178 | error("f must be of type function, but was a "..type(f)) 179 | end 180 | local it = nil 181 | local result = function() 182 | while true do 183 | if it == nil then 184 | local e = iter() 185 | if e == nil then 186 | return nil 187 | else 188 | it = _iterator(f(e)) 189 | end 190 | else 191 | local e = it() 192 | if e ~= nil then 193 | return e 194 | else 195 | it = nil 196 | end 197 | end 198 | end 199 | end 200 | return result 201 | end 202 | 203 | local function _flatten(iter) 204 | return _flatmap(iter, function(e) return e end) 205 | end 206 | 207 | local function _distinct(iter) 208 | local processed = {} 209 | local result = function() 210 | local e = iter() 211 | while e ~= nil do 212 | if processed[e]==nil then 213 | processed[e]=true 214 | return e 215 | else 216 | e = iter() 217 | end 218 | end 219 | return nil 220 | end 221 | return result 222 | end 223 | 224 | local function _limit(iter,max) 225 | local count = 0 226 | local result = function() 227 | count = count + 1 228 | if count > max then 229 | return nil 230 | else 231 | return iter() 232 | end 233 | end 234 | return result 235 | end 236 | 237 | local function _skip(iter,num) 238 | local i = 0 239 | while i len then 352 | idx = 1 353 | end 354 | local it = itarr[idx] 355 | local e = it() 356 | if e ~= nil then 357 | idx = idx + 1 358 | return e 359 | else 360 | table.remove(itarr, idx) 361 | len = #itarr 362 | end 363 | end 364 | 365 | local nilcount = 0 366 | local result = {} 367 | for i,it in ipairs(itarr) do 368 | local e = it() 369 | if e == nil then 370 | nilcount = nilcount + 1 371 | else 372 | result[i-nilcount] = e 373 | end 374 | end 375 | if nilcount >= #itarr then 376 | return nil 377 | else 378 | return result 379 | end 380 | end 381 | end 382 | 383 | local function _reduce(iter,init,op) 384 | if op == nil then 385 | error("op must be of type function, but was nil") 386 | end 387 | if type(op)~="function" then 388 | error("op must be of type function, but was a "..type(op)) 389 | end 390 | local result = init 391 | for e in iter do 392 | result = op(result,e) 393 | end 394 | return result 395 | end 396 | 397 | local function _reverse(iter) 398 | local result = _toarray(iter) 399 | local len = #result 400 | for i=1, len/2 do 401 | result[i], result[len-i+1] = result[len-i+1], result[i] 402 | end 403 | return _iterator(result) 404 | end 405 | 406 | local function _sort(iter,comp) 407 | local result = _toarray(iter) 408 | table.sort(result,comp) 409 | return _iterator(result) 410 | end 411 | 412 | local function _count(iter) 413 | local result = 0 414 | for e in iter do 415 | result = result + 1 416 | end 417 | return result 418 | end 419 | 420 | local function _max(iter,comp) 421 | local result = nil 422 | for e in iter do 423 | if result == nil or (comp ~= nil and comp(result,e)) or result < e then 424 | result = e 425 | end 426 | end 427 | return result 428 | end 429 | 430 | local function _min(iter,comp) 431 | local result = nil 432 | for e in iter do 433 | if result == nil or (comp ~= nil and comp(e,result)) or e < result then 434 | result = e 435 | end 436 | end 437 | return result 438 | end 439 | 440 | local function _sum(iter) 441 | local result = 0 442 | for e in iter do 443 | result = result + e 444 | end 445 | return result 446 | end 447 | 448 | local function _avg(iter) 449 | local sum = 0 450 | local count = 0; 451 | for e in iter do 452 | count = count + 1 453 | sum = sum + e 454 | end 455 | if count == 0 then 456 | return nil 457 | else 458 | return sum/count 459 | end 460 | end 461 | 462 | local function _allmatch(iter,p) 463 | if p == nil then 464 | error("p must be of type function, but was nil") 465 | end 466 | if type(p)~="function" then 467 | error("p must be of type function, but was a "..type(p)) 468 | end 469 | for e in iter do 470 | if not p(e) then 471 | return false 472 | end 473 | end 474 | return true 475 | end 476 | 477 | local function _anymatch(iter,p) 478 | if p == nil then 479 | error("p must be of type function, but was nil") 480 | end 481 | if type(p)~="function" then 482 | error("p must be of type function, but was a "..type(p)) 483 | end 484 | for e in iter do 485 | if p(e) then 486 | return true 487 | end 488 | end 489 | return false 490 | end 491 | 492 | local function _nonematch(iter,p) 493 | return not _anymatch(iter,p) 494 | end 495 | 496 | -- Returns a new stream created form the given interator function. 497 | local function _stream(iter) 498 | local result = { 499 | -- Returns the iterator function for the elements of this stream. 500 | iter = function() 501 | return iter 502 | end, 503 | -- Returns the next (aka first) element of this stream, or nil if the stream is empty. 504 | next = function() 505 | return _next(iter) 506 | end, 507 | -- Returns a lazily concatenated stream whose elements are all the elements of this stream 508 | -- followed by all the elements of the streams provided by the varargs parameter. 509 | concat = function(...) 510 | local streams = {iter} 511 | for i,s in ipairs({...}) do 512 | streams[i+1] = s.iter() 513 | end 514 | return stream(_concat(streams)) 515 | end, 516 | -- Returns a stream consisting of the elements of this stream, additionally performing 517 | -- the provided action on each element as elements are consumed from the resulting stream. 518 | peek = function(c) 519 | return stream(_peek(iter,c)) 520 | end, 521 | -- Returns a stream consisting of the elements of this stream that match the given predicate. 522 | filter = function(p) 523 | return stream(_filter(iter,p)) 524 | end, 525 | -- Returns a stream consisting of chunks, made of n adjacent elements of the original stream. 526 | pack = function(n) 527 | return stream(_pack(iter,n)) 528 | end, 529 | -- Returns a stream consisting of the results of applying the given function 530 | -- to the elements of this stream. 531 | map = function(f) 532 | return stream(_map(iter,f)) 533 | end, 534 | -- Returns a stream consisting of the flattened results 535 | -- produced by applying the provided mapping function on each element. 536 | flatmap = function(f) 537 | return stream(_flatmap(iter,f)) 538 | end, 539 | -- Returns a stream consisting of the flattened elements. 540 | flatten = function() 541 | return stream(_flatten(iter)) 542 | end, 543 | -- Returns a stream consisting of the elements of this stream, 544 | -- truncated to be no longer than maxsize in length. 545 | limit = function(maxsize) 546 | return stream(_limit(iter,maxsize)) 547 | end, 548 | -- Returns a stream consisting of the remaining elements of this stream 549 | -- after discarding the first n elements of the stream. If this stream contains 550 | -- fewer than n elements then an empty stream will be returned. 551 | skip = function(n) 552 | return stream(_skip(iter,n)) 553 | end, 554 | -- Returns the last element of this stream. 555 | last = function() 556 | return _last(iter) 557 | end, 558 | -- Performs the given action for each element of this stream. 559 | foreach = function(c) 560 | _foreach(iter,c) 561 | end, 562 | -- Returns an array containing the elements of this stream. 563 | toarray = function() 564 | return _toarray(iter) 565 | end, 566 | -- Returns a stream consisting of the elements of this stream, ordered randomly. 567 | -- Call math.randomseed( os.time() ) first to get nice random orders. 568 | shuffle = function() 569 | return stream(_shuffle(iter)) 570 | end, 571 | -- Returns a table which is grouping the elements of this stream by keys provided from 572 | -- the specified classification function. 573 | group = function(f) 574 | return _group(iter,f) 575 | end, 576 | -- Returns two streams consisting of the elements of this stream 577 | -- separated by the given predicate. 578 | split = function(f) 579 | return _split(iter,f) 580 | end, 581 | -- Returns a lazily merged stream whose elements are all the elements of this stream 582 | -- and of the streams provided by the varargs parameter. The elements are taken from all 583 | -- streams round-robin. 584 | merge = function(...) 585 | local itarr = {iter} 586 | for i,s in ipairs({...}) do 587 | itarr[i+1] = s.iter() 588 | end 589 | return stream(_merge(itarr)) 590 | end, 591 | -- Returns the result of the given collector that is supplied 592 | -- with an iterator for the elements of this stream. 593 | collect = function(c) 594 | return c(iter) 595 | end, 596 | -- Performs a reduction on the elements of this stream, using the provided initial value 597 | -- and the associative accumulation function, and returns the reduced value. 598 | reduce = function(init,op) 599 | return _reduce(iter,init,op) 600 | end, 601 | -- Returns a stream consisting of the elements of this stream in reversed order. 602 | reverse = function() 603 | return stream(_reverse(iter)) 604 | end, 605 | -- Returns a stream consisting of the elements of this stream, sorted according to the 606 | -- provided comparator. 607 | -- See table.sort for details on the comp parameter. 608 | -- If comp is not given, then the standard Lua operator < is used. 609 | sort = function(comp) 610 | return stream(_sort(iter,comp)) 611 | end, 612 | -- Returns a stream consisting of the distinct elements 613 | -- (according to the standard Lua operator ==) of this stream. 614 | distinct = function() 615 | return stream(_distinct(iter)) 616 | end, 617 | -- Returns the count of elements in this stream. 618 | count = function() 619 | return _count(iter) 620 | end, 621 | -- Returns the maximum element of this stream according to the provided comparator, 622 | -- or nil if this stream is empty. 623 | -- See table.sort for details on the comp parameter. 624 | -- If comp is not given, then the standard Lua operator < is used. 625 | max = function(comp) 626 | return _max(iter,comp) 627 | end, 628 | -- Returns the minimum element of this stream according to the provided comparator, 629 | -- or nil if this stream is empty. 630 | -- See table.sort for details on the comp parameter. 631 | -- If comp is not given, then the standard Lua operator < is used. 632 | min = function(comp) 633 | return _min(iter,comp) 634 | end, 635 | -- Returns the sum of elements in this stream. 636 | sum = function() 637 | return _sum(iter) 638 | end, 639 | -- Returns the arithmetic mean of elements of this stream, or nil if this stream is empty. 640 | avg = function() 641 | return _avg(iter) 642 | end, 643 | -- Returns whether all elements of this stream match the provided predicate. 644 | -- If the stream is empty then true is returned and the predicate is not evaluated. 645 | allmatch = function(p) 646 | return _allmatch(iter,p) 647 | end, 648 | -- Returns whether any elements of this stream match the provided predicate. 649 | -- If the stream is empty then false is returned and the predicate is not evaluated. 650 | anymatch = function(p) 651 | return _anymatch(iter,p) 652 | end, 653 | -- Returns whether no elements of this stream match the provided predicate. 654 | -- If the stream is empty then true is returned and the predicate is not evaluated. 655 | nonematch = function(p) 656 | return _nonematch(iter,p) 657 | end 658 | } 659 | return result 660 | end 661 | 662 | -- create an appropriate stream depending on the input type 663 | if input==nil then 664 | return _stream(_iterator({})) 665 | elseif type(input)=="table" then 666 | return _stream(_iterator(input)) 667 | elseif type(input)=="boolean" or type(input)=="number" or type(input)=="string" then 668 | return _stream(_iterator({input})) 669 | elseif type(input)=="function" then 670 | return _stream(input) 671 | else 672 | error("input must be nil or of type table, boolean, number, string, or function, but was a "..type(input)) 673 | end 674 | end 675 | -------------------------------------------------------------------------------- /tests.lua: -------------------------------------------------------------------------------- 1 | -- Test cases for Lua Stream API 2 | 3 | require "stream" 4 | 5 | function check(cond, format, ...) 6 | local message = string.format("Test Failed! "..format, ...) 7 | assert(cond, message) 8 | end 9 | 10 | function assert_equals(aact, aexp) 11 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 12 | for i=1,#aact do 13 | local exp = aexp[i] 14 | local act = aact[i] 15 | check(act == exp, "act=%s, exp=%s", act, exp) 16 | end 17 | end 18 | 19 | do 20 | print("Testing toarray") 21 | local aexp = {1,2,3,4,5} 22 | local aact = stream({1,2,3,4,5}).toarray() 23 | 24 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 25 | for i=1,#aact do 26 | local exp = aexp[i] 27 | local act = aact[i] 28 | check(act == exp, "act=%s, exp=%s", act, exp) 29 | end 30 | end 31 | 32 | do 33 | print("Testing iter") 34 | local exp = 0 35 | for act in stream({1,2,3,4,5}).iter() do 36 | exp = exp + 1 37 | check(act == exp, "act=%s, exp=%s", act, exp) 38 | end 39 | end 40 | 41 | do 42 | print("Testing foreach") 43 | local aexp = {1,2,3,4,5} 44 | local aact = {} 45 | local function consume(x) 46 | aact[#aact+1] = x 47 | end 48 | stream({1,2,3,4,5}).foreach(consume) 49 | 50 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 51 | for i=1,5 do 52 | local exp = aexp[i] 53 | local act = aact[i] 54 | check(act == exp, "act=%s, exp=%s", act, exp) 55 | end 56 | end 57 | 58 | do 59 | print("Testing filter") 60 | local function isEven(x) 61 | return x % 2 == 0 62 | end 63 | local aexp = {2,4,6,8} 64 | local aact = stream({1,2,3,4,5,6,7,8,9}).filter(isEven).toarray() 65 | 66 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 67 | for i=1,#aact do 68 | local exp = aexp[i] 69 | local act = aact[i] 70 | check(act == exp, "act=%s, exp=%s", act, exp) 71 | end 72 | end 73 | 74 | do 75 | print("Testing reverse") 76 | local aexp = {5,4,3,2,1} 77 | local aact = stream({1,2,3,4,5}).reverse().toarray() 78 | 79 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 80 | for i=1,#aact do 81 | local exp = aexp[i] 82 | local act = aact[i] 83 | check(act == exp, "act=%s, exp=%s", act, exp) 84 | end 85 | end 86 | 87 | do 88 | print("Testing sort") 89 | local aexp = {1,2,3,4,5,6,7,8,9} 90 | local aact = stream({5,7,6,3,4,1,2,8,9}).sort().toarray() 91 | 92 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 93 | for i=1,#aact do 94 | local exp = aexp[i] 95 | local act = aact[i] 96 | check(act == exp, "act=%s, exp=%s", act, exp) 97 | end 98 | end 99 | 100 | do 101 | print("Testing map") 102 | local function square(x) 103 | return x*x 104 | end 105 | local aexp = {1,4,9,16,25} 106 | local aact = stream({1,2,3,4,5}).map(square).toarray() 107 | 108 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 109 | for i=1,#aact do 110 | local exp = aexp[i] 111 | local act = aact[i] 112 | check(act == exp, "act=%s, exp=%s", act, exp) 113 | end 114 | end 115 | 116 | do 117 | print("Testing next") 118 | local aexp = {1,2,3} 119 | local aact = {} 120 | local s = stream({1,2,3}) 121 | local e = nil 122 | for i=1,#aexp do 123 | aact[#aact+1] = s.next() 124 | end 125 | 126 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 127 | for i=1,#aact do 128 | local exp = aexp[i] 129 | local act = aact[i] 130 | check(act == exp, "act=%s, exp=%s", act, exp) 131 | end 132 | end 133 | 134 | do 135 | print("Testing last") 136 | local act = stream({2,2,2,2,1}).last() 137 | local exp = 1 138 | check(act == exp, "act=%s, exp=%s", act, exp) 139 | end 140 | 141 | do 142 | print("Testing count") 143 | local act = stream({1,2,3,4,5,6,7,8,9}).count() 144 | local exp = 9 145 | check(act == exp, "act=%s, exp=%s", act, exp) 146 | end 147 | 148 | do 149 | print("Testing max") 150 | local act = stream({5,7,6,3,4,1,2,8,9}).max() 151 | local exp = 9 152 | check(act == exp, "act=%s, exp=%s", act, exp) 153 | end 154 | 155 | do 156 | print("Testing min") 157 | local act = stream({5,7,6,3,4,1,2,8,9}).min() 158 | local exp = 1 159 | check(act == exp, "act=%s, exp=%s", act, exp) 160 | end 161 | 162 | do 163 | print("Testing sum") 164 | local act = stream({1,2,3,4,5}).sum() 165 | local exp = 15 166 | check(act == exp, "act=%s, exp=%s", act, exp) 167 | end 168 | 169 | do 170 | print("Testing avg") 171 | local act = stream({1,2,3,4,5,6,7,8,9}).avg() 172 | local exp = 5 173 | check(act == exp, "act=%s, exp=%s", act, exp) 174 | end 175 | 176 | do 177 | print("Testing collect") 178 | local aexp = {1,2,3,4,5} 179 | local aact = {} 180 | local function read(iter) 181 | for x in iter do 182 | aact[#aact+1] = x 183 | end 184 | end 185 | stream({1,2,3,4,5}).collect(read) 186 | 187 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 188 | for i=1,5 do 189 | local exp = aexp[i] 190 | local act = aact[i] 191 | check(act == exp, "act=%s, exp=%s", act, exp) 192 | end 193 | end 194 | 195 | do 196 | print("Testing limit") 197 | local aexp = {1,2,3} 198 | local aact = stream({1,2,3,4,5}).limit(3).toarray() 199 | 200 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 201 | for i=1,#aact do 202 | local exp = aexp[i] 203 | local act = aact[i] 204 | check(act == exp, "act=%s, exp=%s", act, exp) 205 | end 206 | end 207 | 208 | do 209 | print("Testing skip") 210 | local aexp = {4,5} 211 | local aact = stream({1,2,3,4,5}).skip(3).toarray() 212 | 213 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 214 | for i=1,#aact do 215 | local exp = aexp[i] 216 | local act = aact[i] 217 | check(act == exp, "act=%s, exp=%s", act, exp) 218 | end 219 | end 220 | 221 | do 222 | print("Testing reverse") 223 | local aexp = {5,4,3,2,1} 224 | local aact = stream({1,2,3,4,5}).reverse().toarray() 225 | 226 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 227 | for i=1,#aact do 228 | local exp = aexp[i] 229 | local act = aact[i] 230 | check(act == exp, "act=%s, exp=%s", act, exp) 231 | end 232 | end 233 | 234 | do 235 | print("Testing distinct") 236 | local aexp = {1,2,4,5,3} 237 | local aact = stream({1,2,4,2,4,2,5,3,5,1}).distinct().toarray() 238 | 239 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 240 | for i=1,#aact do 241 | local exp = aexp[i] 242 | local act = aact[i] 243 | check(act == exp, "act=%s, exp=%s", act, exp) 244 | end 245 | end 246 | 247 | do 248 | print("Testing peek") 249 | local aexp = {1,2,3,4,5} 250 | local aact = {} 251 | local function consume(x) 252 | aact[#aact+1] = x 253 | end 254 | stream({1,2,3,4,5}).peek(consume).toarray() 255 | 256 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 257 | for i=1,5 do 258 | local exp = aexp[i] 259 | local act = aact[i] 260 | check(act == exp, "act=%s, exp=%s", act, exp) 261 | end 262 | end 263 | 264 | do 265 | print("Testing allmatch true") 266 | local function is_odd(x) 267 | return x%2==0 268 | end 269 | local act = stream({2,4,6,8,10}).allmatch(is_odd) 270 | local exp = true 271 | check(act == exp, "act=%s, exp=%s", act, exp) 272 | end 273 | 274 | do 275 | print("Testing allmatch false") 276 | local function is_odd(x) 277 | return x%2==0 278 | end 279 | local act = stream({2,4,6,8,11}).allmatch(is_odd) 280 | local exp = false 281 | check(act == exp, "act=%s, exp=%s", act, exp) 282 | end 283 | 284 | do 285 | print("Testing anymatch true") 286 | local function is_odd(x) 287 | return x%2==0 288 | end 289 | local act = stream({1,2,3}).anymatch(is_odd) 290 | local exp = true 291 | check(act == exp, "act=%s, exp=%s", act, exp) 292 | end 293 | 294 | do 295 | print("Testing anymatch false") 296 | local function is_odd(x) 297 | return x%2==0 298 | end 299 | local act = stream({1,3,5,7}).anymatch(is_odd) 300 | local exp = false 301 | check(act == exp, "act=%s, exp=%s", act, exp) 302 | end 303 | 304 | do 305 | print("Testing nonematch true") 306 | local function is_odd(x) 307 | return x%2==0 308 | end 309 | local act = stream({1,3,5,7}).nonematch(is_odd) 310 | local exp = true 311 | check(act == exp, "act=%s, exp=%s", act, exp) 312 | end 313 | 314 | do 315 | print("Testing nonematch false") 316 | local function is_odd(x) 317 | return x%2==0 318 | end 319 | local act = stream({1,2,3}).nonematch(is_odd) 320 | local exp = false 321 | check(act == exp, "act=%s, exp=%s", act, exp) 322 | end 323 | 324 | do 325 | print("Testing flatmap") 326 | local function duplicate(x) 327 | return {x,x} 328 | end 329 | local aexp = {1,1,2,2,3,3,4,4,5,5} 330 | local aact = stream({1,2,3,4,5}).flatmap(duplicate).toarray() 331 | 332 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 333 | for i=1,#aact do 334 | local exp = aexp[i] 335 | local act = aact[i] 336 | check(act == exp, "act=%s, exp=%s", act, exp) 337 | end 338 | end 339 | 340 | do 341 | print("Testing flatten") 342 | local aexp = {1,2,3,4,5,6,7,8,9} 343 | local aact = stream({{1,2},{3,4,5,6},{7},{},{8,9}}).flatten().toarray() 344 | 345 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 346 | for i=1,#aact do 347 | local exp = aexp[i] 348 | local act = aact[i] 349 | check(act == exp, "act=%s, exp=%s", act, exp) 350 | end 351 | end 352 | 353 | do 354 | print("Testing concat 1") 355 | local aexp = {1,2,3,4,5,6,7,8,9} 356 | local aact = stream({1,2,3,4}).concat(stream({5,6,7,8,9})).toarray() 357 | 358 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 359 | for i=1,#aact do 360 | local exp = aexp[i] 361 | local act = aact[i] 362 | check(act == exp, "act=%s, exp=%s", act, exp) 363 | end 364 | end 365 | 366 | do 367 | print("Testing concat 2") 368 | local aexp = {1,2,3,4,5,6,7,8,9} 369 | local aact = stream({1,2,3,4}).concat(stream({5,6}),stream({7,8,9})).toarray() 370 | 371 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 372 | for i=1,#aact do 373 | local exp = aexp[i] 374 | local act = aact[i] 375 | check(act == exp, "act=%s, exp=%s", act, exp) 376 | end 377 | end 378 | 379 | do 380 | print("Testing merge 1") 381 | local aexp = {1,5,2,6,3,7,4,8,9} 382 | local aact = stream({1,2,3,4}).merge(stream({5,6,7,8,9})).toarray() 383 | 384 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 385 | for i=1,#aact do 386 | local exp = aexp[i] 387 | local act = aact[i] 388 | check(act == exp, "act=%s, exp=%s", act, exp) 389 | end 390 | end 391 | 392 | do 393 | print("Testing merge 2") 394 | local aexp = {1,5,7,2,6,8,3,9,4} 395 | local aact = stream({1,2,3,4}).merge(stream({5,6}),stream({7,8,9})).toarray() 396 | 397 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 398 | for i=1,#aact do 399 | local exp = aexp[i] 400 | local act = aact[i] 401 | check(act == exp, "act=%s, exp=%s", act, exp) 402 | end 403 | end 404 | 405 | do 406 | print("Testing group") 407 | local function is_odd(x) 408 | return x%2==0 409 | end 410 | local aexp1 = {2,4} 411 | local aexp2 = {1,3} 412 | local mact = stream({1,2,3,4}).group(is_odd) 413 | do 414 | local aact = mact[true] 415 | local aexp = aexp1 416 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 417 | for i=1,#aact do 418 | local exp = aexp[i] 419 | local act = aact[i] 420 | check(act == exp, "act=%s, exp=%s", act, exp) 421 | end 422 | end 423 | do 424 | local aact = mact[false] 425 | local aexp = aexp2 426 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 427 | for i=1,#aact do 428 | local exp = aexp[i] 429 | local act = aact[i] 430 | check(act == exp, "act=%s, exp=%s", act, exp) 431 | end 432 | end 433 | end 434 | 435 | do 436 | print("Testing split") 437 | local function is_odd(x) 438 | return x%2==0 439 | end 440 | local aexp1 = {2,4} 441 | local aexp2 = {1,3} 442 | local s1,s2 = stream({1,2,3,4}).split(is_odd) 443 | 444 | do 445 | local aexp = aexp1 446 | local aact = s1.toarray() 447 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 448 | for i=1,#aact do 449 | local exp = aexp[i] 450 | local act = aact[i] 451 | check(act == exp, "act=%s, exp=%s", act, exp) 452 | end 453 | end 454 | do 455 | local aexp = aexp2 456 | local aact = s2.toarray() 457 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 458 | for i=1,#aact do 459 | local exp = aexp[i] 460 | local act = aact[i] 461 | check(act == exp, "act=%s, exp=%s", act, exp) 462 | end 463 | end 464 | end 465 | 466 | do 467 | print("Testing reduce") 468 | local function add(a,b) 469 | return a+b 470 | end 471 | local act = stream({1,2,3,4,5}).reduce(0,add) 472 | local exp = 15 473 | check(act == exp, "act=%s, exp=%s", act, exp) 474 | end 475 | 476 | do 477 | print("Testing pack") 478 | local aexp = {{1,2},{3,4},{5,6},{7,8},{9}} 479 | local aact = stream({1,2,3,4,5,6,7,8,9}).pack(2).toarray() 480 | 481 | check(#aact == #aexp, "#aact=%s, #aexp=%s", #aact, #aexp) 482 | for i=1,#aact do 483 | local exp = aexp[i] 484 | local act = aact[i] 485 | assert_equals(act,exp) 486 | end 487 | end 488 | --------------------------------------------------------------------------------