В Ruby, похоже, не так много встроенных методов для того, чтобы делать разные вещи, которые вы хотели делать с перечислителями, но вы можете создавать свои собственные методы. Вот что я сделал здесь, используя Ruby 1.9:
iter.rb
def get_enums_from_args(args)
args.collect { |e| e.is_a?(Enumerator) ? e.dup : e.to_enum }
end
def build(y, &block)
while true
y << (begin yield; rescue StopIteration; break; end)
end
end
def zip(*args)
enums = get_enums_from_args args
Enumerator.new do |y|
build y do
enums.collect { |e| e.next }
end
end
end
def chain(*args)
enums = get_enums_from_args args
Enumerator.new do |y|
enums.each do |e|
build y do
e.next
end
end
end
end
def multiply(*args)
enums = get_enums_from_args args
duped_enums = enums.collect { |e| e.dup }
Enumerator.new do |y|
begin
while true
y << (begin; enums.collect { |e| e.peek }; rescue StopIteration; break; end )
index = enums.length - 1
while true
begin
enums[index].next
enums[index].peek
break
rescue StopIteration
# Some iterator ran out of items.
# If it was the first iterator, we are done,
raise if index == 0
# If it was a different iterator, reset it
# and then look at the iterator before it.
enums[index] = duped_enums[index].dup
index -= 1
end
end
end
rescue StopIteration
end
end
end
И я написал спецификацию, используя rspec для тестирования функций и демонстрации того, что они делают:
iter_spec.rb:
require_relative 'iter'
describe "zip" do
it "zips together enumerators" do
e1 = "Louis".chars
e2 = "198".chars
zip(e1,e2).to_a.should == [ ['L','1'], ['o','9'], ['u','8'] ]
end
it "works with arrays too" do
zip([1,2], [:a, nil]).to_a.should == [ [1,:a], [2,nil] ]
end
end
describe "chain" do
it "chains enumerators" do
e1 = "Jon".chars
e2 = 0..99999999999
e = chain(e1, e2)
e.next.should == "J"
e.next.should == "o"
e.next.should == "n"
e.next.should == 0
e.next.should == 1
end
end
describe "multiply" do
it "multiplies enumerators" do
e1 = "ABC".chars
e2 = 1..3
multiply(e1, e2).to_a.should == [["A", 1], ["A", 2], ["A", 3], ["B", 1], ["B", 2], ["B", 3], ["C", 1], ["C", 2], ["C", 3]]
end
it "is lazily evalutated" do
e1 = 0..999999999
e2 = 1..3
e = multiply(e1, e2)
e.next.should == [0, 1]
e.next.should == [0, 2]
e.next.should == [0, 3]
e.next.should == [1, 1]
e.next.should == [1, 2]
end
it "resulting enumerator can not be cloned effectively" do
ranks = chain(2..10, [:jack, :queen, :king, :ace])
suits = [:clubs, :diamonds, :hearts, :spades]
cards = multiply(suits, ranks)
c2 = cards.clone
cards.next.should == [:clubs, 2]
c2.next.should == [:clubs, 2]
c2.next.should == [:clubs, 3]
c2.next.should == [:clubs, 4]
c2.next.should == [:clubs, 5]
cards.next.should == [:clubs, 6]
end
it "resulting enumerator can not be duplicated after first item is evaluated" do
ranks = chain(2..10, [:jack, :queen, :king, :ace])
suits = [:clubs, :diamonds, :hearts, :spades]
cards = multiply(ranks, suits)
cards.peek
lambda { cards.dup }.should raise_error TypeError
end
end
Как показано в спецификации выше, эти методы используют ленивую оценку.
Кроме того, основным недостатком функций zip
, chain
и multiply
, определенных здесь, является то, что результирующий перечислитель не может быть легко дублирован или клонирован, потому что мы не написали никакого кода для дублирования аргументов перечисления на что полагаются эти новые счетчики. Вам, вероятно, понадобится создать подкласс Enumerator
или класс, включающий модуль Enumerable
или что-то в этом роде, чтобы dup
работал хорошо.