Super-iterator? (long)

Here’s an idea for you. I’ve worked on it a couple of days.

Please feel free to critique the concept and/or the code.

The code can definitely be improved. It’s ugly and has
limitations and probably has bugs.

Motivation:

  1. Sometimes I want to know whether I’m in the first (or last)
    iteration of an iterator block.
  2. Sometimes I want to do things “alternately” (i.e., for every
    other iteration).
  3. I’m a tiny bit displeased by the difference between each and
    each_with_index. I’d like them to be done by one thing.
  4. Sometimes I want to change an item in an array, for instance.
    each won’t allow this; I have to use each_with_index and manually
    index into the array.
  5. Sometimes an ordinary iterator seems inadequate; I want something
    that can grab the next entry on demand, without waiting for the
    loop to run around again.

The solution:

  1. Create an IteratorVar class that stores all the information used
    in the looping.
  2. Give it first? and last? methods.
  3. Give it odd? and even? methods.
  4. Give it an accessor that can retrieve the value and is even able
    to replace the value in the original list.
  5. Give it a ‘next’ method that will update the counter and return
    the relevant object.
  6. Put ‘iterator’ into Enumerable (actually, at this point it should
    probably NOT go into Enumerable) which wraps it all in a big while
    loop that does a yield.
  7. The yielded item will be an IteratorVar.

Bugs and limitations:

  1. It pretty much only works for arrays now. The "list = self.entries"
    line created a new array, so I couldn’t store items back into the array
    with #value=. So I changed it to “list = self” – meaning that the
    object must have [] and be indexed by an integer. So that’s pretty
    much an Array. I’ve left it this way to illustrate the concept.
  2. Obviously things like files are out of the question at the moment.

Code and output below.

Cheers,
Hal Fulton

class IteratorVar
attr_accessor :count_, :max_
attr_reader :value
attr_reader :skip?
attr_writer :skip

def initialize(index,max,list)
@count_ = index
@max_ = max
@value = list[index]
@skip = false
@list_ = list
end

def first?
@count_ == 0
end

def last?
@count_ >= @max_
end

def odd?
@count_ % 2 == 1
end

def even?
@count_ % 2 == 0
end

def next
@count_ += 1
@value = @list_[@count_]
@skip = true
self.dup
end

def value=(val)
@value = val
@list_[@count_] = val
end

def inspect
@value.inspect
end

def to_s
@value.to_s
end

def to_str
@value.to_str
end

end

module Enumerable

def iterate
low = 0
high = self.size-1
list = self
i = low
x = IteratorVar.new(i,high,list)
while i <= high
x.count_ = i
x.value = list[i]
yield x
if x.skip?
i = x.count_
x.skip = false
else
i = x.count_ += 1
end
end
end

end

a = %w[zero one two three four]

a.iterate do |x|
puts “Value is #{x} “
puts " Index is odd " if x.odd?
puts " Index is even” if x.even?
puts " First item!” if x.first?
puts " Last item!" if x.last?
end

puts

Same array a – capitalize every other entry

a.iterate do |x|
if x.odd?
x.value = x.value.upcase
end
end

a.each {|x| puts x}

puts

Problem: For a list of items, grab the “parent”

and the fixed number of children following. A

parent is a single letter – if it’s a vowel

[aeiou] it has a single child; a consonant will

have three children

puts

items = %w[ b 0 1 2 a 3 c 3 4 5 d 6 7 8 e 9 ]

items.iterate do |item|
parent = item.value
if parent =~ /[aeiou]/
puts "#{parent} -> #{item.next}"
else
arr = []
3.times { arr << item.next}
puts "#{parent} -> #{arr}"
end
end

Output…

=begin
Value is zero
Index is even
First item!
Value is one
Index is odd
Value is two
Index is even
Value is three
Index is odd
Value is four
Index is even
Last item!

zero
ONE
two
THREE
four

b -> 012
a -> 3
c -> 345
d -> 678
e -> 9
=end

Hi –

Here’s an idea for you. I’ve worked on it a couple of days.

Please feel free to critique the concept and/or the code.

I’ve only just started to look at it, so this is sort of a preliminary
question. Namely: how much of what you’re doing here could/would be
accomplished with #map_with_indices – and, perhaps more,
#map_with_indices! That would combine the counter aspects of the
#each*index methods with the in-place operations of #map(!).

More comments/questions probably coming :slight_smile:

David

···

On Tue, 6 Aug 2002, Hal E. Fulton wrote:


David Alan Black
home: dblack@candle.superlink.net
work: blackdav@shu.edu
Web: http://pirate.shu.edu/~blackdav

Hi –

Just for fun, I’ve converted your tests to Test::Unit format. This is
a drop-in replacement for everything after the module Enumerable
extensions.

