Implement lazy enumerator in rub
2014-12-11 本文已影响62人
davidhuangdw
How
- we can make a monad,
- it stores:
- original_collection
- transformation proc
- every time(map, select, reject, drop):
- create a new monad, with:
- the same original_collection
- adjusted transformation proc
- create a new monad, with:
- it stores:
Test
shared_examples_for 'lazy' do
it "should make lazy enumeration" do
expect(take_first_three_odd).to eq first_three_odd
end
end
describe Lazy do
let(:inf) { (1...Float::INFINITY) }
let(:lazy_list) {inf.my_lazy.map{|i| i*i}}
let(:take_first_three_odd) {lazy_list.select(&:odd?).take(3)}
let(:first_three_odd) {[1,9,25]}
context 'when infinite list' do
it_behaves_like 'lazy'
end
context 'when drop a few' do
let(:take_first_three_odd) {lazy_list.reject(&:even?).drop(2).take(3)}
let(:first_three_odd) {[25,49,81]}
it_behaves_like 'lazy'
end
context 'when drop more than once' do
let(:take_first_three_odd) {lazy_list.reject(&:even?).drop(1).drop(2).first(3)}
let(:first_three_odd) {[49,81,121]}
it_behaves_like 'lazy'
end
context 'when have nil' do
let(:take_first_three_odd) {lazy_list.reject(&:even?).map{nil}.drop(2).first(3)}
let(:first_three_odd) {[nil,nil,nil]}
it_behaves_like 'lazy'
end
end
Code
class Lazy
def initialize(coll, &trans)
@coll = coll
@trans = trans || proc{|c,&b| c.each(&b)}
end
def my_lazy; self end
def map(&blk)
create do |c,&b|
@trans.call(c){|e| b[blk[e]]}
end
end
def select(&blk)
create do |c,&b|
@trans.call(c){|e| b[e] if blk[e]}
end
end
def reject(&blk)
select{|e| !blk[e]}
end
def drop(n)
count = 0
create do |c, &b|
@trans.call(c) do |e|
b[e] unless count<n
count+=1
end
end
end
def take(n)
res= []
@trans.call(@coll) do |e|
res.size<n ? res<<e : (return res)
end
res
end
alias_method :first, :take
private
def create(&blk)
self.class.new(@coll,&blk)
end
end
module Enumerable
def my_lazy; Lazy.new(self) end
end