Rubin: manipulate iterators?

I'm having problems with Ruby related to creating unidirectional, lazily evaluated, potentially endless iterators. Basically, I'm trying to use Ruby as if I were using Haskell lists and, to a lesser extent, Python generators.

Not that I myself did not understand them; I just don’t know how to use them as casually as other languages, and I'm also not sure which Ruby methods will turn them into arrays behind me, without having to unload the entire sequence into memory.

And yes, I am studying the Ruby reference guide. For half an hour, actually, carefully. Or maybe not.

For example, if I were to use a deck of cards, in Python (untested) it would look something like this:

# Python 3 from itertools import chain, count face_ranks = dict( zip( ('jack', 'queen', 'king', 'ace'), count(11))) sorted_deck = map( lambda suit: map( lambda rank: { 'rank' : rank, 'suit' : suit }, chain( range(2, 11), face_ranks.keys())), ('clubs', 'diamonds', 'hearts', 'spades')) 

So, how would I do this in Ruby, completely avoiding arrays? Please note that the code above uses, as far as I know, only tuples and generators: in no case is the whole whole sequence dumped into memory, as if I were using an array. I could be wrong in the code above, but you get what I want.

How do I bind iterators (e.g. Python chain ())? How to create an infinite range iterator (e.g. Python count ())? How to add an array to an iterator (for example, pass a tuple into a Python () chain) without converting the entire object to an array in the process?

I have seen solutions, but they include arrays or unnecessary complexity, such as fibers.

In Python, I can manipulate and traverse iterators with the same simplicity as arrays. I can almost see them as the Haskell lists that I like best with, and this is really what I think about when coding. I am not comfortable working with Ruby arrays, so I turn to its alternatives for help.

I managed to gather information about this on the Internet on the Internet, but I could not find anything that would cover the basic manipulations with such data structures in Ruby? Any help?

+6
source share
3 answers

Ruby doesn't seem to have many built-in methods for doing the different things you wanted to do with the counters, but you can create your own methods. This is what I did here using 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 

And I wrote a specification using rspec to test functions and demonstrate what they do:

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 

As shown in the above specifications, these methods use lazy evaluation.

In addition, the main weakness of the zip , chain and multiply functions defined here is that the resulting counter cannot be easily duplicated or cloned, because we did not write code to duplicate the listing arguments that these new counters rely on. You probably need to subclass Enumerator or make a class by including an Enumerable module or something similar to make dup work beautifully.

+4
source

It seems that you are avoiding Ruby arrays due to performance issues, possibly due to the experience you had with arrays in other languages. You do not need to avoid Ruby arrays - they are closest to a tuple in Ruby.

 foo = 1, 2, 3, 4 foo.class #=> Array 

It looks like you are looking for Range instead of generator:

 range = 1..4 range.class #=> Range range.count #=> 4 ('a'..'z').each { |letter| letter.do_something } 

The range is not converted to an array, but it includes Enumerable, so you can use all your regular counters. As for the loop / iteration - the built-in loop in Ruby via Enumerable. for i in group is actually syntactic sugar for enumerator loops (e.g. .each ). Enumerated methods usually return the sender, so you can bind them:

 (1..10).map { |n| n * 2 }.each { |n| print "##{n}" } # outputs #2#4#6#8#10#12#14#16#18#20 # returns an array: #=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] 

I would like to give you more specific answers about your Python "Ruby equivalents, but I am not familiar with Python.

Update

You can combine ranges into a nested array as follows:

 (1..26).zip('a'..'z') #=> [[1, 'a'], [2, 'b'], ...] 

... but Ranges are not changeable. You can convert the range to an array using (1..5).to_a , or you can (1..5).to_a over it as shown above. If you have several specific data ranges that you want to check for inclusion, you can use a couple of ranges and a map:

 allowed = 'a'..'z', 1..100 input = # whatever allowed.each do |range| return false unless range.cover? input end 

Of course, you can always use counters with ranges to “generate” values ​​on the fly.

+2
source

The closest equivalent in Ruby is Enumerator . This allows you to make lazy generators.

+2
source

Source: https://habr.com/ru/post/894507/


All Articles