A few comments:

  1. I’m still not sold on the whole thing :slight_smile: But it’s extremely
    interesting.

  2. I had to change #next to #i_next, because it was next’ing integers and
    characters.

  3. #odd? and #even? are, in my view, too “meta”. When I see “x.odd?”,
    it’s virtually impossible not to expect that it’s testing an integer
    for oddness.

  4. Are you sure you want #next (or #i_next, or whatever) to consume
    items? What if you want to grab the next item, but also want to
    iterate to that item in the normal way? You seem to have less
    functionality here (if I’m right about that) than you would with one
    of the “index” methods.

  5. I’ve done some fancy #to_s’ing… not sure if I should have to do
    that. Anyway, I’ve done it, just to get the tests to pass initially.

David

-----------------------------------------------------------

if FILE == $0

require “test/unit” # I know, I know – I should indent.

class TestMe < Test::Unit::TestCase
def set_up
@a = %w[zero one two three four]
end
def test1
i = 0
@a.iterate do |x|
assert(x.first?) if x == "zero"
assert(x.even?) if i % 2 == 0
assert(x.odd?) if i % 2 == 1
assert(x.last?) if x == "four"
i += 1
end
end

puts

Same array a – capitalize every other entry

def test2
@a.iterate do |x|
if x.odd?
x.value = x.value.upcase
end
end
assert_equal(@a, %w{ zero ONE two THREE four })
end

Problem: For a list of items, grab the “parent”

and the fixed number of children following. A

parent is a single letter – if it’s a vowel

[aeiou] it has a single child; a consonant will

have three children

def test3
puts
items = %w[ b 0 1 2 a 3 c 3 4 5 d 6 7 8 e 9 ]
i = 0
items.iterate do |item|
parent = item.value
if parent =~ /[aeiou]/
assert_equal(item.i_next.to_s, items[i+=1])
else
arr = []
3.times { |n| arr << item.i_next.to_s }
arr.each_index {|n| assert_equal(arr[n],items[i+n+1])}
i += 3
end
i += 1
end
end
end
end

Here’s an idea for you. I’ve worked on it a couple of days.
[…]
2. Sometimes I want to do things “alternately” (i.e., for every
other iteration).

I think this is a specific case – maybe you will want to do
something every third/fifth iteration…

class IteratorVar
[…]
How about deleting odd? and even? and having:

def mod?(x)
  @count_ % x == 0
end

def mod(x)
  @count_ % x
end

the latter so you can pick out the value you want. Iterate round
the clock and fire off lunch at mod(24)==13, etc…

Someone else made this point about next updating count.
I’d suggest next and next! to overcome this.

def inspect
@value.inspect
end

Should there be an inspect that tells you about @count_ as well?

    HTH
    Hugh
···

On Tue, 6 Aug 2002, Hal E. Fulton wrote:

Just for fun, I’ve converted your tests to Test::Unit format. This is
a drop-in replacement for everything after the module Enumerable
extensions.

Neat. I need to get into that habit.

A few comments:

  1. I’m still not sold on the whole thing :slight_smile: But it’s extremely
    interesting.

Oh, neither am I (completely sold). It’s an experiment.
I’m sold on my own “motivations” but not on this solution.

  1. I had to change #next to #i_next, because it was next’ing integers and
    characters.

Huh?? next is a method of IteratorVar… it shouldn’t cause any
kind of conflict. Can you explain?

  1. #odd? and #even? are, in my view, too “meta”. When I see “x.odd?”,
    it’s virtually impossible not to expect that it’s testing an integer
    for oddness.

I agree. But I couldn’t think of what else to call them. When I want
to do things alternately in a loop, sometimes I want the even-numbered
iterations, and sometimes the odd.

  1. Are you sure you want #next (or #i_next, or whatever) to consume
    items? What if you want to grab the next item, but also want to
    iterate to that item in the normal way? You seem to have less
    functionality here (if I’m right about that) than you would with one
    of the “index” methods.

According to my own usage patterns, I usually do want it to consume.
If I were writing some kind of parser, it might be different.

  1. I’ve done some fancy #to_s’ing… not sure if I should have to do
    that. Anyway, I’ve done it, just to get the tests to pass initially.

Actually, according to the way I was doing things, I should have
said x.next.value instead of just x.next (assuming no one actually
wants to store an IteratorVar). Maybe that would help.

Hal

···

----- Original Message -----
From: “David Alan Black” dblack@candle.superlink.net
To: “ruby-talk ML” ruby-talk@ruby-lang.org
Sent: Monday, August 05, 2002 4:37 PM
Subject: Re: Super-iterator? (long)

How about deleting odd? and even? and having:

def mod?(x)

Interesting. I’d probably have both. I think 90%
of the time (for me) I’m just concerned with “every
other” iteration. And that’s when I care at all,
which is less than 10% of the time.

Someone else made this point about next updating count.
I’d suggest next and next! to overcome this.

Interesting. I can see that.

Should there be an inspect that tells you about @count_ as well?

inspect was just there for my experimenting convenience…
what it should be remains to be seen.

Thanks,
Hal

···

----- Original Message -----
From: “Hugh Sasse Staff Elec Eng” hgs@dmu.ac.uk
To: “ruby-talk ML” ruby-talk@ruby-lang.org
Sent: Tuesday, August 06, 2002 4:37 AM
Subject: Re: Super-iterator? (long)