transducers 0.4.94

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
16
+ require 'transducers'
17
+ require 'benchmark'
18
+
19
+ class Inc
20
+ def process(n) n + 1 end
21
+ end
22
+
23
+ times = 100
24
+ size = 1000
25
+
26
+ e = 1.upto(size)
27
+ a = e.to_a
28
+
29
+ T = Transducers
30
+
31
+ Benchmark.benchmark do |bm|
32
+ {"enum" => e, "array" => a}.each do |label, coll|
33
+ puts "map with object (#{label})"
34
+ 3.times do
35
+ bm.report do
36
+ times.times do
37
+ T.transduce(T.map(Inc.new), :<<, [], coll)
38
+ end
39
+ end
40
+ end
41
+
42
+ puts "map with Symbol(#{label})"
43
+ 3.times do
44
+ bm.report do
45
+ times.times do
46
+ T.transduce(T.map(:succ), :<<, [], coll)
47
+ end
48
+ end
49
+ end
50
+
51
+ puts "map with block (#{label})"
52
+ 3.times do
53
+ bm.report do
54
+ times.times do
55
+ T.transduce(T.map {|n| n + 1}, :<<, [], coll)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,158 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
16
+ require 'transducers'
17
+ require 'benchmark'
18
+
19
+ class Inc
20
+ def call(n) n + 1 end
21
+ end
22
+
23
+ class Even
24
+ def call(n) n.even? end
25
+ end
26
+
27
+ map_inc = Transducers.map(Inc.new)
28
+ filter_even = Transducers.filter(Even.new)
29
+ map_inc_filter_even = Transducers.compose(map_inc, filter_even)
30
+ e = 1.upto(1000)
31
+ a = e.to_a
32
+
33
+ times = 100
34
+
35
+ T = Transducers
36
+
37
+ Benchmark.benchmark do |bm|
38
+ {"enum" => e, "array" => a}.each do |label, coll|
39
+ puts "select (#{label})"
40
+
41
+ 3.times do
42
+ bm.report do
43
+ times.times do
44
+ coll.select {|n| n.even?}
45
+ end
46
+ end
47
+ end
48
+
49
+ puts "filter (#{label})"
50
+ 3.times do
51
+ bm.report do
52
+ times.times do
53
+ T.transduce(filter_even, :<<, [], coll)
54
+ end
55
+ end
56
+ end
57
+
58
+ puts
59
+
60
+ puts "map (#{label})"
61
+ 3.times do
62
+ bm.report do
63
+ times.times do
64
+ coll.map {|n| n + 1}
65
+ end
66
+ end
67
+ end
68
+
69
+ puts "map (#{label})"
70
+ 3.times do
71
+ bm.report do
72
+ times.times do
73
+ T.transduce(map_inc, :<<, [], coll)
74
+ end
75
+ end
76
+ end
77
+
78
+ puts
79
+
80
+ puts "map + select (#{label})"
81
+ 3.times do
82
+ bm.report do
83
+ times.times do
84
+ coll.
85
+ map {|n| n + 1}.
86
+ select {|n| n.even?}
87
+ end
88
+ end
89
+ end
90
+
91
+ puts "map + filter (#{label})"
92
+ 3.times do
93
+ bm.report do
94
+ times.times do
95
+ T.transduce(map_inc_filter_even, :<<, [], coll)
96
+ end
97
+ end
98
+ end
99
+
100
+ puts
101
+ end
102
+ end
103
+
104
+ __END__
105
+
106
+ select (enum)
107
+ 0.010000 0.000000 0.010000 ( 0.008299)
108
+ 0.010000 0.000000 0.010000 ( 0.007843)
109
+ 0.000000 0.000000 0.000000 ( 0.007821)
110
+ filter (enum)
111
+ 0.020000 0.000000 0.020000 ( 0.017747)
112
+ 0.020000 0.000000 0.020000 ( 0.019915)
113
+ 0.020000 0.000000 0.020000 ( 0.019416)
114
+
115
+ map (enum)
116
+ 0.010000 0.000000 0.010000 ( 0.008053)
117
+ 0.010000 0.000000 0.010000 ( 0.007952)
118
+ 0.010000 0.000000 0.010000 ( 0.009550)
119
+ map (enum)
120
+ 0.020000 0.000000 0.020000 ( 0.021479)
121
+ 0.020000 0.000000 0.020000 ( 0.020805)
122
+ 0.020000 0.000000 0.020000 ( 0.023205)
123
+
124
+ map + select (enum)
125
+ 0.010000 0.000000 0.010000 ( 0.015156)
126
+ 0.020000 0.000000 0.020000 ( 0.016545)
127
+ 0.010000 0.010000 0.020000 ( 0.019388)
128
+ map + filter (enum)
129
+ 0.030000 0.000000 0.030000 ( 0.026530)
130
+ 0.030000 0.000000 0.030000 ( 0.025563)
131
+ 0.020000 0.000000 0.020000 ( 0.027893)
132
+
133
+ select (array)
134
+ 0.010000 0.000000 0.010000 ( 0.006520)
135
+ 0.010000 0.000000 0.010000 ( 0.006570)
136
+ 0.000000 0.000000 0.000000 ( 0.007032)
137
+ filter (array)
138
+ 0.020000 0.000000 0.020000 ( 0.022639)
139
+ 0.030000 0.000000 0.030000 ( 0.023813)
140
+ 0.020000 0.000000 0.020000 ( 0.022440)
141
+
142
+ map (array)
143
+ 0.010000 0.000000 0.010000 ( 0.005880)
144
+ 0.000000 0.000000 0.000000 ( 0.005613)
145
+ 0.010000 0.000000 0.010000 ( 0.005294)
146
+ map (array)
147
+ 0.020000 0.000000 0.020000 ( 0.024443)
148
+ 0.030000 0.000000 0.030000 ( 0.023856)
149
+ 0.020000 0.000000 0.020000 ( 0.024172)
150
+
151
+ map + select (array)
152
+ 0.010000 0.000000 0.010000 ( 0.012158)
153
+ 0.010000 0.000000 0.010000 ( 0.012061)
154
+ 0.020000 0.000000 0.020000 ( 0.014131)
155
+ map + filter (array)
156
+ 0.020000 0.000000 0.020000 ( 0.026898)
157
+ 0.030000 0.000000 0.030000 ( 0.025745)
158
+ 0.030000 0.000000 0.030000 ( 0.028196)
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env sh
2
+ # Copyright (c) Cognitect, Inc.
3
+ # All rights reserved.
4
+
5
+ for r in 1.9.3-p547 2.0.0-p353 2.1.0 2.1.1 2.1.2 jruby-1.7.13 jruby-1.7.14 jruby-1.7.15 jruby-1.7.16
6
+ do
7
+ eval "rbenv local $r"
8
+ echo `ruby -v`
9
+ rspec
10
+ done
11
+
12
+ rm .ruby-version
data/build/revision ADDED
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+
3
+ # Returns the revision number used for deployment.
4
+
5
+ set -e
6
+
7
+ REVISION=`git --no-replace-objects describe --tags --match v0.0`
8
+
9
+ # Extract the version number from the string. Do this in two steps so
10
+ # it is a little easier to understand.
11
+ REVISION=${REVISION:5} # drop the first 5 characters
12
+ REVISION=${REVISION:0:${#REVISION}-9} # drop the last 9 characters
13
+
14
+ echo $REVISION
data/dev/irb_tools.rb ADDED
@@ -0,0 +1,36 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'stringio'
16
+
17
+ def time
18
+ start = Time.now
19
+ yield
20
+ puts "Elapsed: #{Time.now - start}"
21
+ end
22
+
23
+ class Object
24
+ def to_transit(format=:json)
25
+ sio = StringIO.new
26
+ Transit::Writer.new(format, sio).write(self)
27
+ sio.string
28
+ end
29
+ end
30
+
31
+ class String
32
+ def from_transit(format=:json)
33
+ sio = StringIO.new(self)
34
+ Transit::Reader.new(format, sio).read
35
+ end
36
+ end
@@ -0,0 +1,615 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Transducers are composable algorithmic transformations. See
16
+ # http://clojure.org/transducers before reading on.
17
+ #
18
+ # ## Terminology
19
+ #
20
+ # We need to expand the terminology a bit in order to map the concepts
21
+ # described on http://clojure.org/transducers to an OO language like
22
+ # Ruby.
23
+ #
24
+ # A _reducer_ is an object with a `step` method that takes a result
25
+ # (so far) and an input and returns a new result. This is similar to
26
+ # the blocks we pass to Ruby's `reduce` (a.k.a `inject`), and serves a
27
+ # similar role in _transducing process_.
28
+ #
29
+ # A _handler_ is an object with a `call` method that a reducer uses
30
+ # to process input. In a `map` operation, this would transform the
31
+ # input, and in a `filter` operation it would act as a predicate.
32
+ #
33
+ # A _transducer_ is an object that transforms a reducer by adding
34
+ # additional processing for each element in a collection of inputs.
35
+ #
36
+ # A _transducing process_ is invoked by calling
37
+ # `Transducers.transduce` with a transducer, a reducer, an optional
38
+ # initial value, and an input collection.
39
+ #
40
+ # Because Ruby doesn't come with free-floating handlers (e.g. Clojure's
41
+ # `inc` function) or reducing functions (e.g. Clojure's `conj`), we have
42
+ # to build these things ourselves.
43
+ #
44
+ # ## Examples
45
+ #
46
+ # ```ruby
47
+ # # handler
48
+ # inc = Class.new do
49
+ # def call(input) input += 1 end
50
+ # end.new
51
+ #
52
+ # # reducer
53
+ # appender = Class.new do
54
+ # def step(result, input) result << input end
55
+ # end.new
56
+ #
57
+ # # transducing process
58
+ # Transducers.transduce(Transducers.map(inc), appender, [], 0..9)
59
+ # #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
60
+ # ```
61
+ #
62
+ # You can pass a `Symbol` or a `Block` to transducer constructors
63
+ # (`Transducers.map` in this example), so the above can be achieved
64
+ # more easily e.g.
65
+ #
66
+ # ```
67
+ # Transducers.transduce(Transducers.map(:succ), appender, [], 0..9)
68
+ # #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
69
+ # Transducers.transduce(Transducers.map {|n|n+1}, appender, [], 0..9)
70
+ # #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
71
+ # ```
72
+ #
73
+ # You can omit the initial value if the reducer (`appender` in this
74
+ # example) provides one:
75
+ #
76
+ # ```
77
+ # def appender.init() [] end
78
+ # Transducers.transduce(Transducers.map {|n|n+1}, appender, 0..9)
79
+ # #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
80
+ # ```
81
+ #
82
+ # You can also just pass a `Symbol` and an initial value instead of a
83
+ # reducer object, and the `transduce` method will build one for you.
84
+ #
85
+ # ```
86
+ # Transducers.transduce(Transducers.map {|n|n+1}, :<<, [], 0..9)
87
+ # #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
88
+ # ```
89
+ #
90
+ # ## Composition
91
+ #
92
+ # Imagine that you want to take a range of numbers, select all the even
93
+ # ones, double them, and then take the first 5. Here's one way to
94
+ # do that in Ruby:
95
+ #
96
+ # ```ruby
97
+ # (1..100).
98
+ # select {|n| n.even?}.
99
+ # map {|n| n * 2}.
100
+ # take(5)
101
+ # #=> [4, 8, 12, 16, 20]
102
+ # ```
103
+ #
104
+ # Here's the same process with transducers:
105
+ #
106
+ # ```ruby
107
+ # t = Transducers.compose(
108
+ # Transducers.filter(:even?),
109
+ # Transducers.map {|n| n * 2},
110
+ # Transducers.take(5))
111
+ # Transducers.transduce(t, :<<, [], 1..100)
112
+ # #=> [4, 8, 12, 16, 20]
113
+ # ```
114
+ #
115
+ # Now that we've defined the transducer as a series of
116
+ # transformations, we can apply it to different contexts, e.g.
117
+ #
118
+ # ```ruby
119
+ # Transducers.transduce(t, :+, 0, 1..100)
120
+ # #=> 60
121
+ # Transducers.transduce(t, :*, 1, 1..100)
122
+ # #=> 122880
123
+ # ```
124
+ module Transducers
125
+ class Reducer
126
+ def initialize(init, sym=nil, &block)
127
+ raise ArgumentError.new("No init provided") if init == :no_init_provided
128
+ @init = init
129
+ if sym
130
+ @sym = sym
131
+ (class << self; self; end).class_eval do
132
+ def step(result, input)
133
+ result.send(@sym, input)
134
+ end
135
+ end
136
+ else
137
+ @block = block
138
+ (class << self; self; end).class_eval do
139
+ def step(result, input)
140
+ @block.call(result, input)
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ def init() @init end
147
+ def complete(result) result end
148
+ def step(result, input)
149
+ # placeholder for docs - overwritten in initalize
150
+ end
151
+ end
152
+
153
+ # @api private
154
+ class Reduced
155
+ attr_reader :val
156
+
157
+ def initialize(val)
158
+ @val = val
159
+ end
160
+ end
161
+
162
+ # @api private
163
+ class PreservingReduced
164
+ def apply(reducer)
165
+ @reducer = reducer
166
+ end
167
+
168
+ def step(result, input)
169
+ ret = @reducer.step(result, input)
170
+ Reduced === ret ? Reduced.new(ret) : ret
171
+ end
172
+ end
173
+
174
+ class WrappingReducer
175
+ class MethodHandler
176
+ def initialize(method)
177
+ @method = method
178
+ end
179
+
180
+ def call(input)
181
+ input.send @method
182
+ end
183
+ end
184
+
185
+ def initialize(reducer, handler=nil, &block)
186
+ @reducer = reducer
187
+ @handler = if block
188
+ block
189
+ elsif Symbol === handler
190
+ MethodHandler.new(handler)
191
+ else
192
+ handler
193
+ end
194
+ end
195
+
196
+ def init()
197
+ @reducer.init
198
+ end
199
+
200
+ def complete(result)
201
+ @reducer.complete(result)
202
+ end
203
+ end
204
+
205
+ # @api private
206
+ class BaseTransducer
207
+ class << self
208
+ attr_reader :reducer_class
209
+
210
+ def define_reducer_class(&block)
211
+ @reducer_class = Class.new(WrappingReducer)
212
+ @reducer_class.class_eval(&block)
213
+ end
214
+ end
215
+
216
+ def initialize(handler, &block)
217
+ @handler = handler
218
+ @block = block
219
+ end
220
+
221
+ def reducer_class
222
+ self.class.reducer_class
223
+ end
224
+ end
225
+
226
+ class << self
227
+ # @overload transduce(transducer, reducer, coll)
228
+ # @overload transduce(transducer, reducer, init, coll)
229
+ # @param [Transducer] transducer
230
+ # @param [Reducer, Symbol, Bock] reducer
231
+ def transduce(transducer, reducer, init=:no_init_provided, coll)
232
+ reducer = Reducer.new(init, reducer) unless reducer.respond_to?(:step)
233
+ reducer = transducer.apply(reducer)
234
+ result = init == :no_init_provided ? reducer.init : init
235
+ case coll
236
+ when Enumerable
237
+ coll.each do |input|
238
+ result = reducer.step(result, input)
239
+ return result.val if Transducers::Reduced === result
240
+ result
241
+ end
242
+ when String
243
+ coll.each_char do |input|
244
+ result = reducer.step(result, input)
245
+ return result.val if Transducers::Reduced === result
246
+ result
247
+ end
248
+ end
249
+ reducer.complete(result)
250
+ end
251
+
252
+ def self.define_transducer_class(name, &block)
253
+ t = Class.new(BaseTransducer)
254
+ t.class_eval(&block)
255
+ unless t.instance_methods.include? :apply
256
+ t.class_eval do
257
+ define_method :apply do |reducer|
258
+ reducer_class.new(reducer, @handler, &@block)
259
+ end
260
+ end
261
+ end
262
+
263
+ Transducers.send(:define_method, name) do |handler=nil, &b|
264
+ t.new(handler, &b)
265
+ end
266
+
267
+ Transducers.send(:module_function, name)
268
+ end
269
+
270
+ # @macro [new] common_transducer
271
+ # @return [Transducer]
272
+ # @method $1(handler=nil, &block)
273
+ # @param [Object, Symbol] handler
274
+ # Given an object that responds to +process+, uses it as the
275
+ # handler. Given a +Symbol+, builds a handler whose +process+
276
+ # method will send +Symbol+ to its argument.
277
+ # @param [Block] block <i>(optional)</i>
278
+ # Given a +Block+, builds a handler whose +process+ method will
279
+ # call the block with its argument(s).
280
+ # Returns a transducer that adds a map transformation to the
281
+ # reducer stack.
282
+ define_transducer_class :map do
283
+ define_reducer_class do
284
+ # Can I doc this?
285
+ def step(result, input)
286
+ @reducer.step(result, @handler.call(input))
287
+ end
288
+ end
289
+ end
290
+
291
+ # @macro common_transducer
292
+ define_transducer_class :filter do
293
+ define_reducer_class do
294
+ def step(result, input)
295
+ @handler.call(input) ? @reducer.step(result, input) : result
296
+ end
297
+ end
298
+ end
299
+
300
+ # @macro common_transducer
301
+ define_transducer_class :remove do
302
+ define_reducer_class do
303
+ def step(result, input)
304
+ @handler.call(input) ? result : @reducer.step(result, input)
305
+ end
306
+ end
307
+ end
308
+
309
+ # @method take(n)
310
+ # @return [Transducer]
311
+ define_transducer_class :take do
312
+ define_reducer_class do
313
+ def initialize(reducer, n)
314
+ super(reducer)
315
+ @n = n
316
+ end
317
+
318
+ def step(result, input)
319
+ @n -= 1
320
+ ret = @reducer.step(result, input)
321
+ @n > 0 ? ret : Reduced.new(ret)
322
+ end
323
+ end
324
+
325
+ def initialize(n)
326
+ @n = n
327
+ end
328
+
329
+ def apply(reducer)
330
+ reducer_class.new(reducer, @n)
331
+ end
332
+ end
333
+
334
+ # @macro common_transducer
335
+ define_transducer_class :take_while do
336
+ define_reducer_class do
337
+ def step(result, input)
338
+ @handler.call(input) ? @reducer.step(result, input) : Reduced.new(result)
339
+ end
340
+ end
341
+ end
342
+
343
+ # @method take_nth(n)
344
+ # @return [Transducer]
345
+ define_transducer_class :take_nth do
346
+ define_reducer_class do
347
+ def initialize(reducer, n)
348
+ super(reducer)
349
+ @n = n
350
+ @count = 0
351
+ end
352
+
353
+ def step(result, input)
354
+ @count += 1
355
+ if @count % @n == 0
356
+ @reducer.step(result, input)
357
+ else
358
+ result
359
+ end
360
+ end
361
+ end
362
+
363
+ def initialize(n)
364
+ @n = n
365
+ end
366
+
367
+ def apply(reducer)
368
+ reducer_class.new(reducer, @n)
369
+ end
370
+ end
371
+
372
+ # @method replace(source_map)
373
+ # @return [Transducer]
374
+ define_transducer_class :replace do
375
+ define_reducer_class do
376
+ def initialize(reducer, smap)
377
+ super(reducer)
378
+ @smap = smap
379
+ end
380
+
381
+ def step(result, input)
382
+ if @smap.has_key?(input)
383
+ @reducer.step(result, @smap[input])
384
+ else
385
+ @reducer.step(result, input)
386
+ end
387
+ end
388
+ end
389
+
390
+ def initialize(smap)
391
+ @smap = case smap
392
+ when Hash
393
+ smap
394
+ else
395
+ (0...smap.size).zip(smap).to_h
396
+ end
397
+ end
398
+
399
+ def apply(reducer)
400
+ reducer_class.new(reducer, @smap)
401
+ end
402
+ end
403
+
404
+ # @macro common_transducer
405
+ define_transducer_class :keep do
406
+ define_reducer_class do
407
+ def step(result, input)
408
+ x = @handler.call(input)
409
+ x.nil? ? result : @reducer.step(result, x)
410
+ end
411
+ end
412
+ end
413
+
414
+ # @macro common_transducer
415
+ # @note the handler for this method requires two arguments: the
416
+ # index and the input.
417
+ define_transducer_class :keep_indexed do
418
+ define_reducer_class do
419
+ def initialize(*)
420
+ super
421
+ @index = -1
422
+ end
423
+
424
+ def step(result, input)
425
+ @index += 1
426
+ x = @handler.call(@index, input)
427
+ x.nil? ? result : @reducer.step(result, x)
428
+ end
429
+ end
430
+ end
431
+
432
+ # @method drop(n)
433
+ # @return [Transducer]
434
+ define_transducer_class :drop do
435
+ define_reducer_class do
436
+ def initialize(reducer, n)
437
+ super(reducer)
438
+ @n = n
439
+ end
440
+
441
+ def step(result, input)
442
+ @n -= 1
443
+ @n <= -1 ? @reducer.step(result, input) : result
444
+ end
445
+ end
446
+
447
+ def initialize(n)
448
+ @n = n
449
+ end
450
+
451
+ def apply(reducer)
452
+ reducer_class.new(reducer, @n)
453
+ end
454
+ end
455
+
456
+ # @macro common_transducer
457
+ define_transducer_class :drop_while do
458
+ define_reducer_class do
459
+ def initalize(*)
460
+ super
461
+ @done_dropping = false
462
+ end
463
+
464
+ def step(result, input)
465
+ @done_dropping ||= !@handler.call(input)
466
+ @done_dropping ? @reducer.step(result, input) : result
467
+ end
468
+ end
469
+ end
470
+
471
+ # @method dedupe
472
+ # @return [Transducer]
473
+ define_transducer_class :dedupe do
474
+ define_reducer_class do
475
+ def initialize(*)
476
+ super
477
+ @prior = :no_value_provided_for_transducer
478
+ end
479
+
480
+ def step(result, input)
481
+ ret = input == @prior ? result : @reducer.step(result, input)
482
+ @prior = input
483
+ ret
484
+ end
485
+ end
486
+ end
487
+
488
+ # @method partition_by
489
+ # @return [Transducer]
490
+ define_transducer_class :partition_by do
491
+ define_reducer_class do
492
+ def initialize(*)
493
+ super
494
+ @a = []
495
+ @prev_val = :no_value_provided_for_transducer
496
+ end
497
+
498
+ def complete(result)
499
+ result = if @a.empty?
500
+ result
501
+ else
502
+ a = @a.dup
503
+ @a.clear
504
+ @reducer.step(result, a)
505
+ end
506
+ @reducer.complete(result)
507
+ end
508
+
509
+ def step(result, input)
510
+ prev_val = @prev_val
511
+ val = @handler.call(input)
512
+ @prev_val = val
513
+ if val == prev_val || prev_val == :no_value_provided_for_transducer
514
+ @a << input
515
+ result
516
+ else
517
+ a = @a.dup
518
+ @a.clear
519
+ ret = @reducer.step(result, a)
520
+ @a << input unless (Reduced === ret)
521
+ ret
522
+ end
523
+ end
524
+ end
525
+ end
526
+
527
+ # @method partition_all
528
+ # @return [Transducer]
529
+ define_transducer_class :partition_all do
530
+ define_reducer_class do
531
+ def initialize(reducer, n)
532
+ super(reducer)
533
+ @n = n
534
+ @a = []
535
+ end
536
+
537
+ def step(result, input)
538
+ @a << input
539
+ if @a.size == @n
540
+ a = @a.dup
541
+ @a.clear
542
+ @reducer.step(result, a)
543
+ else
544
+ result
545
+ end
546
+ end
547
+
548
+ def complete(result)
549
+ if @a.empty?
550
+ result
551
+ else
552
+ a = @a.dup
553
+ @a.clear
554
+ @reducer.step(result, a)
555
+ end
556
+ end
557
+ end
558
+
559
+ def initialize(n)
560
+ @n = n
561
+ end
562
+
563
+ def apply(reducer)
564
+ reducer_class.new(reducer, @n)
565
+ end
566
+ end
567
+
568
+ # @api private
569
+ class RandomSampleHandler
570
+ def initialize(prob)
571
+ @prob = prob
572
+ end
573
+
574
+ def call(_)
575
+ @prob > Random.rand
576
+ end
577
+ end
578
+
579
+ # @return [Transducer]
580
+ def random_sample(prob)
581
+ filter RandomSampleHandler.new(prob)
582
+ end
583
+
584
+ # @method cat
585
+ # @return [Transducer]
586
+ define_transducer_class :cat do
587
+ define_reducer_class do
588
+ def step(result, input)
589
+ Transducers.transduce(PreservingReduced.new, @reducer, result, input)
590
+ end
591
+ end
592
+ end
593
+
594
+ # @api private
595
+ class ComposedTransducer
596
+ def initialize(*transducers)
597
+ @transducers = transducers
598
+ end
599
+
600
+ def apply(reducer)
601
+ @transducers.reverse.reduce(reducer) {|r,t| t.apply(r)}
602
+ end
603
+ end
604
+
605
+ # @return [Transducer]
606
+ def compose(*transducers)
607
+ ComposedTransducer.new(*transducers)
608
+ end
609
+
610
+ # @return [Transducer]
611
+ def mapcat(handler=nil, &block)
612
+ compose(map(handler, &block), cat)
613
+ end
614
+ end
615
+ end