LazyEnumerable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +7 -0
- data/README +20 -0
- data/lib/lazy/collect_enumerable.rb +14 -0
- data/lib/lazy/extensions.rb +28 -0
- data/lib/lazy/iterator.rb +63 -0
- data/lib/lazy/lazy_enumerable.rb +123 -0
- data/lib/lazy/reject_enumerable.rb +14 -0
- data/lib/lazy/select_enumerable.rb +14 -0
- data/lib/lazy_enumerable.rb +6 -0
- data/tests/lazy_enumerable_test.rb +38 -0
- data/tests/lazy_enumerable_test_suite.rb +22 -0
- metadata +65 -0
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,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,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
|