lab42_streams 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +127 -7
- data/lib/lab42/stream/auto_import.rb +0 -3
- data/lib/lab42/stream/behavior.rb +62 -0
- data/lib/lab42/stream/class_methods.rb +1 -1
- data/lib/lab42/stream/core/enumerable.rb +2 -8
- data/lib/lab42/stream/empty.rb +3 -9
- data/lib/lab42/stream/enumerable.rb +39 -41
- data/lib/lab42/stream/higher_order.rb +3 -5
- data/lib/lab42/stream/kernel.rb +60 -23
- data/lib/lab42/stream/proc.rb +0 -19
- data/lib/lab42/stream/utility.rb +1 -1
- data/lib/lab42/stream/version.rb +1 -1
- data/lib/lab42/stream.rb +4 -6
- metadata +11 -97
- data/lib/lab42/stream/empty/enumerable.rb +0 -0
- data/lib/lab42/stream/hash.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e8f831b639a52dd52db85790b4fa4f7a558d4141b8deaf0ba7cd406f59970578
|
4
|
+
data.tar.gz: 58bfe3bfe2d31b694061a704ec6fbb30bbf901fe0e4134f91026edaceac22913
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17bb41d95481d19badab24f90ec1935f4450c19b65435000ba93721b602934506c665a239f524241ab4cff7ef127a84b7cdf4312cba74bb7b4b0eab2ac4c3ba2
|
7
|
+
data.tar.gz: 726f43fcf1c28fc99de3b1792fda975bdb4b9d7ed349aa380b95192af53b048def7717193065d6917a7e235cbddf36eb4ad63e2aa617eceeaa74bb15bd873f00
|
data/README.md
CHANGED
@@ -1,16 +1,136 @@
|
|
1
|
+
[](https://travis-ci.org/RobertDober/lab42_streams)
|
2
|
+
[](https://codeclimate.com/github/RobertDober/lab42_streams)
|
3
|
+
[](https://codeclimate.com/github/RobertDober/lab42_streams)
|
4
|
+
[](https://codeclimate.com/github/RobertDober/lab42_streams)
|
5
|
+
[](http://badge.fury.io/rb/lab42_streams)
|
6
|
+
|
1
7
|
# lab42\_streams
|
2
8
|
|
3
|
-
Bringing Streams to Ruby
|
9
|
+
## Bringing Streams to Ruby
|
4
10
|
|
5
11
|
An excellent introduction into `Streams` can be found [here](http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/6a-streams-part-1/)
|
6
12
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
13
|
+
## Basic Stream Tutorial
|
14
|
+
|
15
|
+
Streams are lazy, immutable lists, or for the purists, lazy cons cells (well the tail/cdr is lazy, the head/car is not).
|
16
|
+
|
17
|
+
A first example
|
18
|
+
|
19
|
+
### Infinite Streams
|
20
|
+
|
21
|
+
Given the following definition
|
22
|
+
|
23
|
+
```ruby :include
|
24
|
+
def fibs a=0, b=1
|
25
|
+
cons_stream a do
|
26
|
+
fibs b, a+b
|
27
|
+
end
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
The following spec will be satisfied
|
32
|
+
|
33
|
+
```ruby :example
|
34
|
+
expect(fibs.drop(1000).head).
|
35
|
+
to eq(43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875)
|
36
|
+
```
|
37
|
+
|
38
|
+
There are several things to remember here:
|
39
|
+
|
40
|
+
* The tail of a stream is *always* provided as block or lambda, the only way in ruby to
|
41
|
+
implement a normal order parameter.
|
42
|
+
|
43
|
+
* The result of the tail (that is when the delay or promise the tail defines is forced or realised)
|
44
|
+
must be a `Stream`. This has to be automated into your reasoning about `Streams` lest you will
|
45
|
+
have difficulties to come up with stream based solutions.
|
46
|
+
|
47
|
+
* When the promise of the tail is forced the stack frame of the `cons_stream` call is not active
|
48
|
+
any more, there will be no stack overflow.
|
49
|
+
|
50
|
+
|
51
|
+
### Transformation Chain
|
52
|
+
|
53
|
+
One major advantage of streams (and lazy evaluation in general) is that transformations can be composed without any performance penality.
|
54
|
+
|
55
|
+
While for example the following code would be terribly inefficent
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
elements = { 2 => "two", 4 => "four" }
|
59
|
+
list = 1..2 # but imagine a very large value of 2
|
60
|
+
list.map{ |x| x * 2 }.map{ |x| elements[x] }.map(&:reverse)
|
61
|
+
```
|
62
|
+
|
63
|
+
the following stream based code is not.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
|
67
|
+
translation = { [true,true] => "fizzbuzz", [true, false] => "fizz", [false, true] => "buzz" }
|
68
|
+
integers = Stream.iterate 0, :succ
|
69
|
+
fizzbuzz = integers
|
70
|
+
.reject{ |x| (x%100).zero? }
|
71
|
+
.map{ |i| [(i%3).zero?,(i%5).zero?,i] }
|
72
|
+
.map{ |f,b,i| translation.fetch([f,b],i) }
|
73
|
+
```
|
74
|
+
|
75
|
+
The reason for this is that, up to now, no single computation has been done, but _promises_ for doing so
|
76
|
+
have been registered. Only when we eventually force values these computations will be executed and then
|
77
|
+
it will make little difference if we execute one complex computation or five simple ones.
|
78
|
+
|
79
|
+
And as we operate on **infinite** streams it becomes obvious that the implementation must delay up to the end.
|
80
|
+
|
81
|
+
### Memoization
|
82
|
+
|
83
|
+
The fourth point to know about `Streams` is that:
|
84
|
+
|
85
|
+
* All promises are **memoized**.
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
Only for that reason the following naïve, but elegant implementation of the fibonacci sequence has O(N) runtime
|
90
|
+
characteristics, and the result can be computed:
|
91
|
+
|
92
|
+
```ruby :include
|
93
|
+
|
94
|
+
let(:fibs1) do
|
95
|
+
cons_stream(0){
|
96
|
+
cons_stream(1){
|
97
|
+
combine_streams fibs1, fibs1.tail, :+
|
98
|
+
}
|
99
|
+
}
|
100
|
+
end
|
101
|
+
```
|
102
|
+
|
103
|
+
```ruby :example
|
104
|
+
expect(fibs1.drop(1000).head)
|
105
|
+
.to eq(43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875)
|
106
|
+
|
107
|
+
```
|
108
|
+
|
109
|
+
|
110
|
+
## Finite Streams
|
111
|
+
|
112
|
+
Finite Streams are implemented the same way LISP imlements lists, by providing an _End_Marker_. What is `nil` in LISP
|
113
|
+
is `empty_stream` in Ruby. As a matter of fact the `empty_stream` method returns a singleton called `Lab42::Stream::Empty` which
|
114
|
+
is also accessible via `EmptyStream` if you required the lib with `require 'lab42/stream/auto_import'` which is true for the demos.
|
115
|
+
|
116
|
+
Here is an example of a finite stream
|
117
|
+
|
118
|
+
```ruby :include
|
119
|
+
let(:digits){ finite_stream( 0..9 ) }
|
120
|
+
```
|
121
|
+
|
122
|
+
Now the following all hold
|
11
123
|
|
124
|
+
```ruby :example
|
125
|
+
expect(digits.drop(9).head).to eq(9)
|
126
|
+
```
|
12
127
|
|
128
|
+
```ruby :example
|
129
|
+
expect(digits.drop(10)).to be_empty
|
130
|
+
```
|
13
131
|
|
14
|
-
|
132
|
+
or alternatively
|
15
133
|
|
16
|
-
|
134
|
+
```ruby :example
|
135
|
+
expect(digits.drop(10)).to eq(EmptyStream)
|
136
|
+
```
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'lab42/core/hash'
|
2
|
+
|
3
|
+
module Lab42
|
4
|
+
class Stream
|
5
|
+
class Behavior
|
6
|
+
|
7
|
+
def initialize( &blk )
|
8
|
+
@behavior = blk
|
9
|
+
end
|
10
|
+
|
11
|
+
def call( *args )
|
12
|
+
@behavior.( *args )
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def const(const_rval)
|
18
|
+
__const_hash__.fetch!(const_rval, new{ |*_| const_rval})
|
19
|
+
end
|
20
|
+
|
21
|
+
def make(*args, &blk)
|
22
|
+
if blk
|
23
|
+
raise ArgumentError, "cannot specify behavior with block and args: #{args.inspect}" unless args.empty?
|
24
|
+
blk
|
25
|
+
else
|
26
|
+
_make_from_args( args )
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def make1(*args, &blk)
|
31
|
+
if blk
|
32
|
+
blk
|
33
|
+
else
|
34
|
+
_make_from_args( args )
|
35
|
+
end
|
36
|
+
end
|
37
|
+
private
|
38
|
+
|
39
|
+
def _make_from_args( args )
|
40
|
+
if args.first.respond_to?( :call )
|
41
|
+
_curry( args )
|
42
|
+
else
|
43
|
+
-> (rcv, *a) do
|
44
|
+
rcv.send(*(args + a))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def _curry(args)
|
50
|
+
-> ( *a ) do
|
51
|
+
args.first.(*(args.drop(1)+a))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def __const_hash__
|
56
|
+
@__const_hash__ ||= {}
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,17 +1,11 @@
|
|
1
1
|
module Enumerable
|
2
|
-
def ordered_by
|
3
|
-
raise ArgumentError, 'need exactly one of block and behavior' if !blk == beh.empty?
|
4
|
-
|
5
|
-
beh = blk || (Symbol === beh.first ? sendmsg( *beh ) : beh.first )
|
2
|
+
def ordered_by beh
|
6
3
|
|
7
|
-
|
8
4
|
sort do | a, b |
|
9
5
|
a == b ? 0 : (
|
10
6
|
beh.(a, b) ? -1 : 1
|
11
7
|
)
|
12
8
|
end
|
13
|
-
end
|
14
9
|
|
15
|
-
|
10
|
+
end
|
16
11
|
end
|
17
|
-
|
data/lib/lab42/stream/empty.rb
CHANGED
@@ -1,7 +1,4 @@
|
|
1
1
|
require 'forwarder'
|
2
|
-
|
3
|
-
require_relative 'empty/enumerable'
|
4
|
-
|
5
2
|
module Lab42
|
6
3
|
class Stream
|
7
4
|
class Empty < Stream
|
@@ -36,20 +33,17 @@ module Lab42
|
|
36
33
|
def inject *args; args.first end
|
37
34
|
alias_method :__inject__, :inject
|
38
35
|
|
39
|
-
|
40
|
-
def itself *args, &blk; self end
|
36
|
+
def itself *; self end
|
41
37
|
|
42
|
-
def scan initial, *
|
38
|
+
def scan( initial, * )
|
43
39
|
[initial]
|
44
40
|
end
|
45
41
|
|
46
|
-
def scan1 *
|
42
|
+
def scan1( * )
|
47
43
|
[]
|
48
44
|
end
|
49
45
|
|
50
46
|
|
51
|
-
private
|
52
|
-
|
53
47
|
def self.new
|
54
48
|
@__instance__ ||= allocate
|
55
49
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative 'core/enumerable'
|
2
|
+
require_relative 'behavior'
|
2
3
|
|
3
4
|
module Lab42
|
4
5
|
class Stream
|
@@ -7,19 +8,17 @@ module Lab42
|
|
7
8
|
module Enumerable
|
8
9
|
|
9
10
|
def drop_until *bhv, &blk
|
10
|
-
bhv =
|
11
|
-
|
12
|
-
loop do
|
13
|
-
return s if bhv.(s.head)
|
14
|
-
s = s.tail
|
15
|
-
end
|
16
|
-
empty_stream
|
11
|
+
bhv = Behavior.make( *bhv, &blk )
|
12
|
+
__drop_while__ bhv.not
|
17
13
|
end
|
18
14
|
|
19
|
-
# N.B. Not implemented as drop_until( bhv.not )
|
20
|
-
# for performance reasons
|
21
15
|
def drop_while *bhv, &blk
|
22
|
-
bhv =
|
16
|
+
bhv = Behavior.make( *bhv, &blk )
|
17
|
+
__drop_while__ bhv
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def __drop_while__ bhv
|
23
22
|
s = self
|
24
23
|
loop do
|
25
24
|
return s unless bhv.(s.head)
|
@@ -31,6 +30,7 @@ module Lab42
|
|
31
30
|
def each
|
32
31
|
t = self
|
33
32
|
loop do
|
33
|
+
return if t.empty?
|
34
34
|
yield t.head
|
35
35
|
t = t.tail
|
36
36
|
end
|
@@ -64,7 +64,7 @@ module Lab42
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def lazy_take n=1
|
67
|
-
raise ArgumentError, "need a non negative
|
67
|
+
raise ArgumentError, "need a non negative Integer" if !(Integer === n) || n < 0
|
68
68
|
__lazy_take__ n
|
69
69
|
end
|
70
70
|
|
@@ -74,7 +74,7 @@ module Lab42
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def lazy_take_until *bhv, &blk
|
77
|
-
bhv =
|
77
|
+
bhv = Behavior.make( *bhv, &blk )
|
78
78
|
__lazy_take_until__ bhv
|
79
79
|
end
|
80
80
|
|
@@ -86,7 +86,7 @@ module Lab42
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def lazy_take_while *bhv, &blk
|
89
|
-
bhv =
|
89
|
+
bhv = Behavior.make( *bhv, &blk )
|
90
90
|
__lazy_take_while__ bhv
|
91
91
|
end
|
92
92
|
|
@@ -98,30 +98,30 @@ module Lab42
|
|
98
98
|
end
|
99
99
|
|
100
100
|
def reduce red=nil, &reducer
|
101
|
-
red =
|
101
|
+
red = Behavior.make( red, &reducer)
|
102
102
|
tail.__inject__ head, red
|
103
103
|
end
|
104
104
|
|
105
105
|
def inject agg, *red, &reducer
|
106
|
-
__inject__ agg,
|
106
|
+
__inject__ agg, Behavior.make( *red, &reducer )
|
107
107
|
end
|
108
108
|
|
109
109
|
def filter *args, &blk
|
110
|
-
__filter__ self,
|
110
|
+
__filter__ self, Behavior.make( *args, &blk )
|
111
111
|
end
|
112
112
|
|
113
113
|
def reject *args, &blk
|
114
|
-
__filter__ self,
|
114
|
+
__filter__ self, Behavior.make( *args, &blk ).not
|
115
115
|
end
|
116
116
|
|
117
117
|
def flatmap *args, &blk
|
118
|
-
__flatmap__
|
118
|
+
__flatmap__ Behavior.make( *args, &blk )
|
119
119
|
end
|
120
120
|
|
121
121
|
def __flatmap__ a_proc
|
122
|
-
# require 'pry'
|
123
|
-
# binding.pry
|
124
122
|
hh = a_proc.( head )
|
123
|
+
raise ArgumentError, "flatmap can only map on streams, use flatmap_with_each to map over streams and enumerables" unless
|
124
|
+
Lab42::Stream === hh
|
125
125
|
if hh.empty?
|
126
126
|
tail.__flatmap__ a_proc
|
127
127
|
else
|
@@ -130,18 +130,18 @@ module Lab42
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def flatmap_with_each *args, &blk
|
133
|
-
__flatmap_with_each__
|
133
|
+
__flatmap_with_each__ Behavior.make( *args, &blk )
|
134
134
|
end
|
135
135
|
|
136
136
|
def __flatmap_with_each__ a_proc, rest_of_enum = []
|
137
137
|
# Process expanded values
|
138
138
|
return cons_stream( rest_of_enum.first ){ __flatmap_with_each__ a_proc, rest_of_enum.drop( 1 ) } unless
|
139
|
-
|
139
|
+
rest_of_enum.empty?
|
140
140
|
|
141
141
|
# Map a scalar value
|
142
142
|
hh = a_proc.( head )
|
143
143
|
return cons_stream( hh ){ tail.__flatmap_with_each__ a_proc } unless
|
144
|
-
|
144
|
+
hh.respond_to? :each
|
145
145
|
|
146
146
|
# Start a new expansion...
|
147
147
|
# ... consider an empty expansion
|
@@ -153,10 +153,8 @@ module Lab42
|
|
153
153
|
|
154
154
|
def scan initial, *args, &blk
|
155
155
|
cons_stream initial do
|
156
|
-
__scan__ initial,
|
156
|
+
__scan__ initial, Behavior.make( *args, &blk )
|
157
157
|
end.tap{ |r|
|
158
|
-
# require 'pry'
|
159
|
-
# binding.pry
|
160
158
|
}
|
161
159
|
end
|
162
160
|
|
@@ -170,16 +168,16 @@ module Lab42
|
|
170
168
|
end
|
171
169
|
|
172
170
|
def take_until *bhv, &blk
|
173
|
-
bhv =
|
174
|
-
|
175
|
-
each do | ele |
|
176
|
-
return x if bhv.( ele )
|
177
|
-
x << ele
|
178
|
-
end
|
179
|
-
x
|
171
|
+
bhv = Behavior.make( *bhv, &blk )
|
172
|
+
__take_while__ bhv.not
|
180
173
|
end
|
174
|
+
|
181
175
|
def take_while *bhv, &blk
|
182
|
-
bhv =
|
176
|
+
bhv = Behavior.make( *bhv, &blk )
|
177
|
+
__take_while__ bhv
|
178
|
+
end
|
179
|
+
|
180
|
+
def __take_while__ bhv
|
183
181
|
x = []
|
184
182
|
each do | ele |
|
185
183
|
return x unless bhv.( ele )
|
@@ -189,8 +187,9 @@ module Lab42
|
|
189
187
|
end
|
190
188
|
|
191
189
|
def to_a
|
192
|
-
take_while
|
190
|
+
take_while Behavior.const( true )
|
193
191
|
end
|
192
|
+
|
194
193
|
alias_method :entries, :to_a
|
195
194
|
|
196
195
|
def make_cyclic
|
@@ -200,9 +199,8 @@ module Lab42
|
|
200
199
|
end
|
201
200
|
|
202
201
|
def map *args, &blk
|
203
|
-
# TODO: Get this check and a factory to create a proc for this into core/fn
|
204
202
|
raise ArgumentError, "use either a block or arguments" if args.empty? && !blk || !args.empty? && blk
|
205
|
-
__map__
|
203
|
+
__map__ Behavior.make( *args, &blk )
|
206
204
|
end
|
207
205
|
|
208
206
|
def __map__ prc
|
@@ -216,7 +214,7 @@ module Lab42
|
|
216
214
|
end
|
217
215
|
|
218
216
|
def take n=1
|
219
|
-
raise ArgumentError, "need a non negative
|
217
|
+
raise ArgumentError, "need a non negative Integer" if !(Integer === n) || n < 0
|
220
218
|
x = []
|
221
219
|
each do | ele |
|
222
220
|
return x if n.zero?
|
@@ -235,12 +233,12 @@ module Lab42
|
|
235
233
|
|
236
234
|
def zip_as_ary *other_streamables
|
237
235
|
zip( *other_streamables )
|
238
|
-
|
236
|
+
.map( &:entries )
|
239
237
|
end
|
240
238
|
|
241
239
|
def __zip__ streams
|
242
|
-
cons_stream( [head] + streams.map(
|
243
|
-
tail.__zip__ streams.map(
|
240
|
+
cons_stream( [head] + streams.map(&:head) ){
|
241
|
+
tail.__zip__ streams.map(&:tail)
|
244
242
|
}
|
245
243
|
end
|
246
244
|
|
@@ -1,16 +1,14 @@
|
|
1
|
+
require_relative 'behavior'
|
1
2
|
module Lab42
|
2
3
|
class Stream
|
3
4
|
module HigherOrder
|
4
5
|
def combine *streams_and_op, &operation
|
5
6
|
op = streams_and_op.pop unless self.class === streams_and_op.last
|
6
|
-
op =
|
7
|
-
# TODO: Decide what to do if op.arity and streams_and_op.size.succ do not match????
|
7
|
+
op = Behavior.make1( op, &operation )
|
8
8
|
__combine__( op, *streams_and_op )
|
9
9
|
end
|
10
10
|
|
11
11
|
def __combine__ op, *streams
|
12
|
-
# TODO: Decide if we can continue if one of the streams is empty iff op.arity < 0
|
13
|
-
# for now no!
|
14
12
|
return empty_stream if streams.any?( &:empty? )
|
15
13
|
values = streams.map( &:head )
|
16
14
|
new_head = op.(head, *values)
|
@@ -23,7 +21,7 @@ module Lab42
|
|
23
21
|
indexed = with_index
|
24
22
|
n.times.map do | i |
|
25
23
|
indexed
|
26
|
-
.filter{ |
|
24
|
+
.filter{ |_, idx| idx % n == i }
|
27
25
|
.map( :first )
|
28
26
|
end
|
29
27
|
end
|
data/lib/lab42/stream/kernel.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'lab42/core/kernel'
|
2
|
+
require_relative 'behavior'
|
3
|
+
|
2
4
|
module Kernel
|
3
5
|
def binop_streams op, stream1, stream2
|
4
6
|
combine_streams stream1, stream2 do |e1, e2|
|
@@ -33,18 +35,24 @@ module Kernel
|
|
33
35
|
end
|
34
36
|
|
35
37
|
def finite_stream enum
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
case enum
|
39
|
+
when Range
|
40
|
+
_finite_stream_from_range enum
|
41
|
+
when Array
|
42
|
+
_finite_stream_from_ary enum
|
43
|
+
when Hash
|
44
|
+
_finite_stream_from_hash enum
|
45
|
+
when Enumerator
|
46
|
+
_finite_stream_from_enumerator! enum.to_enum
|
47
|
+
else
|
48
|
+
raise TypeError, "cannot create a finite stream from type #{enum.class.inspect}"
|
49
|
+
end
|
40
50
|
end
|
41
51
|
|
42
52
|
def flatmap stream, *args, &blk
|
43
53
|
stream.flatmap( *args, &blk )
|
44
54
|
end
|
45
55
|
|
46
|
-
# TODO: Reimplement with a cursor into streams to avoid
|
47
|
-
# the (potentially) costly array arithm in the tail def
|
48
56
|
def merge_streams *streams
|
49
57
|
s = streams.reject( &:empty? )
|
50
58
|
return empty_stream if s.empty?
|
@@ -54,7 +62,7 @@ module Kernel
|
|
54
62
|
end
|
55
63
|
|
56
64
|
def merge_streams_by *streams_and_beh, &blk
|
57
|
-
beh = blk || streams_and_beh.pop
|
65
|
+
beh = Lab42::Stream::Behavior.make1( blk || streams_and_beh.pop, &blk )
|
58
66
|
__merge_streams_by__ beh, streams_and_beh
|
59
67
|
end
|
60
68
|
|
@@ -69,27 +77,56 @@ module Kernel
|
|
69
77
|
|
70
78
|
def iterate_without_block args
|
71
79
|
rest = args.drop 1
|
72
|
-
if
|
80
|
+
if rest.first && rest.first.respond_to?( :call )
|
73
81
|
cons_stream( args.first ){ iterate( rest.first.(*([args.first] + rest.drop(1))), *rest ) }
|
74
82
|
else
|
75
|
-
cons_stream( args.first ){ iterate(
|
83
|
+
cons_stream( args.first ){ iterate( args.first.send(*rest), *rest ) }
|
76
84
|
end
|
77
85
|
end
|
78
86
|
|
79
|
-
private
|
80
|
-
def __merge_streams_by__ beh, streams
|
81
|
-
still_there = streams.reject( &:empty? )
|
82
|
-
return empty_stream if still_there.empty?
|
83
|
-
__merge_streams_by_with_present__ beh, still_there, streams
|
84
|
-
end
|
87
|
+
private
|
85
88
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
89
|
+
def _finite_stream_from_ary ary
|
90
|
+
return empty_stream if ary.empty?
|
91
|
+
cons_stream(ary.first){ finite_stream(ary.drop(1)) }
|
92
|
+
end
|
90
93
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
end
|
94
|
+
def _finite_stream_from_boundaies fst, lst
|
95
|
+
return empty_stream if fst > lst
|
96
|
+
cons_stream(fst){ _finite_stream_from_boundaies fst.succ, lst }
|
97
|
+
end
|
98
|
+
|
99
|
+
def _finite_stream_from_hash hsh
|
100
|
+
return empty_stream if hsh.empty?
|
101
|
+
cons_stream(hsh.first){ finite_stream(hsh.without(hsh.first.first)) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def _finite_stream_from_enumerator! enum
|
105
|
+
cons_stream( enum.next ){ _finite_stream_from_enumerator! enum }
|
106
|
+
rescue StopIteration
|
107
|
+
empty_stream
|
108
|
+
end
|
109
|
+
|
110
|
+
def _finite_stream_from_range range
|
111
|
+
fst = range.first
|
112
|
+
lst = range.last
|
113
|
+
lst = lst.pred if range.exclude_end?
|
114
|
+
_finite_stream_from_boundaies fst, lst
|
115
|
+
end
|
116
|
+
|
117
|
+
def __merge_streams_by__ beh, streams
|
118
|
+
still_there = streams.reject( &:empty? )
|
119
|
+
return empty_stream if still_there.empty?
|
120
|
+
__merge_streams_by_with_present__ beh, still_there
|
121
|
+
end
|
122
|
+
|
123
|
+
def __merge_streams_by_with_present__ beh, still_there
|
124
|
+
ordered_heads = still_there
|
125
|
+
.map( &:head )
|
126
|
+
.ordered_by( beh )
|
127
|
+
|
128
|
+
cons_stream_n( *ordered_heads ){
|
129
|
+
__merge_streams_by__ beh, still_there.map( &:tail )
|
130
|
+
}
|
131
|
+
end
|
95
132
|
end
|
data/lib/lab42/stream/proc.rb
CHANGED
@@ -12,28 +12,9 @@ class Proc
|
|
12
12
|
}
|
13
13
|
end
|
14
14
|
|
15
|
-
# TODO: Use this from core/fn as soon as available
|
16
|
-
def make_behavior *args
|
17
|
-
raise ArgumentError, "cannot specify behavior with block and args: #{args.inspect}" unless args.compact.empty?
|
18
|
-
self
|
19
|
-
end
|
20
|
-
|
21
15
|
def not
|
22
16
|
-> (*args, &blk) {
|
23
17
|
! self.(*args, &blk)
|
24
18
|
}
|
25
19
|
end
|
26
20
|
end
|
27
|
-
|
28
|
-
class NilClass
|
29
|
-
# TODO: Use this from core/fn as soon as available
|
30
|
-
def make_behavior *args
|
31
|
-
return args.first if args.size == 1 && args.first.respond_to?( :call )
|
32
|
-
|
33
|
-
return ->(*a){
|
34
|
-
args.first(*(args.drop(1)+a))
|
35
|
-
} if args.first.respond_to?( :call )
|
36
|
-
|
37
|
-
sendmsg( *args )
|
38
|
-
end
|
39
|
-
end
|
data/lib/lab42/stream/utility.rb
CHANGED
data/lib/lab42/stream/version.rb
CHANGED
data/lib/lab42/stream.rb
CHANGED
@@ -5,12 +5,10 @@ require_relative './stream/kernel'
|
|
5
5
|
require_relative './stream/array'
|
6
6
|
require_relative './stream/enumerable'
|
7
7
|
require_relative './stream/higher_order'
|
8
|
-
require_relative './stream/hash'
|
9
8
|
require_relative './stream/proc'
|
10
9
|
require_relative './stream/class_methods'
|
11
10
|
require_relative './stream/utility'
|
12
11
|
|
13
|
-
# TODO: This should rather be implemented in lab_42/core/fn
|
14
12
|
require_relative './stream/kernel/extensions'
|
15
13
|
|
16
14
|
module Lab42
|
@@ -31,7 +29,7 @@ module Lab42
|
|
31
29
|
def combine_streams *args, &operation
|
32
30
|
op = args.shift unless self.class === args.first
|
33
31
|
raise ArgumentError, "Missing stream parameters" if args.empty?
|
34
|
-
__combine_streams__
|
32
|
+
__combine_streams__ Behavior.make( op, &operation), args
|
35
33
|
end
|
36
34
|
|
37
35
|
def drop n = 1
|
@@ -53,10 +51,10 @@ module Lab42
|
|
53
51
|
def to_stream; self end
|
54
52
|
|
55
53
|
def __combine_streams__ op, args
|
56
|
-
return empty_stream if args.any?(
|
54
|
+
return empty_stream if args.any?(&:empty?)
|
57
55
|
|
58
|
-
new_head = op.(head, *args.map(
|
59
|
-
cons_stream( new_head ){ tail.__combine_streams__(op, args.map(
|
56
|
+
new_head = op.(head, *args.map(&:head))
|
57
|
+
cons_stream( new_head ){ tail.__combine_streams__(op, args.map(&:tail)) }
|
60
58
|
end
|
61
59
|
|
62
60
|
private
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lab42_streams
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Dober
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-10-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: forwarder2
|
@@ -30,98 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0.
|
33
|
+
version: '0.4'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0.
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: pry
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0.9'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0.9'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: pry-nav
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0.2'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0.2'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rspec
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '2.14'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '2.14'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: qed
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - "~>"
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '2.9'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - "~>"
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '2.9'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: ae
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - "~>"
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '1.8'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - "~>"
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '1.8'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: byebug
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '3.1'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "~>"
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '3.1'
|
40
|
+
version: '0.4'
|
125
41
|
description: Lazy Evaluation, Streams, Enumerator#Lazy
|
126
42
|
email: robert.dober@gmail.com
|
127
43
|
executables: []
|
@@ -133,13 +49,12 @@ files:
|
|
133
49
|
- lib/lab42/stream.rb
|
134
50
|
- lib/lab42/stream/array.rb
|
135
51
|
- lib/lab42/stream/auto_import.rb
|
52
|
+
- lib/lab42/stream/behavior.rb
|
136
53
|
- lib/lab42/stream/class_methods.rb
|
137
54
|
- lib/lab42/stream/core/enumerable.rb
|
138
55
|
- lib/lab42/stream/delayed.rb
|
139
56
|
- lib/lab42/stream/empty.rb
|
140
|
-
- lib/lab42/stream/empty/enumerable.rb
|
141
57
|
- lib/lab42/stream/enumerable.rb
|
142
|
-
- lib/lab42/stream/hash.rb
|
143
58
|
- lib/lab42/stream/higher_order.rb
|
144
59
|
- lib/lab42/stream/kernel.rb
|
145
60
|
- lib/lab42/stream/kernel/extensions.rb
|
@@ -150,7 +65,7 @@ homepage: https://github.com/RobertDober/lab42_streams
|
|
150
65
|
licenses:
|
151
66
|
- MIT
|
152
67
|
metadata: {}
|
153
|
-
post_install_message:
|
68
|
+
post_install_message:
|
154
69
|
rdoc_options: []
|
155
70
|
require_paths:
|
156
71
|
- lib
|
@@ -158,16 +73,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
158
73
|
requirements:
|
159
74
|
- - ">="
|
160
75
|
- !ruby/object:Gem::Version
|
161
|
-
version:
|
76
|
+
version: 3.3.5
|
162
77
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
78
|
requirements:
|
164
79
|
- - ">="
|
165
80
|
- !ruby/object:Gem::Version
|
166
81
|
version: '0'
|
167
82
|
requirements: []
|
168
|
-
|
169
|
-
|
170
|
-
signing_key:
|
83
|
+
rubygems_version: 3.5.16
|
84
|
+
signing_key:
|
171
85
|
specification_version: 4
|
172
|
-
summary: Streams for Ruby
|
86
|
+
summary: Streams for Ruby 3.3.5
|
173
87
|
test_files: []
|
File without changes
|