enumerable_fu 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+ gem "rspec", ">= 2"
7
+
8
+ gem "lazing"
9
+ gem "facets"
data/README.markdown ADDED
@@ -0,0 +1,50 @@
1
+ EnumerableFu
2
+ ============
3
+
4
+ "EnumerableFu" extends `Enumerable` with "lazy" versions of various operations,
5
+ allowing streamed processing of large (or even infinite) collections.
6
+
7
+ It also provides some interesting ways of combining Enumerables.
8
+
9
+ Filters
10
+ -------
11
+
12
+ `selecting` (cf. `select`, or `find_all`) returns each element for which the given block is true
13
+
14
+ (1..6).selecting { |x| x.even? } # generates: 2, 4, 6
15
+
16
+ `rejecting` (cf. `reject`) returns each element for which the given block is false:
17
+
18
+ (1..6).rejecting { |x| x.even? } # generates: 1, 3, 5
19
+
20
+ `collecting` (cf. `collect`, or `map`) applies a block to each element:
21
+
22
+ [1,2,3].collecting { |x| x*2 } # generates: 2, 4, 6
23
+
24
+ `uniqing` (cf. `uniq`) returns unique elements:
25
+
26
+ [2,1,2,3,2,1].uniqing # generates: 2, 1, 3
27
+
28
+ Unlike the original methods, the "...ing" variants each return an immutable Enumerable object, rather than an Array. The actual processing is deferred until the result Enumerable is enumerated (e.g. with `each`), and elements are produced "just in time".
29
+
30
+ Mixers
31
+ ------
32
+
33
+ `Enumerable.zipping` pulls elements from a number of Enumerables in parallel, yielding each group.
34
+
35
+ array1 = [1,3,6]
36
+ array2 = [2,4,7]
37
+ Enumerable.zipping(array1, array2) # generates: [1,2], [3,4], [6,7]
38
+
39
+ `Enumerable.merging` merges multiple Enumerables, preserving sort-order. The inputs are assumed to be sorted already.
40
+
41
+ array1 = [1,4,5]
42
+ array2 = [2,3,6]
43
+ Enumerable.merging(array1, array2) # generates: 1, 2, 3, 4, 5, 6
44
+
45
+ Variant `Enumerable.merging_by` uses a block to determine sort-order.
46
+
47
+ array1 = %w(a dd cccc)
48
+ array2 = %w(eee bbbbb)
49
+ Enumerable.merging_by(array1, array2) { |x| x.length }
50
+ # generates: %w(a dd eee cccc bbbbb)
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "rake"
2
+
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ require "rspec/core/rake_task"
7
+
8
+ RSpec::Core::RakeTask.new do |t|
9
+ t.pattern = 'spec/**/*_spec.rb'
10
+ t.rspec_opts = ["--colour", "--format", "nested"]
11
+ end
12
+
13
+ task "default" => "spec"
@@ -0,0 +1,71 @@
1
+ require 'benchmark'
2
+
3
+ $: << File.expand_path("../../lib", __FILE__)
4
+
5
+ RUBY19 = RUBY_VERSION =~ /^1\.9/
6
+
7
+ if RUBY19
8
+ require "lazing"
9
+ module Enumerable
10
+ alias :lazing_select :selecting
11
+ alias :lazing_collect :collecting
12
+ end
13
+ end
14
+
15
+ require "enumerable_fu"
16
+
17
+ require 'facets'
18
+
19
+ array = (1..100000).to_a
20
+
21
+ # Test scenario:
22
+ # - filter out even numbers
23
+ # - square them
24
+ # - grab the first thousand
25
+
26
+ printf "%-30s", "IMPLEMENTATION"
27
+ printf "%12s", "take(10)"
28
+ printf "%12s", "take(100)"
29
+ printf "%12s", "take(1000)"
30
+ printf "%12s", "to_a"
31
+ puts ""
32
+
33
+ def measure(&block)
34
+ begin
35
+ printf "%12.5f", Benchmark.realtime(&block)
36
+ rescue
37
+ printf "%12s", "n/a"
38
+ end
39
+ end
40
+
41
+ def benchmark(description, control_result = nil)
42
+ result = nil
43
+ printf "%-30s", description
44
+ measure { yield.take(10).to_a }
45
+ measure { yield.take(100).to_a }
46
+ measure { result = yield.take(1000).to_a }
47
+ measure { yield.to_a }
48
+ puts ""
49
+ unless control_result.nil? || result == control_result
50
+ raise "unexpected result from '#{description}'"
51
+ end
52
+ result
53
+ end
54
+
55
+ @control = benchmark "conventional" do
56
+ array.select { |x| x.even? }.collect { |x| x*x }
57
+ end
58
+
59
+ benchmark "enumerable_fu", @control do
60
+ array.selecting { |x| x.even? }.collecting { |x| x*x }
61
+ end
62
+
63
+ if RUBY19
64
+ benchmark "lazing", @control do
65
+ array.lazing_select { |x| x.even? }.lazing_collect { |x| x*x }
66
+ end
67
+ end
68
+
69
+ benchmark "facets Enumerable#defer", @control do
70
+ array.defer.select { |x| x.even? }.collect { |x| x*x }
71
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "enumerable_fu/version"
4
+
5
+ Gem::Specification.new do |s|
6
+
7
+ s.name = "enumerable_fu"
8
+ s.version = EnumerableFu::VERSION.dup
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Mike Williams"]
11
+ s.email = "mdub@dogbiscuit.org"
12
+ s.homepage = "http://github.com/mdub/enumerable_fu"
13
+
14
+ s.summary = %{Lazy extensions to Enumerable}
15
+ s.description = %{EnumerableFu extends Enumerable with "lazy" versions of various operations, allowing streamed processing of large (or even infinite) collections.}
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.require_paths = ["lib"]
20
+
21
+ end
@@ -0,0 +1,68 @@
1
+ require 'set'
2
+
3
+ module EnumerableFu
4
+
5
+ class Filter
6
+
7
+ include Enumerable
8
+
9
+ def initialize(&generator)
10
+ @generator = generator
11
+ end
12
+
13
+ def each
14
+ return to_enum unless block_given?
15
+ yielder = proc { |x| yield x }
16
+ @generator.call(yielder)
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ module Enumerable
24
+
25
+ def collecting
26
+ EnumerableFu::Filter.new do |output|
27
+ each do |element|
28
+ output.call yield(element)
29
+ end
30
+ end
31
+ end
32
+
33
+ def selecting
34
+ EnumerableFu::Filter.new do |output|
35
+ each do |element|
36
+ output.call(element) if yield(element)
37
+ end
38
+ end
39
+ end
40
+
41
+ def rejecting
42
+ EnumerableFu::Filter.new do |output|
43
+ each do |element|
44
+ output.call(element) unless yield(element)
45
+ end
46
+ end
47
+ end
48
+
49
+ def uniqing
50
+ EnumerableFu::Filter.new do |output|
51
+ seen = Set.new
52
+ each do |element|
53
+ output.call(element) if seen.add?(element)
54
+ end
55
+ end
56
+ end
57
+
58
+ def uniqing_by
59
+ EnumerableFu::Filter.new do |output|
60
+ seen = Set.new
61
+ each do |element|
62
+ output.call(element) if seen.add?(yield element)
63
+ end
64
+ end
65
+ end
66
+
67
+ end
68
+
@@ -0,0 +1,70 @@
1
+ module EnumerableFu
2
+
3
+ class Merger
4
+
5
+ include Enumerable
6
+
7
+ def initialize(enumerables, &transformer)
8
+ @enumerables = enumerables
9
+ @transformer = transformer
10
+ end
11
+
12
+ def each(&block)
13
+ return to_enum unless block_given?
14
+ Generator.new(@enumerables.map(&:to_enum), @transformer).each(&block)
15
+ end
16
+
17
+ class Generator
18
+
19
+ def initialize(enumerators, transformer)
20
+ @enumerators = enumerators
21
+ @transformer = transformer
22
+ end
23
+
24
+ def each
25
+ while true do
26
+ discard_empty_enumerators
27
+ break if @enumerators.empty?
28
+ yield next_enumerator.next
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def discard_empty_enumerators
35
+ @enumerators.delete_if do |e|
36
+ begin
37
+ e.peek
38
+ false
39
+ rescue StopIteration
40
+ true
41
+ end
42
+ end
43
+ end
44
+
45
+ def next_enumerator
46
+ @enumerators.min_by { |enumerator| transform(enumerator.peek) }
47
+ end
48
+
49
+ def transform(item)
50
+ return item unless @transformer
51
+ @transformer.call(item)
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ class << Enumerable
61
+
62
+ def merging(*enumerables)
63
+ EnumerableFu::Merger.new(enumerables)
64
+ end
65
+
66
+ def merging_by(*enumerables, &block)
67
+ EnumerableFu::Merger.new(enumerables, &block)
68
+ end
69
+
70
+ end
@@ -0,0 +1,2 @@
1
+ require 'enumerable_fu/merging'
2
+ require 'enumerable_fu/zipping'
@@ -0,0 +1,3 @@
1
+ module EnumerableFu
2
+ VERSION = "0.1.0".freeze
3
+ end
@@ -0,0 +1,36 @@
1
+ module EnumerableFu
2
+
3
+ class Zipper
4
+
5
+ include Enumerable
6
+
7
+ def initialize(enumerables)
8
+ @enumerables = enumerables
9
+ end
10
+
11
+ def each
12
+ enumerators = @enumerables.map(&:to_enum)
13
+ while true
14
+ chunk = enumerators.map do |enumerator|
15
+ begin
16
+ enumerator.next
17
+ rescue StopIteration
18
+ nil
19
+ end
20
+ end
21
+ break if chunk.all?(&:nil?)
22
+ yield chunk
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ class << Enumerable
31
+
32
+ def zipping(*enumerables)
33
+ EnumerableFu::Zipper.new(enumerables)
34
+ end
35
+
36
+ end
@@ -0,0 +1,2 @@
1
+ require 'enumerable_fu/filtering'
2
+ require 'enumerable_fu/mixing'
@@ -0,0 +1,62 @@
1
+ require "spec_helper"
2
+
3
+ describe Enumerable do
4
+
5
+ describe "#collecting" do
6
+
7
+ it "transforms items" do
8
+ [1,2,3].collecting { |x| x * 2 }.to_a.should == [2,4,6]
9
+ end
10
+
11
+ it "is lazy" do
12
+ [1,2,3].with_time_bomb.collecting { |x| x * 2 }.first.should == 2
13
+ end
14
+
15
+ end
16
+
17
+ describe "#selecting" do
18
+
19
+ it "excludes items that don't pass the predicate" do
20
+ (1..6).selecting { |x| x.even? }.to_a.should == [2,4,6]
21
+ end
22
+
23
+ it "is lazy" do
24
+ (1..6).with_time_bomb.selecting { |x| x.even? }.first == 2
25
+ end
26
+
27
+ end
28
+
29
+ describe "#rejecting" do
30
+
31
+ it "excludes items that do pass the predicate" do
32
+ (1..6).rejecting { |x| x.even? }.to_a.should == [1,3,5]
33
+ end
34
+
35
+ it "is lazy" do
36
+ (1..6).with_time_bomb.rejecting { |x| x.even? }.first == 1
37
+ end
38
+
39
+ end
40
+
41
+ describe "#uniqing" do
42
+
43
+ it "removes duplicates" do
44
+ [1,3,2,4,3,5,4,6].uniqing.to_a.should == [1,3,2,4,5,6]
45
+ end
46
+
47
+ it "is lazy" do
48
+ [1,2,3].with_time_bomb.uniqing.first.should == 1
49
+ end
50
+
51
+ end
52
+
53
+ describe "#uniqing_by" do
54
+
55
+ it "uses the block to derive identity" do
56
+ @array = %w(A1 A2 B1 A3 C1 B2 C2)
57
+ @array.uniqing_by { |s| s[0,1] }.to_a.should == %w(A1 B1 C1)
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+
3
+ describe Enumerable, :needs_enumerators => true do
4
+
5
+ describe ".merging" do
6
+
7
+ it "merges multiple Enumerators" do
8
+ @array1 = [1,3,6]
9
+ @array2 = [2,4,7]
10
+ @array3 = [5,8]
11
+ @merge = Enumerable.merging(@array1, @array2, @array3)
12
+ @merge.to_a.should == [1,2,3,4,5,6,7,8]
13
+ end
14
+
15
+ it "is lazy" do
16
+ @enum1 = [1,3]
17
+ @enum2 = [2,4].with_time_bomb
18
+ @merge = Enumerable.merging(@enum1, @enum2)
19
+ @merge.take(4).should == [1,2,3,4]
20
+ end
21
+
22
+ end
23
+
24
+ describe ".merging_by" do
25
+
26
+ it "uses the block to determine order" do
27
+ @array1 = %w(cccc dd a)
28
+ @array2 = %w(eeeee bbb)
29
+ @merge = Enumerable.merging_by(@array1, @array2) { |s| -s.length }
30
+ @merge.to_a.should == %w(eeeee cccc bbb dd a)
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe Enumerable, :needs_enumerators => true do
4
+
5
+ describe "#zipping" do
6
+
7
+ it "zips together multiple Enumerables" do
8
+ @array1 = [1,3,6]
9
+ @array2 = [2,4,7]
10
+ @array3 = [5,8]
11
+ @zip = Enumerable.zipping(@array1, @array2, @array3)
12
+ @zip.to_a.should == [[1,2,5], [3,4,8], [6,7,nil]]
13
+ end
14
+
15
+ it "is lazy" do
16
+ @zip = Enumerable.zipping(%w(a b c), [1,2].with_time_bomb)
17
+ @zip.take(2).should == [["a", 1], ["b", 2]]
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,37 @@
1
+ require "rubygems"
2
+
3
+ require 'rspec'
4
+
5
+ RSpec.configure do |config|
6
+ unless defined?(::Enumerator)
7
+ config.filter_run_excluding :needs_enumerators => true
8
+ end
9
+ end
10
+
11
+ class Boom < StandardError; end
12
+
13
+ class WithTimeBomb
14
+
15
+ include Enumerable
16
+
17
+ def initialize(source)
18
+ @source = source
19
+ end
20
+
21
+ def each(&block)
22
+ @source.each(&block)
23
+ raise Boom
24
+ end
25
+
26
+ end
27
+
28
+ module Enumerable
29
+
30
+ # extend an Enumerable to throw an exception after last element
31
+ def with_time_bomb
32
+ WithTimeBomb.new(self)
33
+ end
34
+
35
+ end
36
+
37
+ require "enumerable_fu"
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: enumerable_fu
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Mike Williams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-14 00:00:00 +10:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: EnumerableFu extends Enumerable with "lazy" versions of various operations, allowing streamed processing of large (or even infinite) collections.
18
+ email: mdub@dogbiscuit.org
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files: []
24
+
25
+ files:
26
+ - .gitignore
27
+ - Gemfile
28
+ - README.markdown
29
+ - Rakefile
30
+ - benchmarks/pipeline_bench.rb
31
+ - enumerable_fu.gemspec
32
+ - lib/enumerable_fu.rb
33
+ - lib/enumerable_fu/filtering.rb
34
+ - lib/enumerable_fu/merging.rb
35
+ - lib/enumerable_fu/mixing.rb
36
+ - lib/enumerable_fu/version.rb
37
+ - lib/enumerable_fu/zipping.rb
38
+ - spec/enumerable_fu/filtering_spec.rb
39
+ - spec/enumerable_fu/merging_spec.rb
40
+ - spec/enumerable_fu/zipping_spec.rb
41
+ - spec/spec_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/mdub/enumerable_fu
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.6.2
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Lazy extensions to Enumerable
70
+ test_files:
71
+ - spec/enumerable_fu/filtering_spec.rb
72
+ - spec/enumerable_fu/merging_spec.rb
73
+ - spec/enumerable_fu/zipping_spec.rb
74
+ - spec/spec_helper.rb