enumerable_fu 0.1.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.
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