transducers 0.4.94

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.
@@ -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