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:
- Sometimes I want to know whether I’m in the first (or last)
iteration of an iterator block. - Sometimes I want to do things “alternately” (i.e., for every
other iteration). - 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. - 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. - 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:
- Create an IteratorVar class that stores all the information used
in the looping. - Give it first? and last? methods.
- Give it odd? and even? methods.
- Give it an accessor that can retrieve the value and is even able
to replace the value in the original list. - Give it a ‘next’ method that will update the counter and return
the relevant object. - 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. - The yielded item will be an IteratorVar.
Bugs and limitations:
- 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. - 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