LazyEnumerable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2008 Blaine Buxton
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,20 @@
1
+ ### Comment Unit Tests
2
+ ### Author:: Blaine Buxton (mailto:altodorado@blainebuxton.com)
3
+ ### Copyright:: Copyright (c) 2008
4
+ ### License:: See LICENSE
5
+ ###
6
+ ### It takes a functional approach to the common Enumerable protocol of select, collect, and reject.
7
+ ### By functional, I mean the collection is not changed nor is a new modified one created.
8
+ ### The blocks are kept around until they are absolutely needed.
9
+ ### I have been wanting this functionality for some time because it's nice for large collections.
10
+ ### If you have a collection in which you are calling a lot selects, rejects, or collects on,
11
+ ### then this will not create the intermediate collections.
12
+ ### It will wait until you ask something of the collection where it can not delay the answer.
13
+ ### This should make these chained operations must faster on large collections.
14
+ ###
15
+ ### This was a lot of fun to program and it's not that big. Take whatever you want from it!
16
+ ###
17
+ ### Read the tests to see how it works!
18
+ ### If you learn anything from this please feel to send me an email!
19
+ ###
20
+ ### Check out the tests for examples.
@@ -0,0 +1,14 @@
1
+ module Lazy
2
+
3
+ class CollectEnumerable < LazyEnumerable
4
+ define_for(:lazy_collect)
5
+
6
+ def each(&each_block)
7
+ @original.each do |every|
8
+ each_block.call(@block.call(every))
9
+ end
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,28 @@
1
+ ###
2
+ ### Hack to get the current method off the stack. I hate this.
3
+ ### Why can't I reflect on the stack and get the real method
4
+ ### invocation? Grrr...
5
+ ###
6
+ module Kernel
7
+
8
+ private
9
+ def current_method
10
+ (caller[0] =~ /`([^']*)'/ and $1).to_sym
11
+ end
12
+
13
+ end
14
+
15
+ ###
16
+ ### Conversion methods
17
+ ###
18
+ module Enumerable
19
+
20
+ def to_lazy
21
+ Lazy::LazyEnumerable.new(self)
22
+ end
23
+
24
+ def to_real
25
+ self
26
+ end
27
+
28
+ end
@@ -0,0 +1,63 @@
1
+ ###
2
+ ### LazyEnumerable makes the implementation of higher order methods easier. LazyIterator
3
+ ### is a simple implemetation of that. It basically works by "stacking" up the method
4
+ ### calls until the collection needs to be calculated (realized). It then calls the
5
+ ### "stacked up" methods.
6
+ ###
7
+ ### LazyEnumerable is immutable and I would like LazyIterator to be as well. But,
8
+ ### every attempt has made the code ugly and inelegant. I'm still looking for a good
9
+ ### solution. Immuntability gives the ability to calculate on different levels.
10
+ ###
11
+ module Lazy
12
+
13
+ class Iterator < LazyEnumerable
14
+
15
+ def initialize(proc)
16
+ @internal=proc
17
+ @sends=[]
18
+ end
19
+
20
+ ##
21
+ ## Simply make a closure around the method and capturing it's arguments as well
22
+ ## This is so it can be replayed. Notice again, I cache the method with a define_method
23
+ ##
24
+ def method_missing(method_name, *arguments)
25
+ proc=lambda do |*args|
26
+ send=lambda {|each| each.send(method_name, *args)}
27
+ @sends << send
28
+ self
29
+ end
30
+ self._class.send(:define_method, method_name, &proc)
31
+ proc.call(*arguments)
32
+ end
33
+
34
+ def respond_to?(symbol,include_private = false)
35
+ true
36
+ end
37
+
38
+ ##
39
+ ## Do a placebo collect to get it to create an array with the elements
40
+ ##
41
+ def to_real
42
+ collect { |each| each }
43
+ end
44
+
45
+ ##
46
+ ## Call each member in the original collection and call each "captured" method
47
+ ## in succession. Notice the use of inject to make this really elegant and succint.
48
+ ## I do cheat a little and realize the collection and then call each. I want
49
+ ## to change this in the future.
50
+ ##
51
+ def each(&block)
52
+ method_missing(:each, &block) if (block.nil?)
53
+ answer=@internal.call do |each|
54
+ result=@sends.inject(each) do |result, each_send |
55
+ each_send.call(result)
56
+ end
57
+ end
58
+ answer.each(&block)
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,123 @@
1
+ ### It takes a functional approach to the common Enumerable protocol of select, collect, and reject.
2
+ ### By functional, I mean the collection is not changed nor is a new modified one created.
3
+ ### The blocks are kept around until they are absolutely needed.
4
+ ### I have been wanting this functionality for some time because it's nice for large collections.
5
+ ### If you have a collection in which you are calling a lot selects, rejects, or collects on,
6
+ ### then this will not create the intermediate collections.
7
+ ### It will wait until you ask something of the collection where it can not delay the answer.
8
+ ### This should make these chained operations must faster on large collections.
9
+ ###
10
+ ### This was a lot of fun to program and it's not that big. Take whatever you want from it!
11
+
12
+ module Lazy
13
+
14
+ class LazyEnumerable
15
+ ##
16
+ ## Remove any unnecessary methods so that method_missing is invoked
17
+ ##
18
+ def self.wack_all_my_methods
19
+ to_wack=instance_methods.reject do |each|
20
+ ['===','method_missing'].include?(each) || each =~ /^__/
21
+ end
22
+ to_wack.each do |each|
23
+ alias_method(("_" << each), each)
24
+ undef_method(each)
25
+ end
26
+ end
27
+
28
+ ##
29
+ ## Wack all of my methods, then apply Enumerable and define my constants
30
+ ## and class variable for cached method calls
31
+ ##
32
+ wack_all_my_methods
33
+ include Enumerable
34
+ PLACEBO=lambda {|each| each}
35
+ @@cached={}
36
+
37
+ ##
38
+ ## Meta-programming to provide a shorthand for extensions to let themselves
39
+ ## known so that they can be found on the method_missing.
40
+ ##
41
+ def self.define_for(method_name)
42
+ @@cached[method_name]=self
43
+ end
44
+
45
+ ##
46
+ ## Lookup a subclass for the particular method. Basically, this is so that each
47
+ ## template will provide its own functionality for each
48
+ ##
49
+ def self.class_for(method_name)
50
+ @@cached[method_name]
51
+ end
52
+
53
+ def self.iterator_creator(*method_names)
54
+ method_names.each { |every| iterator_creator_for(every) }
55
+ end
56
+
57
+ def self.iterator_creator_for(method_name)
58
+ non_lazy_name = /lazy_(.+)/.match(method_name.to_s)[1].to_sym
59
+ define_method(method_name) { Iterator.new(_method(non_lazy_name)) }
60
+ end
61
+
62
+ def initialize(original, block=PLACEBO)
63
+ @original=original
64
+ @block=block
65
+ _freeze
66
+ end
67
+
68
+ ##
69
+ ## This is basically the default implementation and should be overriden in
70
+ ## subclasses. It's because we can create a basic LazyEnumerable (on call to_lazy)
71
+ ## that simply is a placebo implementation to keep things simple
72
+ ##
73
+
74
+ def each(&block)
75
+ @original.each(&block)
76
+ end
77
+
78
+ ##
79
+ ## Look up the implementation of the iterator method (for example, :select, :collect:, etc)
80
+ ## I do define the missing method on this call. The reason is for ease of debugging and
81
+ ## speed. We don't need to go through this logic. It's on the fly code generation without
82
+ ## eval
83
+ ##
84
+ def method_missing(method_name, *arguments)
85
+ my_class=self._class.class_for(method_name)
86
+ if (my_class.nil?)
87
+ super.method_missing(method_name, *arguments)
88
+ end
89
+
90
+ proc = lambda {|*args| my_class.new(self, *args)}
91
+ self._class.send(:define_method, method_name, &proc)
92
+ proc.call(*arguments)
93
+ end
94
+
95
+ def respond_to?(symbol,include_private = false)
96
+ my_class=self._class.class_for(method_name)
97
+ !my_class.nil?
98
+ end
99
+
100
+ ##
101
+ ## Give me the size. This can be dangerous if you ever wrap an infinite enumerable
102
+ ##
103
+ def size
104
+ inject(0) {|sum,each| sum + 1 }
105
+ end
106
+
107
+ def to_lazy
108
+ self
109
+ end
110
+
111
+ def to_real
112
+ _collect(PLACEBO)
113
+ end
114
+
115
+ ##
116
+ ## Create the lazy iterators when no block is passed in the methods
117
+ ## below
118
+ ##
119
+ iterator_creator :lazy_select, :lazy_collect, :lazy_reject, :lazy_detect, :lazy_each
120
+
121
+ end
122
+
123
+ end
@@ -0,0 +1,14 @@
1
+ module Lazy
2
+
3
+ class RejectEnumerable < LazyEnumerable
4
+ define_for(:lazy_reject)
5
+
6
+ def each(&each_block)
7
+ @original.each do |every|
8
+ each_block.call(every) unless @block.call(every)
9
+ end
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,14 @@
1
+ module Lazy
2
+
3
+ class SelectEnumerable < LazyEnumerable
4
+ define_for(:lazy_select)
5
+
6
+ def each(&each_block)
7
+ @original.each do |every|
8
+ each_block.call(every) if @block.call(every)
9
+ end
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,6 @@
1
+ require 'lazy/extensions'
2
+ require 'lazy/lazy_enumerable'
3
+ require 'lazy/collect_enumerable'
4
+ require 'lazy/reject_enumerable'
5
+ require 'lazy/select_enumerable'
6
+ require 'lazy/iterator'
@@ -0,0 +1,38 @@
1
+ require 'test/unit'
2
+ class LazyEnumerableTest < Test::Unit::TestCase
3
+
4
+ def test_lazy
5
+ original=(1..100)
6
+ modified=original.to_lazy.select { |each| each % 2 == 0 }
7
+ modified_even=modified
8
+ modified=modified.reject { |each| (each % 4).zero? }
9
+ modified=modified.collect { |each| each.to_s }
10
+ modified=modified.select { |each| each.size == 1 }
11
+ #force, the collection to be realized
12
+ realized_collection=modified.to_real
13
+ assert_equal(['2', '6'], realized_collection)
14
+ assert_equal(50,modified_even.size)
15
+ assert(modified_even.to_real.class != Lazy::LazyEnumerable)
16
+ end
17
+
18
+ def test_iterator
19
+ original=(1..100)
20
+ modified=original.to_lazy.lazy_select.even
21
+ modified_even=modified
22
+ modified=(modified.lazy_reject % 4).zero?
23
+ modified=modified.lazy_collect.to_s
24
+ modified=modified.select { |each| each.size == 1 }
25
+ #force, the collection to be realized
26
+ realized_collection=modified.to_real
27
+ assert_equal(['2', '6'], realized_collection)
28
+ assert_equal(50,modified_even.size)
29
+ assert(modified_even.to_real.class != Lazy::LazyEnumerable)
30
+ end
31
+
32
+ end
33
+
34
+ class Numeric
35
+ def even
36
+ self % 2 == 0
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ MY_DIR = File.expand_path(File.dirname(__FILE__))
2
+
3
+ def lib_require(name)
4
+ begin
5
+ require name
6
+ rescue LoadError
7
+ $LOAD_PATH << File.expand_path(File.join(MY_DIR, '..', 'lib'))
8
+ require name
9
+ end
10
+ end
11
+
12
+ def tests_require(name)
13
+ begin
14
+ require name
15
+ rescue LoadError
16
+ $LOAD_PATH << MY_DIR
17
+ require name
18
+ end
19
+ end
20
+
21
+ lib_require 'lazy_enumerable'
22
+ tests_require 'lazy_enumerable_test'
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: LazyEnumerable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Blaine Buxton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-20 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: altodorado@blainebuxton
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - LICENSE
25
+ files:
26
+ - tests/lazy_enumerable_test.rb
27
+ - tests/lazy_enumerable_test_suite.rb
28
+ - lib/lazy
29
+ - lib/lazy/collect_enumerable.rb
30
+ - lib/lazy/extensions.rb
31
+ - lib/lazy/iterator.rb
32
+ - lib/lazy/lazy_enumerable.rb
33
+ - lib/lazy/reject_enumerable.rb
34
+ - lib/lazy/select_enumerable.rb
35
+ - lib/lazy_enumerable.rb
36
+ - README
37
+ - LICENSE
38
+ has_rdoc: true
39
+ homepage: http://lazyenum.rubyforge.org/
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project: lazyenum
60
+ rubygems_version: 1.0.1
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: Functional programming with collections (Higher Order Methods)
64
+ test_files:
65
+ - tests/lazy_enumerable_test_suite.rb