Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

accessing index inside map

3 views
Skip to first unread message

"Peña, Botp"

unread,
Jul 8, 2005, 3:30:22 AM7/8/05
to
Hi All,

How does one access the array index in #map?

eg,

irb(main):002:0> [1,2,3,4,5,6].map! {|x| x*2 }
=> [2, 4, 6, 8, 10, 12]

i'd like to double the element values except those loc on the 2nd and 5th
indices eg.

sorry if the answer is too obvious but i cannot find something like
array#each_with_index...

thanks in advance

kind regards -botp


nobuyoshi nakada

unread,
Jul 8, 2005, 4:22:55 AM7/8/05
to
Hi,

At Fri, 8 Jul 2005 16:30:22 +0900,
Peña, Botp wrote in [ruby-talk:147536]:


> i'd like to double the element values except those loc on the 2nd and 5th
> indices eg.

$ ruby -renumerator -e 'p (1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i) ? x : x*2}'
[2, 4, 3, 8, 10, 6]

--
Nobu Nakada


daz

unread,
Jul 8, 2005, 4:56:52 AM7/8/05
to

Peña, Botp wrote:

> [...] i cannot find something like array#each_with_index...

David Black was once President of Citizens for MWI, Inc.
(google "map with index" MWI).

Welcome!


You can add your own, of course.

module Enumerable
def map_with_index!
each_with_index do |e, ix|
self[ix] = yield e, ix
end
self
end
end

arr = (1..6).to_a
arr.map_with_index! do |e, ix|
[2, 5].include?(ix) ? e : e*2
end

p arr
#=> [2, 4, 3, 8, 10, 6]


daz

"Peña, Botp"

unread,
Jul 8, 2005, 5:11:27 AM7/8/05
to
nobuyoshi nakada [mailto:nobuyosh...@ge.com] wrote:

#> i'd like to double the element values except those loc on
#the 2nd and 5th
#> indices eg.
#
#$ ruby -renumerator -e 'p
#(1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i) ?
#x : x*2}'
#[2, 4, 3, 8, 10, 6]

Hi Nobu,

Wow, cool. But my brain is very tiny compared to other rubyist brains.

i was hoping for something straight like,

>[1,2,3,4,5,6].map{|x,i|[2,5].include?(i) ? x : x*2}


=>[2, 4, 3, 8, 10, 6]

Maybe you can hack one for dumb rubyists like me.

thanks and kind regards -botp

#
#--
#Nobu Nakada
#


"Peña, Botp"

unread,
Jul 8, 2005, 5:13:10 AM7/8/05
to
daz [mailto:do...@d10.karoo.co.uk] wrote:

#> [...] i cannot find something like array#each_with_index...
#
#David Black was once President of Citizens for MWI, Inc.
#(google "map with index" MWI).
#
#Welcome!
#
#
#You can add your own, of course.
#
#module Enumerable
# def map_with_index!
# each_with_index do |e, ix|
# self[ix] = yield e, ix
# end
# self
# end
#end
#
#arr = (1..6).to_a
#arr.map_with_index! do |e, ix|
# [2, 5].include?(ix) ? e : e*2
#end
#
#p arr
##=> [2, 4, 3, 8, 10, 6]

uber cool and simple.
Thanks daz and DavidB.

kind regards -botp

#
#
#daz
#
#
#
#


Robert Klemme

unread,
Jul 8, 2005, 6:17:26 AM7/8/05
to

Err, are you kidding? Enumerator isn't really that difficult - and you
don't need to modify Hash or other classes. It's a simple wrapper
delegating each to each_with_index. That simple.

Regards

robert

David A. Black

unread,
Jul 8, 2005, 6:40:44 AM7/8/05
to
This message is in MIME format. The first part should be readable text,
while the remaining parts are likely unreadable without MIME-aware tools.

David A. Black

unread,
Jul 8, 2005, 6:44:39 AM7/8/05
to

Michael Campbell

unread,
Jul 8, 2005, 8:56:03 AM7/8/05
to
On 7/8/05, David A. Black <dbl...@wobblini.net> wrote:
> Hi --

> I'd still like to see #man_with_index added to Enumerable.

"index" implies an ordering, which maps by definition don't have.
Shouldn't that be something along the lines of "#map_with_key"?

But point taken; it seems a natural thing, regardless of actual name,
to be in Enumerable.


David A. Black

unread,
Jul 8, 2005, 9:25:10 AM7/8/05
to
Hi --

Actually you're right about the ordering, and I think it should not be
in Enumerable. Hash#each_with_index has always struck me as pointless
for that reason: it just slaps essentially arbitrary integers on an
unordered collection. It also has always struck me as odd that
Enumerable has #each_with_index but lacks #index.

What I'd really like to see would be #each_with_index removed from
Enumerable, and pushed down to Array and other classes that need it
(like #index), and then #map_with_index added to Array too.

I think there's an RCR about this, probably, somewhere... possibly
rejected... :-) There's also been a lot of discussion here and
elsewhere about things like whether "enumerable" has to mean
"associated serially with integers", whether hashes should really be
understood as enumerable, whether hash keys are the equivalent of
array indices or whether, if hashes have a numerical "index", that
index is the equivalent of an array index, and if so, why hashes need
that extra layer of indexing, etc. (That's also come up in connection
with what an Array#to_hash method would do.)

I don't have any definitive answers, but I think there's room to clear
up the ambiguous status of the concept "index" as it is manifested in
Enumerable.


David

--
David A. Black
dbl...@wobblini.net


Robert Klemme

unread,
Jul 8, 2005, 9:30:56 AM7/8/05
to

Key is ambiguous with Hashes... Apart from that: I'm not really sure
whether I like that idea. The basic property of an Enumerable is that it
can be enumerated with each. There are no guarantees about order etc.
each_with_index is really only there to simplify counting. If you want
map_with_index then you'd probably have to provide inject_with_index,
find_with_index etc. Personally I think that map_with_index is rarely
used to justify adding this to the std lib. Just my 0.02 EUR of course.

Btw, another variant for mapping with index:

enum.inject([]) {|ar,el| ar << ([0,3].include?(ar.size) ? el : el*2) }

Kind regards

robert

David A. Black

unread,
Jul 8, 2005, 9:54:56 AM7/8/05
to
Hi --

On Fri, 8 Jul 2005, Robert Klemme wrote:

> Michael Campbell wrote:
>> On 7/8/05, David A. Black <dbl...@wobblini.net> wrote:
>>> Hi --
>>
>>> I'd still like to see #man_with_index added to Enumerable.
>>
>> "index" implies an ordering, which maps by definition don't have.
>> Shouldn't that be something along the lines of "#map_with_key"?
>>
>> But point taken; it seems a natural thing, regardless of actual name,
>> to be in Enumerable.
>
> Key is ambiguous with Hashes... Apart from that: I'm not really sure
> whether I like that idea. The basic property of an Enumerable is that it
> can be enumerated with each. There are no guarantees about order etc.
> each_with_index is really only there to simplify counting. If you want
> map_with_index then you'd probably have to provide inject_with_index,
> find_with_index etc. Personally I think that map_with_index is rarely
> used to justify adding this to the std lib. Just my 0.02 EUR of course.

I'm not a stickler for symmetry (my slogan for Ruby is "The triumph of
balance over symmetry" :-) but it does seem a little arbitrary to have
it recognized that #each_with_index is important for arrays, and #map
is important, but #map_with_index isn't. (I emphasize "arrays"; I've
never seen a case where it was needed or useful for hashes.) I don't
think there's any implication that you'd have to have *_with_index.
Nor any demand: I've never seen anyone implement #find_with_index
(what exactly would it do? :-) but I've seen many of
Array#each_with_index.

Yukihiro Matsumoto

unread,
Jul 8, 2005, 10:02:11 AM7/8/05
to
Hi,

In message "Re: accessing index inside map"


on Fri, 8 Jul 2005 19:20:48 +0900, "Robert Klemme" <bob....@gmx.net> writes:

|> #$ ruby -renumerator -e 'p
|> #(1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i) ?
|> #

|> #[2, 4, 3, 8, 10, 6]
|>
|> Hi Nobu,
|>
|> Wow, cool. But my brain is very tiny compared to other rubyist brains.
|>
|> i was hoping for something straight like,
|>
|>> [1,2,3,4,5,6].map{|x,i|[2,5].include?(i) ? x : x*2}
|> =>[2, 4, 3, 8, 10, 6]
|>
|> Maybe you can hack one for dumb rubyists like me.
|
|Err, are you kidding? Enumerator isn't really that difficult - and you
|don't need to modify Hash or other classes. It's a simple wrapper
|delegating each to each_with_index. That simple.

We have vague plan to make enumerating method to return Enumerator
when no block is given in the future, so that

require 'enumerator'
(1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i) ? x : x*2}

would be

(1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}

then. It's much simpler isn't it?

matz.


Robert Klemme

unread,
Jul 8, 2005, 10:17:05 AM7/8/05
to

What about removing this completely from Enumerable and doing it like
this:

class Indexer
include Enumerable

def initialize(obj)
# @meth = meth
@obj = obj
end

def each
i = 0
@obj.each {|*a| yield *(a << i); i+=1}
self
end
end

module Enumerable
def indexer() Indexer.new(self) end
end

>> aa=[10,20,30]
=> [10, 20, 30]
>> aa.indexer.each {|a,i| printf "%4d. %s\n", i, a}
0. 10
1. 20
2. 30
=> #<Indexer:0x1017edc8 @obj=[10, 20, 30]>
>> aa.indexer.map {|a,i| a*i}
=> [0, 20, 60]
>> aa.indexer.inject([]) {|ar,(a,i)| ar << sprintf( "%4d. %s", i, a)}
=> [" 0. 10", " 1. 20", " 2. 30"]

>> hh={10=>:a, 20=>:b, 30=>:c}
=> {30=>:c, 20=>:b, 10=>:a}
>> hh.indexer.each {|(k,v),i| printf "%4d. %s => %s\n", i, k, v}
0. 30 => c
1. 20 => b
2. 10 => a
=> #<Indexer:0x101a1940 @obj={30=>:c, 20=>:b, 10=>:a}>
>> hh.indexer.map {|(k,v),i| "#{k}=>#{v}" * i}
=> ["", "20=>b", "10=>a10=>a"]
>> hh.indexer.inject([]) {|ar,((k,v),i)| ar << sprintf( "%4d. %s => %s",
i, k, v)}
=> [" 0. 30 => c", " 1. 20 => b", " 2. 10 => a"]

Admittedly the last example looks a bit awkward - but #inject is not easy
anyway and it stays consistend with the others.

Kind regards

robert

William Morgan

unread,
Jul 8, 2005, 10:39:54 AM7/8/05
to
Excerpts from daz's mail of 8 Jul 2005 (EDT):

> David Black was once President of Citizens for MWI, Inc.
> (google "map with index" MWI).
>
> Welcome!

Count me as a member. I write this function approximately once for each
Ruby program I work on.

--
William <wmorgan-...@masanjin.net>


William Morgan

unread,
Jul 8, 2005, 10:41:45 AM7/8/05
to
Excerpts from Michael Campbell's mail of 8 Jul 2005 (EDT):

> On 7/8/05, David A. Black <dbl...@wobblini.net> wrote:
> > I'd still like to see #man_with_index added to Enumerable.
>
> "index" implies an ordering, which maps by definition don't have.
> Shouldn't that be something along the lines of "#map_with_key"?

"Map" here means "collect", not "Hash".

--
William <wmorgan-...@masanjin.net>


William Morgan

unread,
Jul 8, 2005, 10:48:26 AM7/8/05
to
Excerpts from William Morgan's mail of 8 Jul 2005 (EDT):

> Excerpts from Michael Campbell's mail of 8 Jul 2005 (EDT):
> > On 7/8/05, David A. Black <dbl...@wobblini.net> wrote:
> > > I'd still like to see #man_with_index added to Enumerable.
> >
> > "index" implies an ordering, which maps by definition don't have.
> > Shouldn't that be something along the lines of "#map_with_key"?
>
> "Map" here means "collect", not "Hash".

Whoops, I may have read too much into your reply. Sorry.

Anyways, as Robert Klemme points out, the index is often useful in
counting, so I'd be favor of putting map_with_index in Enumerable.

--
William <wmorgan-...@masanjin.net>


Martin DeMello

unread,
Jul 8, 2005, 5:37:10 PM7/8/05
to
Yukihiro Matsumoto <ma...@ruby-lang.org> wrote:
>
> We have vague plan to make enumerating method to return Enumerator
> when no block is given in the future, so that
>
> require 'enumerator'
> (1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i) ? x : x*2}
>
> would be
>
> (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}
>
> then. It's much simpler isn't it?

Wow - I'd love to see that. Any potential drawbacks?

martin

daz

unread,
Jul 8, 2005, 9:16:04 PM7/8/05
to

David A. Black wrote:
[...]

>
> I don't have any definitive answers, but I think there's room to clear
> up the ambiguous status of the concept "index" as it is manifested in
> Enumerable.
>

In history, there was a hack submitted to ruby-core which tried to
implement a "block index" which supplied an automatically incrementing
counter to each invocation of *any* iterator.
The discussion turned into one about syntax rather than the concept.
Obviously, when the interpreter is hacked, some syntax might have to be
chosen to expose the index accessor.
I chose for myself (which is the author's prerogative and kind of essential).

So the analogue of this snippet:

[1,2,3,4,5,6].map_with_index! do |e, ix|


[2, 5].include?(ix) ? e : e*2

end

was proposed to be:

[1,2,3,4,5,6].map! do |e|.ix


[2, 5].include?(ix) ? e : e*2

end

In short, #each_with_index is scrapped and you use the standard #each
(or #map or any of the other methods taking a block) with an index/counter
magically available, on demand, within the block.

One could implement a fixed name like '_index_' or one of those Perl
thingies beginning with $ having local scope (as $1, $2 etc.) or any
number of alternatives; the principle is the same.

Two typically useful applications are:

HUGE_ARRAY.each do |elem|.elnum
break if $DBG && elnum > 500 # seen enough
# stuff
end

IO.foreach() do |line|.linum
# stuff where line number is useful (duh!)
end

One major factor that has bugged me is the lack of symmetry
between #each and #e_w_i which discourages temporary changes.

h = {:a => 1, :b => 2, :c => 3}
# Current conversion of Hash#each to #each_with_index

h.each {|ek, ev| p [ek , ev ]}
h.each_with_index {|e, idx| p [e[0], e[1], idx]}
# ^_________^ ^_^ ^_^ ^^_^ ^_^
# A B C D C E

# A. Change the method name to a much longer one.
# B. Name the index variable.
# C. Hash#each gives key and value -- #each_with_index
# passes these as a two-element array into the first
# block parameter. Specify which element.
# D. The name you chose for the second block parameter of each
# will, probably, need changing (if you used, say, 'key, val').
# E. Use the index variable.

Compared to this (with a wishful block index variable):

h.each {|ek, ev| p [ek, ev ]}
h.each {|ek, ev|.xv p [ek, ev, xv]}
# ^_^ ^_
# B E

Name it ... use it. (=== *_with_index)

In the interpreter, there'd be an integer in each BLOCK
struct which gets incremented before each iteration.
Not too expensive ?

One final gripe -- if you need a counter inside a block,
it's quicker to maintain your own than use #e_w_i.
(Do reply if this is incorrect.)

#--------------------------------------
require 'benchmark'
include Benchmark

arr = Array.new(100000)

bm(7) do |x|
x.report('eachA') do
ixj = 0; arr.each {|e| ixj += 1}
end
x.report('e_w_i') do
arr.each_with_index {|e, dmy| dmy}
end
x.report('eachB') do # same as 'eachA'
ixj = 0; arr.each {|e| ixj += 1}
end
end
#--------------------------------------


BTW, I had to remove my local patch because it was sooo
usable that it was integrating into my style.


daz

Devin Mullins

unread,
Jul 8, 2005, 10:01:09 PM7/8/05
to
daz wrote:

> h.each {|ek, ev| p [ek , ev ]}
> h.each_with_index {|e, idx| p [e[0], e[1], idx]}
># ^_________^ ^_^ ^_^ ^^_^ ^_^
># A B C D C E
>
>

How about:

irb(main):012:0> class Hash
irb(main):013:1> alias_method :orig_each, :each
irb(main):014:1> def each
irb(main):015:2> index = 0
irb(main):016:2> orig_each do |ek, ev|
irb(main):017:3* yield ek, ev, index
irb(main):018:3> index += 1
irb(main):019:3> end
irb(main):020:2> end
irb(main):021:1> end
=> nil
irb(main):022:0> h = {:a => 1, :b => 2, :c => 3}
=> {:c=>3, :a=>1, :b=>2}
irb(main):023:0> h.each {|ek, ev| p [ek , ev ]}
[:c, 3]
[:a, 1]
[:b, 2]
=> {:c=>3, :a=>1, :b=>2}
irb(main):026:0> h.each {|ek, ev, idx| p [ek, ev, idx]}
[:c, 3, 0]
[:a, 1, 1]
[:b, 2, 2]
=> {:c=>3, :a=>1, :b=>2}

This, as you already know, doesn't work with Arrays:
irb(main):027:0> def stuff
irb(main):028:1> yield 1,2
irb(main):029:1> end
=> nil
irb(main):030:0> stuff {|a| p a}
(irb):30: warning: multiple values for a block parameter (2 for 1)
from (irb):28
[1, 2]
=> nil

But that's being warned about, so I hope that, in the future, 'a' will
be set to 1, instead of [1,2]. (...which would make the above trick work
for Arrays.)

It wouldn't provide automatic "with index" functionality, as your hack
does, but it seems (to me) to be the "cleanest" change to allow you to
implement _with_index methods without polluting the methods list.

Sorry if this is a rehash (no pun intended) of stuff covered in the
ruby-core thread.

Devin

daz

unread,
Jul 8, 2005, 10:52:03 PM7/8/05
to

Devin Mullins wrote:
>
> How about:
>
> [... Hash#each_with_index session ...]

Fine, but see |*a| in Array examples below.
Where would the index go (?)

>
> This, as you already know, doesn't work with Arrays:
> irb(main):027:0> def stuff
> irb(main):028:1> yield 1,2
> irb(main):029:1> end
> => nil
> irb(main):030:0> stuff {|a| p a}
> (irb):30: warning: multiple values for a block parameter (2 for 1)
> from (irb):28
> [1, 2]
> => nil
>
> But that's being warned about, so I hope that, in the future, 'a' will
> be set to 1, instead of [1,2]. (...which would make the above trick work
> for Arrays.)

def stuff
yield [1,2] # yield one value, let assignment split them.
end

stuff {|a| p [a]} #-> [[1, 2]]
stuff {|a, b| p [a, b]} #-> [1, 2]
stuff {|a, b, c| p [a, b, c]} #-> [1, 2, nil]

stuff {|*a| p [a]} #-> [[[1, 2]]]
#-> |*a, index| would look ambiguous, here

>
> It wouldn't provide automatic "with index" functionality, as your hack
> does, but it seems (to me) to be the "cleanest" change to allow you to
> implement _with_index methods without polluting the methods list.
>

I'm sure it would have been desirable in the original #e_w_i
implementation, but difficult, therefore #e_w_i takes exactly
two arguments.


> Sorry if this is a rehash (no pun intended) of stuff covered in the
> ruby-core thread.

No danger ;-) That was a looong time ago.

>
> Devin
>

daz

Devin Mullins

unread,
Jul 8, 2005, 11:47:37 PM7/8/05
to
daz wrote:

>Fine, but see |*a| in Array examples below.
>Where would the index go (?)
>

If an assignment contains more lvalues than rvalues, the excess lvalues
are set to nil.
If an assignment contains more rvalues than lvalues, the excess rvalues
are ignores.

There are exceptions, however, for just one lvalue or just one rvalue.

a = 1,2 #=> [1, 2]
a #=> [1, 2]
a,b = [1,2] #=> [1, 2]
[a,b] #=> [1, 2]

I'm proposing we get rid of these heuristics. They can be explicitly be
enabled using asterisks.

*a = 1,2 #=> [1, 2]
a #=> [1, 2]
a,b = *[1,2] #=> [1, 2]
[a,b] #=> [1, 2]

So that the new result without *s would be:

a = 1,2 #=> [1, 2]
a #=> 1
a,b = [1,2] #=> [1, 2]
[a,b] #=> [[1, 2], nil]


This way, one could implement the C-equivalent of

class Array
alias_method :old_each, :each
def each
index = 0
old_each do |obj|
yield obj, index
index += 1
end
end
end

and then call:

["cat","dog","pin"].each {|obj| p obj}
["cat","dog","pin"].each {|obj,idx| puts "#{obj} at #{idx}"}

No doubt, this is strongly backwards-incompatible, but who am I to care? :P

Devin
..not a fan of Microsoft Clippy.

Daniel Amelang

unread,
Jul 9, 2005, 2:51:25 AM7/9/05
to
> We have vague plan to make enumerating method to return Enumerator
> when no block is given in the future,
>
> It's much simpler isn't it?
>

+1

Dan


William Morgan

unread,
Jul 10, 2005, 1:37:57 PM7/10/05
to
Excerpts from daz's mail of 8 Jul 2005 (EDT):

> One major factor that has bugged me is the lack of symmetry
> between #each and #e_w_i which discourages temporary changes.
>
> h = {:a => 1, :b => 2, :c => 3}
> # Current conversion of Hash#each to #each_with_index
>
> h.each {|ek, ev| p [ek , ev ]}
> h.each_with_index {|e, idx| p [e[0], e[1], idx]}
> # ^_________^ ^_^ ^_^ ^^_^ ^_^
> # A B C D C E
>
> # A. Change the method name to a much longer one.
> # B. Name the index variable.
> # C. Hash#each gives key and value -- #each_with_index
> # passes these as a two-element array into the first
> # block parameter. Specify which element.
> # D. The name you chose for the second block parameter of each
> # will, probably, need changing (if you used, say, 'key, val').
> # E. Use the index variable.

Minor point, but you can also do

h.each_with_index { |(ek, ev), idx| p [ek, ev, idx] }

which solves C and D (though I guess introduces an "F").

--
William <wmorgan-...@masanjin.net>


daz

unread,
Jul 10, 2005, 2:28:03 PM7/10/05
to

William Morgan wrote:
> Excerpts from daz's mail of 8 Jul 2005 (EDT):
> >
> > h.each {|ek, ev| p [ek , ev ]}
> > h.each_with_index {|e, idx| p [e[0], e[1], idx]}
> > # ^_________^ ^_^ ^_^ ^^_^ ^_^
> > # A B C D C E
> >
>
> Minor point, but you can also do
>
> h.each_with_index { |(ek, ev), idx| p [ek, ev, idx] }
>
> which solves C and D (though I guess introduces an "F").
>
> William
>

A very strong point; one that I hadn't considered.
If the parens are left in, it's OK.

h.each_with_index { |(ek, ev), idx| p [ek, ev, idx] }

h.each { |(ek, ev) | p [ek, ev ] }

Dang, there's always a way with Ruby ;-)


daz
--
"Doctor, I'm cured." - A. Patient

"Peña, Botp"

unread,
Jul 10, 2005, 9:50:31 PM7/10/05
to
Yukihiro Matsumoto [mailto:ma...@ruby-lang.org] wrote:

#We have vague plan to make enumerating method to return Enumerator
#when no block is given in the future, so that
#
# require 'enumerator'
# (1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i)
#? x : x*2}
#
#would be
#
# (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}
#
#then. It's much simpler isn't it?

Hi sir Matz,

You're vague plan produces clearer ruby..

but isn't
each_with_index.map

so close to being
map_with_index

?

(1..6).map_with_index{|x,i|[2,5].include?(i) ? x : x*2} is very easy to my
tiny brain since all i have to remember is map (just like (in array) all i
have to focus is each, then i follow thru with each_with_index)

i'm sorry, i'm a map/collect fan so pardon my insistence :-)

thanks in advance.

kind regards
-botp -another spoiled nuby spoiled by map


#
# matz.
#


Yukihiro Matsumoto

unread,
Jul 10, 2005, 9:58:57 PM7/10/05
to
Hi,

In message "Re: accessing index inside map"

on Mon, 11 Jul 2005 10:50:31 +0900, "Peña, Botp" <bo...@delmonte-phil.com> writes:

|# (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}
|#
|#then. It's much simpler isn't it?

|but isn't

| each_with_index.map
|
|so close to being
| map_with_index
|
|?

Yes. But when we add map_with_index, we might be asked to add
collect_with_index, detect_with_index, inject_with_index, and all
other enumerable methods _with_index as well.

matz.


nobuyoshi nakada

unread,
Jul 11, 2005, 1:01:31 AM7/11/05
to
Hi,

At Mon, 11 Jul 2005 10:50:31 +0900,
Peña, Botp wrote in [ruby-talk:147720]:


> You're vague plan produces clearer ruby..
>
> but isn't
> each_with_index.map
>
> so close to being
> map_with_index
>
> ?
>
> (1..6).map_with_index{|x,i|[2,5].include?(i) ? x : x*2} is very easy to my
> tiny brain since all i have to remember is map (just like (in array) all i
> have to focus is each, then i follow thru with each_with_index)

What about Enumerator#with_index?

[1,2,3,4,5,6].map.with_index {|x,i|[2,5].include?(i) ? x : x*2}

--
Nobu Nakada


Ryan Leavengood

unread,
Jul 11, 2005, 1:09:41 AM7/11/05
to
nobuyoshi nakada wrote:
>
> What about Enumerator#with_index?
>
> [1,2,3,4,5,6].map.with_index {|x,i|[2,5].include?(i) ? x : x*2}

I like that, personally. In fact each_with_index could become
each.with_index (though we obviously need the old one for backwards
compatibility.)

Ryan


nobuyoshi nakada

unread,
Jul 11, 2005, 1:29:24 AM7/11/05
to
Hi,

At Mon, 11 Jul 2005 14:09:41 +0900,
Ryan Leavengood wrote in [ruby-talk:147729]:

Indeed.

$ ./ruby -renumerator -e 'p (1..6).to_enum.with_index{|x,i|p [x,i]}'
[1, 0]
[2, 1]
[3, 2]
[4, 3]
[5, 4]
[6, 5]
1..6

A patch based on the current implementation.


Index: ext/enumerator/enumerator.c
===================================================================
RCS file: /cvs/ruby/src/ruby/ext/enumerator/enumerator.c,v
retrieving revision 1.3.2.2
diff -U2 -p -r1.3.2.2 enumerator.c
--- ext/enumerator/enumerator.c 4 Nov 2004 01:20:50 -0000 1.3.2.2
+++ ext/enumerator/enumerator.c 11 Jul 2005 05:25:15 -0000
@@ -162,4 +162,27 @@ enumerator_each(obj)
}

+static VALUE
+enumerator_with_index_i(val, memo)
+ VALUE val, *memo;
+{
+ val = rb_yield_values(2, val, INT2FIX(*memo));
+ ++*memo;
+ return val;
+}
+
+static VALUE
+enumerator_with_index(obj)
+ VALUE obj;
+{
+ VALUE memo = 0;
+
+ obj = (VALUE)rb_node_newnode(NODE_MEMO,
+ rb_ivar_get(obj, id_enum_obj),
+ rb_to_id(rb_ivar_get(obj, id_enum_method)),
+ rb_ivar_get(obj, id_enum_args));
+ return rb_iterate((VALUE (*)_((VALUE)))enumerator_iter, obj,
+ enumerator_with_index_i, (VALUE)&memo);
+}
+
void
Init_enumerator()
@@ -183,4 +206,5 @@ Init_enumerator()
rb_define_method(rb_cEnumerator, "initialize", enumerator_initialize, -1);
rb_define_method(rb_cEnumerator, "each", enumerator_each, 0);
+ rb_define_method(rb_cEnumerator, "with_index", enumerator_with_index, 0);

sym_each = ID2SYM(rb_intern("each"));

--
Nobu Nakada


Yukihiro Matsumoto

unread,
Jul 11, 2005, 1:57:00 AM7/11/05
to
Hi,

In message "Re: accessing index inside map"

on Mon, 11 Jul 2005 14:29:24 +0900, nobuyoshi nakada <nobuyosh...@ge.com> writes:

| $ ./ruby -renumerator -e 'p (1..6).to_enum.with_index{|x,i|p [x,i]}'
| [1, 0]
| [2, 1]
| [3, 2]
| [4, 3]
| [5, 4]
| [6, 5]
| 1..6
|
|A patch based on the current implementation.

Can you commit this patch to the HEAD?

matz.


nobuyoshi nakada

unread,
Jul 11, 2005, 3:41:45 AM7/11/05
to
Hi,

At Mon, 11 Jul 2005 14:57:00 +0900,
Yukihiro Matsumoto wrote in [ruby-talk:147731]:


> | $ ./ruby -renumerator -e 'p (1..6).to_enum.with_index{|x,i|p [x,i]}'
> | [1, 0]
> | [2, 1]
> | [3, 2]
> | [4, 3]
> | [5, 4]
> | [6, 5]
> | 1..6
> |
> |A patch based on the current implementation.
>
> Can you commit this patch to the HEAD?

For performance, I'd like to make Enumerator a subclass of
Data instead of Object, in the HEAD.


Index: ext/enumerator/enumerator.c
===================================================================
RCS file: /cvs/ruby/src/ruby/ext/enumerator/enumerator.c,v

retrieving revision 1.5
diff -U2 -p -r1.5 enumerator.c
--- ext/enumerator/enumerator.c 2 Nov 2004 07:38:21 -0000 1.5
+++ ext/enumerator/enumerator.c 11 Jul 2005 07:38:27 -0000
@@ -18,5 +18,77 @@
static VALUE rb_cEnumerator;
static ID sym_each, sym_each_with_index, sym_each_slice, sym_each_cons;
-static ID id_new, id_enum_obj, id_enum_method, id_enum_args;
+#if !defined(HAVE_RB_PROC_CALL) || !defined(HAVE_RB_METHOD_CALL)
+static ID id_call;
+#endif
+
+static VALUE
+proc_call(proc, args)
+ VALUE proc, args;
+{
+#ifdef HAVE_RB_PROC_CALL
+ if (TYPE(args) != T_ARRAY) {
+ args = rb_values_new(1, args);
+ }
+ return rb_proc_call(proc, args);
+#else
+ return rb_funcall2(proc, id_call, 1, &args);
+#endif
+}
+
+static VALUE
+method_call(method, args)
+ VALUE method, args;
+{
+#ifdef HAVE_RB_METHOD_CALL
+ return rb_method_call(RARRAY(args)->len, RARRAY(args)->ptr, method);
+#else
+ return rb_funcall2(method, id_call, RARRAY(args)->len, RARRAY(args)->ptr);
+#endif
+}
+
+struct enumerator {
+ VALUE method;
+ VALUE proc;
+ VALUE args;
+ VALUE (*iter)_((VALUE, struct enumerator *));
+};
+
+static void enumerator_mark _((void *));
+static void
+enumerator_mark(p)
+ void *p;
+{
+ struct enumerator *ptr = p;
+ rb_gc_mark(ptr->method);
+ rb_gc_mark(ptr->proc);
+ rb_gc_mark(ptr->args);
+}
+
+static struct enumerator *
+enumerator_ptr(obj)
+ VALUE obj;
+{
+ struct enumerator *ptr;
+
+ Data_Get_Struct(obj, struct enumerator, ptr);
+ if (RDATA(obj)->dmark != enumerator_mark) {
+ rb_raise(rb_eTypeError,
+ "wrong argument type %s (expected Enumerable::Enumerator)",
+ rb_obj_classname(obj));
+ }
+ if (!ptr) {
+ rb_raise(rb_eArgError, "uninitialized enumerator");
+ }
+ return ptr;
+}
+
+static VALUE enumerator_iter_i _((VALUE, struct enumerator *));
+static VALUE
+enumerator_iter_i(i, e)
+ VALUE i;
+ struct enumerator *e;
+{
+ return rb_yield(proc_call(e->proc, i));
+}

static VALUE
@@ -26,5 +98,7 @@ obj_to_enum(obj, enum_args)
rb_ary_unshift(enum_args, obj);

- return rb_apply(rb_cEnumerator, id_new, enum_args);
+ return rb_class_new_instance(RARRAY(enum_args)->len,
+ RARRAY(enum_args)->ptr,
+ rb_cEnumerator);
}

@@ -33,5 +107,8 @@ enumerator_enum_with_index(obj)
VALUE obj;
{
- return rb_funcall(rb_cEnumerator, id_new, 2, obj, sym_each_with_index);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_with_index;
+ return rb_class_new_instance(2, args, rb_cEnumerator);
}

@@ -39,8 +116,8 @@ static VALUE
each_slice_i(val, memo)
VALUE val;
- NODE *memo;
+ VALUE *memo;
{
- VALUE ary = memo->u1.value;
- long size = memo->u3.cnt;
+ VALUE ary = memo[0];
+ long size = (long)memo[1];

rb_ary_push(ary, val);
@@ -48,5 +125,5 @@ each_slice_i(val, memo)
if (RARRAY(ary)->len == size) {
rb_yield(ary);
- memo->u1.value = rb_ary_new2(size);
+ memo[0] = rb_ary_new2(size);
}

@@ -59,14 +136,14 @@ enum_each_slice(obj, n)
{
long size = NUM2LONG(n);
- NODE *memo;
- VALUE ary;
+ VALUE args[2], ary;

if (size <= 0) rb_raise(rb_eArgError, "invalid slice size");

- memo = rb_node_newnode(NODE_MEMO, rb_ary_new2(size), 0, size);
+ args[0] = rb_ary_new2(size);
+ args[1] = (VALUE)size;

- rb_iterate(rb_each, obj, each_slice_i, (VALUE)memo);
+ rb_iterate(rb_each, obj, each_slice_i, (VALUE)args);

- ary = memo->u1.value;
+ ary = args[0];
if (RARRAY(ary)->len > 0) rb_yield(ary);

@@ -78,5 +155,9 @@ enumerator_enum_slice(obj, n)
VALUE obj, n;
{
- return rb_funcall(rb_cEnumerator, id_new, 3, obj, sym_each_slice, n);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_slice;
+ args[2] = n;
+ return rb_class_new_instance(3, args, rb_cEnumerator);
}

@@ -84,8 +165,8 @@ static VALUE
each_cons_i(val, memo)
VALUE val;
- NODE *memo;
+ VALUE *memo;
{
- VALUE ary = memo->u1.value;
- long size = memo->u3.cnt;
+ VALUE ary = memo[0];
+ long size = (long)memo[1];

if (RARRAY(ary)->len == size) {
@@ -104,10 +185,11 @@ enum_each_cons(obj, n)
{
long size = NUM2LONG(n);
- NODE *memo;
+ VALUE args[2];

if (size <= 0) rb_raise(rb_eArgError, "invalid size");
- memo = rb_node_newnode(NODE_MEMO, rb_ary_new2(size), 0, size);
+ args[0] = rb_ary_new2(size);
+ args[1] = (VALUE)size;

- rb_iterate(rb_each, obj, each_cons_i, (VALUE)memo);
+ rb_iterate(rb_each, obj, each_cons_i, (VALUE)args);

return Qnil;
@@ -118,5 +200,19 @@ enumerator_enum_cons(obj, n)
VALUE obj, n;
{
- return rb_funcall(rb_cEnumerator, id_new, 3, obj, sym_each_cons, n);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_cons;
+ args[2] = n;
+ return rb_class_new_instance(3, args, rb_cEnumerator);
+}
+
+static VALUE enumerator_allocate _((VALUE));
+static VALUE
+enumerator_allocate(klass)
+ VALUE klass;
+{
+ struct enumerator *ptr;
+ return Data_Make_Struct(rb_cEnumerator, struct enumerator,
+ enumerator_mark, -1, ptr);
}

@@ -128,4 +224,5 @@ enumerator_initialize(argc, argv, obj)
{
VALUE enum_obj, enum_method, enum_args;
+ struct enumerator *ptr = enumerator_ptr(obj);

rb_scan_args(argc, argv, "11*", &enum_obj, &enum_method, &enum_args);
@@ -134,16 +231,25 @@ enumerator_initialize(argc, argv, obj)
enum_method = sym_each;

- rb_ivar_set(obj, id_enum_obj, enum_obj);
- rb_ivar_set(obj, id_enum_method, enum_method);
- rb_ivar_set(obj, id_enum_args, enum_args);
+ ptr->method = rb_obj_method(enum_obj, enum_method);
+ if (rb_block_given_p()) {
+ ptr->proc = rb_block_proc();
+ ptr->iter = enumerator_iter_i;
+ }
+ else {
+ ptr->iter = (VALUE (*) _((VALUE, struct enumerator *)))rb_yield;
+ }
+ ptr->args = enum_args;

- return Qnil;
+ return obj;
}

+static VALUE enumerator_iter _((VALUE));
static VALUE
enumerator_iter(memo)
- NODE *memo;
+ VALUE memo;
{
- return rb_apply(memo->u1.value, memo->u2.id, memo->u3.value);
+ struct enumerator *e = (struct enumerator *)memo;
+
+ return method_call(e->method, e->args);
}

@@ -152,14 +258,29 @@ enumerator_each(obj)
VALUE obj;
{
- VALUE val;
+ struct enumerator *e = enumerator_ptr(obj);

- obj = (VALUE)rb_node_newnode(NODE_MEMO,
- rb_ivar_get(obj, id_enum_obj),
- rb_to_id(rb_ivar_get(obj, id_enum_method)),
- rb_ivar_get(obj, id_enum_args));
- val = rb_iterate((VALUE (*)_((VALUE)))enumerator_iter, obj, rb_yield, 0);
+ return rb_iterate(enumerator_iter, (VALUE)e, e->iter, (VALUE)e);
+}
+


+static VALUE
+enumerator_with_index_i(val, memo)
+ VALUE val, *memo;
+{
+ val = rb_yield_values(2, val, INT2FIX(*memo));
+ ++*memo;

return val;


}

+static VALUE
+enumerator_with_index(obj)
+ VALUE obj;
+{

+ struct enumerator *e = enumerator_ptr(obj);


+ VALUE memo = 0;
+

+ return rb_iterate(enumerator_iter, (VALUE)e,


+ enumerator_with_index_i, (VALUE)&memo);
+}
+
void
Init_enumerator()

@@ -181,6 +302,8 @@ Init_enumerator()
rb_include_module(rb_cEnumerator, rb_mEnumerable);

+ rb_define_alloc_func(rb_cEnumerator, enumerator_allocate);


rb_define_method(rb_cEnumerator, "initialize", enumerator_initialize, -1);
rb_define_method(rb_cEnumerator, "each", enumerator_each, 0);
+ rb_define_method(rb_cEnumerator, "with_index", enumerator_with_index, 0);

sym_each = ID2SYM(rb_intern("each"));

@@ -189,7 +312,6 @@ Init_enumerator()
sym_each_cons = ID2SYM(rb_intern("each_cons"));

- id_new = rb_intern("new");
- id_enum_obj = rb_intern("enum_obj");
- id_enum_method = rb_intern("enum_method");
- id_enum_args = rb_intern("enum_args");
+#if !defined(HAVE_RB_PROC_CALL) || !defined(HAVE_RB_METHOD_CALL)
+ id_call = rb_intern("call");
+#endif
}
Index: ext/enumerator/extconf.rb
===================================================================
RCS file: /cvs/ruby/src/ruby/ext/enumerator/extconf.rb,v
retrieving revision 1.1
diff -U2 -p -r1.1 extconf.rb
--- ext/enumerator/extconf.rb 1 Nov 2004 05:04:04 -0000 1.1
+++ ext/enumerator/extconf.rb 23 Jun 2005 02:55:54 -0000
@@ -1,2 +1,5 @@
require 'mkmf'
+
+%w"rb_obj_method rb_method_call".all? {|f| have_func(f, "ruby.h")}
+have_func("rb_proc_call", "ruby.h")
create_makefile('enumerator')

--
Nobu Nakada


Yukihiro Matsumoto

unread,
Jul 11, 2005, 3:48:29 AM7/11/05
to
Hi,

In message "Re: accessing index inside map"

on Mon, 11 Jul 2005 16:41:45 +0900, nobuyoshi nakada <nobuyosh...@ge.com> writes:

|> Can you commit this patch to the HEAD?
|
|For performance, I'd like to make Enumerator a subclass of
|Data instead of Object, in the HEAD.

It's all up to you. Although I feel no need to check rb_proc_call,
since enumerator is bundled with Ruby itself.

matz.


nobuyoshi nakada

unread,
Jul 11, 2005, 4:37:07 AM7/11/05
to
Hi,

At Mon, 11 Jul 2005 16:48:29 +0900,
Yukihiro Matsumoto wrote in [ruby-talk:147737]:


> |> Can you commit this patch to the HEAD?
> |
> |For performance, I'd like to make Enumerator a subclass of
> |Data instead of Object, in the HEAD.
>
> It's all up to you. Although I feel no need to check rb_proc_call,
> since enumerator is bundled with Ruby itself.

Static function proc_call() is defined now but rb_proc_call()
is not. Also rb_obj_method() and rb_method_call().

--
Nobu Nakada


Yukihiro Matsumoto

unread,
Jul 11, 2005, 5:10:17 AM7/11/05
to
Hi,

In message "Re: accessing index inside map"

on Mon, 11 Jul 2005 17:37:07 +0900, nobuyoshi nakada <nobuyosh...@ge.com> writes:

|> It's all up to you. Although I feel no need to check rb_proc_call,
|> since enumerator is bundled with Ruby itself.
|
|Static function proc_call() is defined now but rb_proc_call()
|is not. Also rb_obj_method() and rb_method_call().

I know. But if you need those functions, just export (with proper
prefix). I see no need for have_func() check in extconf.rb.

matz.


"Peña, Botp"

unread,
Jul 11, 2005, 6:18:19 AM7/11/05
to
nobuyoshi nakada [mailto:nobuyosh...@ge.com] wrote:

#> You're vague plan produces clearer ruby..
#>
#> but isn't
#> each_with_index.map
#>
#> so close to being
#> map_with_index
#>
#> ?
#>
#> (1..6).map_with_index{|x,i|[2,5].include?(i) ? x : x*2} is
#very easy to my
#> tiny brain since all i have to remember is map (just like
#(in array) all i
#> have to focus is each, then i follow thru with each_with_index)
#
#What about Enumerator#with_index?
#
# [1,2,3,4,5,6].map.with_index {|x,i|[2,5].include?(i) ? x : x*2}

imho, #with_index pleads for a better name. It sounds far from being a
method; which leads me to incline back to your original proposition of

each_with_index.map...

another concern also is that sir matz already warned me of the spilling of
the *_with_index naming all over the place..

OK, i'll be shooting for the moon here. How about passing a parameter? Is it
then possible to make #each or #map same behavior w #each_with_index or
#map_with_index or whatever *_with_index so that the ff works,

def each(with_index=true) do... end

def map(with_index=true) do ... end

and thus, we can do

[1,2,3,4,5,6].map(true) {|x,i|[2,5].include?(i) ? x : x*2}

I think this one does not break old code, no?


I find index being more of an attribute than a pure method.

And you are all right, I just need the index, why do i need another set of
methods?

Again, imho.

Thanks and kind regards -botp


#
#--
#Nobu Nakada
#


David A. Black

unread,
Jul 11, 2005, 6:48:06 AM7/11/05
to
This message is in MIME format. The first part should be readable text,
while the remaining parts are likely unreadable without MIME-aware tools.

David A. Black

unread,
Jul 11, 2005, 7:07:37 AM7/11/05
to

"Peña, Botp"

unread,
Jul 11, 2005, 7:12:26 AM7/11/05
to
dblack@wobblini wrote:

#> OK, i'll be shooting for the moon here. How about passing a parameter? Is
it
#> then possible to make #each or #map same behavior w #each_with_index or
#> #map_with_index or whatever *_with_index so that the ff works,
#>
#> def each(with_index=true) do... end
#>
#> def map(with_index=true) do ... end
#>
#> and thus, we can do
#>
#> [1,2,3,4,5,6].map(true) {|x,i|[2,5].include?(i) ? x : x*2}
#
#I'd hope not to see that. I'm actually hoping that maybe someday all
#the boolean flag methods (like instance_methods(true) etc.) will be
#changed :-) They've always seemed to me to be the most cryptic thing
#in all of Ruby.
#

Yes, you are correct. But there is some sort of give and take here or
balance if i may say (i think i heard the word balance fr you, sir David)..

How about

[1,2,3,4,5,6].map(WITH_INDEX) {|x,i|[2,5].include?(i) ? x : x*2}
or
[1,2,3,4,5,6].map(with_index=>true) {|x,i|[2,5].include?(i) ? x : x*2}

if we don't pass flags or attribs, wouldn't that make creating additional
methods like

each
each_with_index
map
map_with_index
collect
collect_with_index
..

?

it's with_index es all over..


Also, if we create a with_index method, wouldn't that be invoking another
method just for the index, like

each_with_index.map
or
map.with_index

Wouldn't my algorithm slow down? Does not the chain begs for a single method
-which brings us back to the original question...?


kind regards -botp


#David


Robert Klemme

unread,
Jul 11, 2005, 7:39:45 AM7/11/05
to
David A. Black wrote:
> Hi --

>
> On Mon, 11 Jul 2005, [iso-8859-1] "Peña, Botp" wrote:
>
>> OK, i'll be shooting for the moon here. How about passing a
>> parameter? Is it then possible to make #each or #map same behavior w
>> #each_with_index or #map_with_index or whatever *_with_index so that
>> the ff works,
>>
>> def each(with_index=true) do... end
>>
>> def map(with_index=true) do ... end
>>
>> and thus, we can do
>>
>> [1,2,3,4,5,6].map(true) {|x,i|[2,5].include?(i) ? x : x*2}
>
> I'd hope not to see that. I'm actually hoping that maybe someday all
> the boolean flag methods (like instance_methods(true) etc.) will be
> changed :-) They've always seemed to me to be the most cryptic thing
> in all of Ruby.

To second that: it's generally considered not very clean to have a flag
change a method's behavior. I think this is a case where another method
is superior to having one method behavior switched. Even from an
implementation perspective: either you end up with a single method that
consists of if-then-else on the top level with one implementation in each
branch or you end up with a lot of if-then-else inside the looping. The
latter is inefficient and the former is more efficient but then you can
have to methods anyway.

Kind regards

robert

Jim Freeze

unread,
Jul 11, 2005, 8:27:35 AM7/11/05
to
* "Pea, Botp" <bo...@delmonte-phil.com> [2005-07-11 20:12:26 +0900]:

> How about
>
> [1,2,3,4,5,6].map(WITH_INDEX) {|x,i|[2,5].include?(i) ? x : x*2}
> or
> [1,2,3,4,5,6].map(with_index=>true) {|x,i|[2,5].include?(i) ? x : x*2}

How about a more 'rails' like implementation:

[1,2,3].map(:with_index) { |x,ix| ... }
[1,2,3].collect(:with_index) { |x,ix| ... }
[1,2,3].collect!(:with_index) { |x,ix| ... }
[1,2,3].each(:with_index) { |x,ix| ... }
[1,2,3].each { |x| ... }

--
Jim Freeze


daz

unread,
Jul 11, 2005, 8:57:16 AM7/11/05
to

Peña, Botp wrote:
>
> [...] wouldn't that make creating additional methods like

>
> each
> each_with_index
> map
> map_with_index
> collect
> collect_with_index
> ..
>
> ?
>
> it's with_index es all over..
>

It is. None are required; not even each_with_index.

>
> Also, if we create a with_index method, wouldn't that be invoking another
> method just for the index, like
>
> each_with_index.map
> or
> map.with_index
>
> Wouldn't my algorithm slow down? Does not the chain begs for a single method
> -which brings us back to the original question...?
>

Yes. I'm taking sedatives while it's seriously being considered that
an Array should become an Enumerator in order to use its #with_index method
on an intermediate #map object to produce ... an Array ... instead of the
regular #map method with an optional index.

I would have posted a link to the ruby-core thread, but it was my
first post to a mailing list and my MAIL settings were different
from my NEWS settings, so it's messed up (quoted-printable MIME).

Here's a text version (minor EDITs). I'm sure it'll be relevant to you.

http://www.d10.karoo.net/ruby/xv/ruby-core-1495.txt

You'll see examples using bang! methods.

(( I'd be fascinated to see map!.with_index or map.with_index!
produce anything intelligible using require 'enumerator'. ))


>
> kind regards -botp
>

daz

David A. Black

unread,
Jul 11, 2005, 1:55:27 PM7/11/05
to

David A. Black

unread,
Jul 11, 2005, 1:58:47 PM7/11/05
to

Eric Hodel

unread,
Jul 11, 2005, 7:26:48 PM7/11/05
to

On 11 Jul 2005, at 05:27, Jim Freeze wrote:

> How about a more 'rails' like implementation:
>
> [1,2,3].map(:with_index) { |x,ix| ... }
> [1,2,3].collect(:with_index) { |x,ix| ... }
> [1,2,3].collect!(:with_index) { |x,ix| ... }
> [1,2,3].each(:with_index) { |x,ix| ... }
> [1,2,3].each { |x| ... }

Ugh. My impression has always been that they couldn't make up their
minds, or, didn't think hard enough about picking good names the
first time around.

--
Eric Hodel - drb...@segment7.net - http://segment7.net
FEC2 57F1 D465 EB15 5D6E 7C11 332A 551C 796C 9F04

"Peña, Botp"

unread,
Jul 11, 2005, 8:31:03 PM7/11/05
to
daz [mailto:do...@d10.karoo.co.uk] wrote:

#> Wouldn't my algorithm slow down? Does not the chain begs for
#a single method
#> -which brings us back to the original question...?
#>
#
#Yes. I'm taking sedatives while it's seriously being considered that
#an Array should become an Enumerator in order to use its
##with_index method
#on an intermediate #map object to produce ... an Array ...
#instead of the
#regular #map method with an optional index.
#
#I would have posted a link to the ruby-core thread, but it was my
#first post to a mailing list and my MAIL settings were different
#from my NEWS settings, so it's messed up (quoted-printable MIME).
#
#Here's a text version (minor EDITs). I'm sure it'll be
#relevant to you.
#
# http://www.d10.karoo.net/ruby/xv/ruby-core-1495.txt
#
#You'll see examples using bang! methods.
#
#(( I'd be fascinated to see map!.with_index or map.with_index!
# produce anything intelligible using require 'enumerator'. ))
#

cool. you're a ruby hacker indeed.

but imho (my opinion only) i do not like the ||.x notation.

how about

# we only need the index
[1,2,3].each() {|i|* printf i,"\n"}

# we need the val and the index
[1,2,3].each() {|x,i|* printf x,i,"\n"}

# in other words, we always get the last param as the index
[[0,1],[1,2],[3,4]].each() {|x,y,i|* printf x,i,"\n"}

ie, we put a star after the param delimiters || to signal an index. It's
like telling the programmer/reader "*hint-hint*"

kind regards -botp

#daz
#
#
#
#


daz

unread,
Jul 11, 2005, 10:41:01 PM7/11/05
to

Peña, Botp wrote:
>
> [...] but imho (my opinion only) i do not like the ||.x notation.

OK. You're not the first and you won't be the last ;-)
But more important than how it looks is whether it alters meaning.

>
> how about
>
> # we only need the index
> [1,2,3].each() {|i|* printf i,"\n"}
>
> # we need the val and the index
> [1,2,3].each() {|x,i|* printf x,i,"\n"}
>
> # in other words, we always get the last param as the index
> [[0,1],[1,2],[3,4]].each() {|x,y,i|* printf x,i,"\n"}
>
> ie, we put a star after the param delimiters || to signal an index. It's
> like telling the programmer/reader "*hint-hint*"
>

[1].each { |n, i| x=n; p x } #-> 1
[1].each { |n, i|* x=n; p x } #-> [1]

Great care is needed when adding beyond the |bars| into code territory.
But /no damage/ is caused by thinking aloud :-)

For #each_with_index, the index is passed as a block parameter.
That method has _two_ block parameters. That's probably why we
expect to see the index there in an alternative implementation
with the same result.

[2].each { |n | p [n ] } #-> [2]
[2].each_with_index { |n, index | p [n, index] } #-> [2, 0]

Let's say that we were offered no choice of name and that an
automatic block index were called $INDX.

[2].meth { |n | p [n ] } #-> [2]
[2].meth { |n | p [n, $INDX] } #-> [2, 0]

#meth passes _one_ argument and, regardless of whether or not
we use the $INDX, it still only passes _one_ argument.
'$INDX' isn't passed at all ... it's just ... /there/.

IMPO, auto-index-naming wouldn't belong between the bars but I
would be interested in hearing contrasting views.

Your suggestion, correctly, highlights the problem that an extra
block parameter, by itself, doesn't indicate to the parser that
it's an index. Your extra '*' marker does that.

Interesting.


daz

"Peña, Botp"

unread,
Jul 11, 2005, 11:23:26 PM7/11/05
to
daz [mailto:do...@d10.karoo.co.uk] wrote:

#> how about
#>
#> # we only need the index
#> [1,2,3].each() {|i|* printf i,"\n"}
#>
#> # we need the val and the index
#> [1,2,3].each() {|x,i|* printf x,i,"\n"}
#>
#> # in other words, we always get the last param as the index
#> [[0,1],[1,2],[3,4]].each() {|x,y,i|* printf x,i,"\n"}
#>
#> ie, we put a star after the param delimiters || to signal an
#index. It's
#> like telling the programmer/reader "*hint-hint*"
#>
#
#[1].each { |n, i| x=n; p x } #-> 1
#[1].each { |n, i|* x=n; p x } #-> [1]

ouch! am i hopeless here ? :-(

how about we give more affinity to |n,i|* than to *x ? The spacing clearly
shows, right?


#
#Great care is needed when adding beyond the |bars| into code territory.
#But /no damage/ is caused by thinking aloud :-)

yes, thanks.

#
#For #each_with_index, the index is passed as a block parameter.
#That method has _two_ block parameters. That's probably why we
#expect to see the index there in an alternative implementation
#with the same result.
#
#[2].each { |n | p [n ] } #-> [2]
#[2].each_with_index { |n, index | p [n, index] } #-> [2, 0]
#
#Let's say that we were offered no choice of name and that an
#automatic block index were called $INDX.
#
#[2].meth { |n | p [n ] } #-> [2]
#[2].meth { |n | p [n, $INDX] } #-> [2, 0]
#
##meth passes _one_ argument and, regardless of whether or not
#we use the $INDX, it still only passes _one_ argument.
#'$INDX' isn't passed at all ... it's just ... /there/.
#

correct. a $ variable would be great also.
I'm not sure though how it will behave in nested loops or blocks though..

#IMPO, auto-index-naming wouldn't belong between the bars but I
#would be interested in hearing contrasting views.

imho, i treat blocks as just that, blocks. I do not want it to carry any
baggage coming fr outside, including the index. But that is just me.

#
#Your suggestion, correctly, highlights the problem that an extra
#block parameter, by itself, doesn't indicate to the parser that
#it's an index. Your extra '*' marker does that.

I couldn't think of any simple yet visible marker, so the star "*".

kind regards -botp


#
#Interesting.
#
#

nobuyoshi nakada

unread,
Jul 11, 2005, 11:56:50 PM7/11/05
to
Hi,

At Fri, 8 Jul 2005 23:02:11 +0900,
Yukihiro Matsumoto wrote in [ruby-talk:147565]:


> We have vague plan to make enumerating method to return Enumerator

> when no block is given in the future, so that
>

> require 'enumerator'
> (1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i) ? x : x*2}
>
> would be
>
> (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}


>
> then. It's much simpler isn't it?

Is this plan going on?

I noticed that #map and #collect return an Array if no block
is given, instead of raising an exception.


Index: enum.c
===================================================================
RCS file: /cvs/ruby/src/ruby/enum.c,v
retrieving revision 1.58
diff -U2 -p -r1.58 enum.c
--- enum.c 30 Jun 2005 16:45:33 -0000 1.58
+++ enum.c 12 Jul 2005 03:54:56 -0000
@@ -18,4 +18,63 @@ VALUE rb_mEnumerable;
static ID id_each, id_eqq, id_cmp;

+#ifndef BUILTIN_ENUMERATOR
+#define BUILTIN_ENUMERATOR 1
+#endif
+
+#if BUILTIN_ENUMERATOR
+#define Init_enumerator static init_enumerator
+#include "ext/enumerator/enumerator.c"
+#undef Init_enumerator
+#else
+static ID id_enumerator;
+#endif
+
+void
+Init_enumerator()
+{
+#if BUILTIN_ENUMERATOR
+ char *src = ruby_sourcefile = rb_source_filename("enumerator");
+ init_enumerator();
+ rb_provide(src);
+#else
+ id_enumerator = rb_intern("Enumerator");
+ rb_autoload(rb_mEnumerable, id_enumerator, "enumerator");


+#endif
+}
+
+static VALUE

+enumeratorize(argc, argv, obj)
+ int argc;
+ VALUE *argv;
+ VALUE obj;
+{
+ VALUE enum_method = ID2SYM(rb_frame_this_func());
+#if BUILTIN_ENUMERATOR
+ VALUE enum_obj = enumerator_allocate(rb_cEnumerator);
+ struct enumerator *ptr = enumerator_ptr(enum_obj);
+
+ ptr->method = rb_obj_method(obj, enum_method);


+ if (rb_block_given_p()) {
+ ptr->proc = rb_block_proc();
+ ptr->iter = enumerator_iter_i;
+ }
+ else {
+ ptr->iter = (VALUE (*) _((VALUE, struct enumerator *)))rb_yield;
+ }

+ if (argc) ptr->args = rb_ary_new4(argc, argv);
+
+ return enum_obj;
+#else
+ VALUE enumerator = rb_const_get(rb_mEnumerable, id_enumerator);
+ VALUE args = rb_ary_new2(argc + 2);
+
+ RARRAY(args)->ptr[0] = obj;
+ RARRAY(args)->ptr[1] = enum_method;
+ MEMCPY(RARRAY(args)->ptr + 2, argv, VALUE, argc);
+ RARRAY(args)->len = argc + 2;
+ return rb_class_new_instance(RARRAY(args)->len, RARRAY(args)->ptr, enumerator);
+#endif
+}
+
VALUE
rb_each(obj)
@@ -115,4 +174,6 @@ enum_find(argc, argv, obj)

rb_scan_args(argc, argv, "01", &if_none);
+ if (!rb_block_given_p())
+ return enumeratorize(argc, argv, obj);
rb_iterate(rb_each, obj, find_i, (VALUE)&memo);
if (memo != Qundef) {
@@ -152,6 +213,9 @@ enum_find_all(obj)
VALUE obj;
{
- VALUE ary = rb_ary_new();
+ VALUE ary;

+ if (!rb_block_given_p()) return enumeratorize(0, 0, obj);
+
+ ary = rb_ary_new();
rb_iterate(rb_each, obj, find_all_i, ary);

@@ -184,6 +248,9 @@ enum_reject(obj)
VALUE obj;
{
- VALUE ary = rb_ary_new();
+ VALUE ary;

+ if (!rb_block_given_p()) return enumeratorize(0, 0, obj);
+
+ ary = rb_ary_new();
rb_iterate(rb_each, obj, reject_i, ary);

@@ -226,7 +293,10 @@ enum_collect(obj)
VALUE obj;
{
- VALUE ary = rb_ary_new();
+ VALUE ary;
+
+ if (!rb_block_given_p()) return enumeratorize(0, 0, obj);

- rb_iterate(rb_each, obj, rb_block_given_p() ? collect_i : collect_all, ary);
+ ary = rb_ary_new();
+ rb_iterate(rb_each, obj, collect_i, ary);

return ary;
@@ -344,4 +414,6 @@ enum_partition(obj)
VALUE ary[2];

+ if (!rb_block_given_p()) return enumeratorize(0, 0, obj);
+
ary[0] = rb_ary_new();
ary[1] = rb_ary_new();
@@ -477,4 +549,6 @@ enum_sort_by(obj)
long i;

+ if (!rb_block_given_p()) return enumeratorize(0, 0, obj);
+
if (TYPE(obj) == T_ARRAY) {
ary = rb_ary_new2(RARRAY(obj)->len);
@@ -764,5 +838,6 @@ enum_min_by(obj)
VALUE memo[2];

- rb_need_block();
+ if (!rb_block_given_p()) return enumeratorize(0, 0, obj);
+
memo[0] = Qundef;
memo[1] = Qnil;
@@ -807,5 +882,6 @@ enum_max_by(obj)
VALUE memo[2];

- rb_need_block();
+ if (!rb_block_given_p()) return enumeratorize(0, 0, obj);
+
memo[0] = Qundef;
memo[1] = Qnil;
@@ -845,4 +921,6 @@ enum_member(obj, val)
VALUE memo[2];

+ if (!rb_block_given_p()) return enumeratorize(1, &val, obj);
+
memo[0] = val;
memo[1] = Qfalse;
@@ -882,5 +960,6 @@ enum_each_with_index(obj)
VALUE memo = 0;

- rb_need_block();
+ if (!rb_block_given_p()) return enumeratorize(0, 0, obj);
+
rb_iterate(rb_each, obj, each_with_index_i, (VALUE)&memo);
return obj;
Index: inits.c
===================================================================
RCS file: /cvs/ruby/src/ruby/inits.c,v
retrieving revision 1.9
diff -U2 -p -r1.9 inits.c
--- inits.c 19 Dec 2003 03:58:57 -0000 1.9
+++ inits.c 12 Jul 2005 03:49:43 -0000
@@ -19,4 +19,5 @@ void Init_Comparable _((void));
void Init_Dir _((void));
void Init_Enumerable _((void));
+void Init_enumerator _((void));
void Init_Exception _((void));
void Init_syserr _((void));
@@ -81,4 +82,5 @@ rb_call_inits()
Init_GC();
Init_marshal();
+ Init_enumerator();
Init_version();
}

--
Nobu Nakada


Yukihiro Matsumoto

unread,
Jul 12, 2005, 12:08:33 AM7/12/05
to
Hi,

In message "Re: accessing index inside map"

on Tue, 12 Jul 2005 12:56:50 +0900, nobuyoshi nakada <nobuyosh...@ge.com> writes:

|> (1..6).each_with_index.map{|x,i|[2,5].include?(i) ? x : x*2}
|>
|> then. It's much simpler isn't it?
|
|Is this plan going on?
|
|I noticed that #map and #collect return an Array if no block
|is given, instead of raising an exception.

I agree with the plan, so that someone come up with the implementation
before I do, I'd glad to merge it. ... boy, you did that.

I think it's a good chance. Can you merge enumerator in the following
steps?

* merge ext/enumerator/enumerator.c to enum.c
* remove ext/enumerator directory.
* patch enum.c as you did.

matz.


nobuyoshi nakada

unread,
Jul 12, 2005, 2:17:53 AM7/12/05
to
Hi,

At Tue, 12 Jul 2005 13:08:33 +0900,
Yukihiro Matsumoto wrote in [ruby-talk:147835]:


> I think it's a good chance. Can you merge enumerator in the following
> steps?
>
> * merge ext/enumerator/enumerator.c to enum.c
> * remove ext/enumerator directory.
> * patch enum.c as you did.

Enumerator needs its own initialization routine to be called
after Init_GC() got run, so one alternative Would be to move
enumerator.c to the top directory.

Of course, they are still small enough to merge.

Which is better?

--
Nobu Nakada


Yukihiro Matsumoto

unread,
Jul 12, 2005, 2:47:35 AM7/12/05
to
Hi,

In message "Re: accessing index inside map"

on Tue, 12 Jul 2005 15:17:53 +0900, nobuyoshi nakada <nobuyosh...@ge.com> writes:

|Enumerator needs its own initialization routine to be called
|after Init_GC() got run, so one alternative Would be to move
|enumerator.c to the top directory.

Adding Init_Enumerable() as well in the enum.c seems enough for me.
Don't forget to modify inits.c too.

matz.


Martin DeMello

unread,
Jul 12, 2005, 4:37:52 AM7/12/05
to
David A. Black <dbl...@wobblini.net> wrote:
>
> I can understand that a general solution makes sense, though I'm still
> very unclear on the whole notion of "index" as it pertains to
> Enumerable.

If we're making this general, I till think 'counter' would be a far better
name than 'index'

martin

Brian Candler

unread,
Jul 12, 2005, 5:04:03 AM7/12/05
to
On Mon, Jul 11, 2005 at 08:07:37PM +0900, David A. Black wrote:
> I'm probably the person who has asked for map_with_index the most, and
> I promise not to ask for *_with_index :-)

>
> I can understand that a general solution makes sense, though I'm still
> very unclear on the whole notion of "index" as it pertains to
> Enumerable.

I'm joining this converstation late, but ISTM that the fundamental issue
here is that one object may have multiple enumeration methods (e.g. #each
and #each_with_index), but the methods in Enumerable are only able to call
#each. So perhaps a solution is to be able to choose the name of the
enumerator method to call.

e.g.

module Enumerable
def altmap(meth=:each)
res = []
send(meth) { |*args| res << yield(*args) }
res
end
end

a = [10,20,30,40]
p a.altmap { |x| x*2 } #=> [20, 40, 60, 80]
p a.altmap(:each_with_index) { |x,i| x+i } #=> [10, 21, 32, 43]

h = {1=>"one", 2=>"two", 3=>"three"}
p h.altmap(:each_key) { |x| x + x } #=> [2, 4, 6]
p h.altmap(:each_value) { |x| x + x } #=> ["oneone", "twotwo", "threethree"]

Then, Enumerable doesn't have to care about the concept of what's an "index"
- conceptually we move #each_with_index into the object being enumerated,
and it can provide whatever index makes sense to it. (Enumerable could still
provide a default #each_with_index as it does now, though)

Aside: I always found each_with_index to be odd, because we have

myhash.each { |key,value| ... }
myarray.each_with_index { |value,key| ... }

I'd like to be able to think of an Array as a special case of Hash, where
the keys just happen to be Integers starting from 0.

This could be regularised if Hash#each_with_index did { |k,v| yield v,k }

Regards,

Brian.


Brian Candler

unread,
Jul 12, 2005, 5:33:45 AM7/12/05
to
On Tue, Jul 12, 2005 at 10:04:02AM +0100, Brian Candler wrote:
> module Enumerable
> def altmap(meth=:each)
> res = []
> send(meth) { |*args| res << yield(*args) }
> res
> end
> end

P.S. I note that some Enumerable methods already take arguments:
detect/find, grep, inject could have meth=:each as a later argument for
backwards-compatibility.

The only awkward ones are to_set and zip. Even then, they could be made
backwards-compatible by checking if the first argument is a Symbol and using
it as a method name in that case.

I don't really see the usefulness of the foo.to_set(klass,*args) syntax
anyway, given that it just calls klass.new(foo,*args). However, I can see
uses for
h.to_set(:each_key)
h.to_set(:each_value)

Regards,

Brian.


Robert Klemme

unread,
Jul 12, 2005, 5:48:06 AM7/12/05
to

IMHO the enumerator approach is slightly superior as your suggestion can
lead to conflicts with current method arguments as you have noticed.
Personally I don't have a problem using

a.to_enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

I think this is more flexible and modularized. And the overhead of a new
instance creation is usually neglectible compared to the iteration
overhead.

Kind regards

robert

Brian Candler

unread,
Jul 12, 2005, 6:02:30 AM7/12/05
to
> IMHO the enumerator approach is slightly superior as your suggestion can
> lead to conflicts with current method arguments as you have noticed.
> Personally I don't have a problem using
>
> a.to_enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>
> I think this is more flexible and modularized. And the overhead of a new
> instance creation is usually neglectible compared to the iteration
> overhead.

"Do the simplest thing which will possibly work" :-)

Actually I think my version reads better:

a.inject(0,:each_with_index) {|sum,(a,i)| sum + a*i}

"Starting with 0, call :each_with_index with the following block".

Regards,

Brian.


daz

unread,
Jul 12, 2005, 6:21:12 AM7/12/05
to

Peña, Botp wrote:
> nobuyoshi nakada wrote:
>
> #> i'd like to double the element values except those loc on
> #the 2nd and 5th
> #> indices eg.
> #
> #$ ruby -renumerator -e 'p
> #(1..6).enum_for(:each_with_index).map{|x,i|[2,5].include?(i) ?
> #x : x*2}'
> #[2, 4, 3, 8, 10, 6]
>
> Hi Nobu,
>
> Wow, cool. But my brain is very tiny compared to other rubyist brains.
>
> i was hoping for something straight like,
>
> >[1,2,3,4,5,6].map{|x,i|[2,5].include?(i) ? x : x*2}
> =>[2, 4, 3, 8, 10, 6]
>

i=-1;[1,2,3,4,5,6].map{|x|[2,5].include?(i+=1) ? x : x*2}
#=> [2, 4, 3, 8, 10, 6]

You can, of course, use this idiom to emulate *_with_index
functionality for (almost?) any iterator method.


daz
--

Never seek simplicity where complexity will suffice.
- (paraphrase ;-)


Robert Klemme

unread,
Jul 12, 2005, 7:19:14 AM7/12/05
to
Brian Candler wrote:
>> IMHO the enumerator approach is slightly superior as your suggestion
>> can lead to conflicts with current method arguments as you have
>> noticed. Personally I don't have a problem using
>>
>> a.to_enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>>
>> I think this is more flexible and modularized. And the overhead of
>> a new instance creation is usually neglectible compared to the
>> iteration overhead.
>
> "Do the simplest thing which will possibly work" :-)

Well, apparently we have differing opinions on this. Personally I find
the Enumerator approach simpler as it does not clutter the original's
class interface (introducing an optional parameter on each method doubles
the number of legal invocations). :-)

> Actually I think my version reads better:
>
> a.inject(0,:each_with_index) {|sum,(a,i)| sum + a*i}
>
> "Starting with 0, call :each_with_index with the following block".

There is a chance that folks who don't know #inject might read "start with
index 0".

Well, I think a vote is in order. Did you consider submitting this as
RCR?

Kind regards

robert

nobu....@softhome.net

unread,
Jul 12, 2005, 11:04:28 AM7/12/05
to
Hi,

At Tue, 12 Jul 2005 15:47:35 +0900,
Yukihiro Matsumoto wrote in [ruby-talk:147846]:


> |Enumerator needs its own initialization routine to be called
> |after Init_GC() got run, so one alternative Would be to move
> |enumerator.c to the top directory.
>
> Adding Init_Enumerable() as well in the enum.c seems enough for me.
> Don't forget to modify inits.c too.

I think "enumerator" feature is needed to be provided for
backward compatibility's sake. And rb_provide() can't be
called before Init_load() (not Init_GC(), sorry).

# Now I'm merging documents in enumerator.txt to enumerator.c.

--
Nobu Nakada


Brian Candler

unread,
Jul 12, 2005, 12:10:19 PM7/12/05
to
> Well, apparently we have differing opinions on this. Personally I find
> the Enumerator approach simpler as it does not clutter the original's
> class interface (introducing an optional parameter on each method doubles
> the number of legal invocations). :-)

My own interpretation of "do the simplest" includes "don't introduce any
unnecessary abstractions". Arguably, the 'optional' parameter is always
there; it just happens to default to :each. There are lots of similar cases
in the Ruby standard library.

The Enumerator stuff is new to me, since I'm still just using 1.8.2.
However, I find obj.to_enum(:each_with_index) confusing; after all, obj is
*already* enumerable. Making this more general, you might get

obj.rename(:each=>:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

Creating an object just to say "call #each_with_index instead of #each"
seems wasteful unless you plan to re-use it, given that you could just call
#each_with_index in the first place.

However, perhaps 'to_enum' could be called something friendlier, e.g.

obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

In fact, even

obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

reads better to me, as it's not stressing the creation of an intermediate
object. to_foo looks like you are converting obj into something completely
different, rather than just adding a temporary wrapper.

From the OP's question, then, it's a question of

[1,2,3,4,5,6].map(:each_with_index) { |x,i| [2,5].include?(i) ? x : x*2 }

versus

[1,2,3,4,5,6].enum(:each_with_index).map { |x,i| [2,5].include?(i) ? x : x*2 }

I still prefer the first :-) It's a mapping operation, applied directly to
an Array.

> Well, I think a vote is in order. Did you consider submitting this as
> RCR?

I'm not familiar with the RCR process.

I just thought I'd wait and see what the list members (and Matz in
particular) thought. This idea seems sufficiently obvious that I guess
there's some reason why it has not been implemented already.

Regards,

Brian.


David A. Black

unread,
Jul 12, 2005, 12:37:23 PM7/12/05
to
Hi --

On Wed, 13 Jul 2005, Brian Candler wrote:

>> Well, apparently we have differing opinions on this. Personally I find
>> the Enumerator approach simpler as it does not clutter the original's
>> class interface (introducing an optional parameter on each method doubles
>> the number of legal invocations). :-)
>
> My own interpretation of "do the simplest" includes "don't introduce any
> unnecessary abstractions". Arguably, the 'optional' parameter is always
> there; it just happens to default to :each. There are lots of similar cases
> in the Ruby standard library.
>
> The Enumerator stuff is new to me, since I'm still just using 1.8.2.
> However, I find obj.to_enum(:each_with_index) confusing; after all, obj is
> *already* enumerable. Making this more general, you might get
>
> obj.rename(:each=>:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>
> Creating an object just to say "call #each_with_index instead of #each"
> seems wasteful unless you plan to re-use it, given that you could just call
> #each_with_index in the first place.
>
> However, perhaps 'to_enum' could be called something friendlier, e.g.
>
> obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>
> In fact, even
>
> obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>
> reads better to me, as it's not stressing the creation of an intermediate
> object. to_foo looks like you are converting obj into something completely
> different, rather than just adding a temporary wrapper.

I wonder whether one might (re)introduce the "iter[ator]" set of
terms, rather than enumerator. I'm not totally clear on the relation
between them, so I may be off the mark, but I was thinking of things
like:

obj.iterate_with(:each_with_index).inject ...

a little bit on the model of sort_by. Then again, this may be getting
dangerously close to the rejected RCR at
http://www.rcrchive.net/rejected.html#rcr50.

I have to admit I haven't totally assimilated the Enumerator
mechanisms, which strike me as very useful and powerful but sort of a
secondary system overlaid on the whole iterator and/or enumerable
thing. I guess the barrier, for me, is having the separate Enumerator
object, rather than just having it happen in the course of calling a
method. I guess that's also what's powerful about it :-)

> I'm not familiar with the RCR process.

See http://www.rcrchive.net.


David

--
David A. Black
dbl...@wobblini.net


nobu....@softhome.net

unread,
Jul 12, 2005, 12:46:17 PM7/12/05
to
Hi,

At Wed, 13 Jul 2005 01:10:19 +0900,
Brian Candler wrote in [ruby-talk:147876]:


> However, perhaps 'to_enum' could be called something friendlier, e.g.
>
> obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

`using' sounds too general.

> In fact, even
>
> obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>
> reads better to me, as it's not stressing the creation of an intermediate
> object. to_foo looks like you are converting obj into something completely
> different, rather than just adding a temporary wrapper.

Enumerable#to_enum has an alias named as #enum_for.

--
Nobu Nakada


Daniel Brockman

unread,
Jul 12, 2005, 12:59:44 PM7/12/05
to
nobu....@softhome.net writes:

How about just `for'? That word is already strongly associated
with enumeration, and connects well to the `each_*'.

obj.for(:each_with_index).inject(0) { |sum, (a, i)| sum + a * i }

However, ``for each inject'' sounds pretty weird, since we're not
injecting something into each element --- we're injecting it into the
whole collection.

I don't know, just a thought.

--
Daniel Brockman <dan...@brockman.se>

Robert Klemme

unread,
Jul 13, 2005, 3:39:04 AM7/13/05
to
Brian Candler wrote:
>> Well, apparently we have differing opinions on this. Personally I
>> find the Enumerator approach simpler as it does not clutter the
>> original's class interface (introducing an optional parameter on
>> each method doubles the number of legal invocations). :-)
>
> My own interpretation of "do the simplest" includes "don't introduce
> any unnecessary abstractions". Arguably, the 'optional' parameter is
> always
> there; it just happens to default to :each. There are lots of similar
> cases
> in the Ruby standard library.
>
> The Enumerator stuff is new to me, since I'm still just using 1.8.2.

It's in 1.8.2 and I think it's been around for some time, maybe even as
far back as 1.7.*.

> However, I find obj.to_enum(:each_with_index) confusing; after all,
> obj is *already* enumerable.

Well, to_enum is rather a shorthand for to_enumerator. There's also
enum_for (as Nobu pointed out).

> Making this more general, you might get
>
> obj.rename(:each=>:each_with_index).inject(0) {|sum,(a,i)| sum +
> a*i}

With this naming I'd rather associate that :each is renamed on the
instance obj to :each_with_index and that this change is permanent. The
to_xyz notation clearly indicates a conversion i.e. you expect to get a
new isntance.

> Creating an object just to say "call #each_with_index instead of
> #each"
> seems wasteful unless you plan to re-use it, given that you could
> just call #each_with_index in the first place.

It's not that uncommon in OO oriented languages to create temporary
objects. For example command pattern has often this property: you create
an instance, set all properties you need for processing, execute it, fetch
the result and drop the instance again. This is a nice way of doing
housekeeping for temporary state that is solely associated with the
calculation.

The overhead in this case is neglectible in the general case, i.e., an
iteration that creates lots of new objects along the way.

> However, perhaps 'to_enum' could be called something friendlier, e.g.
>
> obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

using for what?

> In fact, even
>
> obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

That reads better IMHO.

> reads better to me, as it's not stressing the creation of an
> intermediate object. to_foo looks like you are converting obj into
> something completely different, rather than just adding a temporary
> wrapper.

Yeah, maybe. Anyway, this is far better than rename IMHO.

> From the OP's question, then, it's a question of
>
> [1,2,3,4,5,6].map(:each_with_index) { |x,i| [2,5].include?(i) ? x :
> x*2 }
>
> versus
>
> [1,2,3,4,5,6].enum(:each_with_index).map { |x,i| [2,5].include?(i)
> ? x : x*2 }
>
> I still prefer the first :-) It's a mapping operation, applied
> directly to
> an Array.
>
>> Well, I think a vote is in order. Did you consider submitting this
>> as RCR?
>
> I'm not familiar with the RCR process.

It's fairly easy: you register, create your RCR (there are some helpful
questions on the website) and submit it. Then you wait for comments.

DAB> See http://www.rcrchive.net.

> I just thought I'd wait and see what the list members (and Matz in
> particular) thought. This idea seems sufficiently obvious that I guess
> there's some reason why it has not been implemented already.

Certainly also a good approach. The main advantage I see with RCR's is
that there is a reference point and all the comments are tied together.

Kind regards

robert

Brian Candler

unread,
Jul 13, 2005, 4:47:43 AM7/13/05
to
On Wed, Jul 13, 2005 at 01:46:17AM +0900, nobu....@softhome.net wrote:
> > However, perhaps 'to_enum' could be called something friendlier, e.g.
> >
> > obj.using(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>
> `using' sounds too general.
>
> > In fact, even
> >
> > obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
> >
> > reads better to me, as it's not stressing the creation of an intermediate
> > object. to_foo looks like you are converting obj into something completely
> > different, rather than just adding a temporary wrapper.
>
> Enumerable#to_enum has an alias named as #enum_for.

The preposition "for" doesn't really sound right here. I don't think we are
enumerating "for" each_with_index; we are enumerating "with"
each_with_index, or "using", or perhaps "over".

The underscore also seems to break up the line visually. Now I know the full
details, I can understand the following, but there are two visual breaks
between the 'map' and the object being enumerated over:

obj.enum_for(:each_with_index).map { .. }
^ ^
break break

I think that

obj.enum(:each_with_index).map { .. }

is more clearly a single method call separating the object and its map.

The idea of an enumeration method returning an Enumerator if called without
a block is interesting:

obj.each_with_index.map { .. }

although this means that every enumeration method you write needs extra code
to support this behaviour. And I dread to think what happens if you start to
chain these methods :-)

This mechanism requires every programmer to do more work in their
enumeration methods. In that case, I still prefer to modify every method in
Enumerable instead: i.e.

obj.map(:each_with_index) { .. }

But only one person has commented on this so far.

Regards,

Brian.


Brian Candler

unread,
Jul 13, 2005, 5:34:01 AM7/13/05
to
> > The Enumerator stuff is new to me, since I'm still just using 1.8.2.
>
> It's in 1.8.2 and I think it's been around for some time, maybe even as
> far back as 1.7.*.

Ah, I was just going on "ri Enumerator", which shows only SyncEnumerator.

<< Rummages around >> - OK, so this is a C extension, which like most of
them, is undocumented in a running system. Pulling down the source though, I
find ext/enumerator/enumerator.txt

Perhaps someone would like to rdoc-merge this into the source?

Regards,

Brian.


daz

unread,
Jul 13, 2005, 5:21:30 AM7/13/05
to

Robert Klemme wrote:

> Brian Candler wrote:
>
> > In fact, even
> >
> > obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>
> That reads better IMHO.
>
> > reads better to me, as it's not stressing the creation of an
> > intermediate object. to_foo looks like you are converting obj into
> > something completely different, rather than just adding a temporary
> > wrapper.
>
> Yeah, maybe. Anyway, this is far better than rename IMHO.
>


[1]; obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}

[2]; i=-1; obj.inject(0) {|sum, a| sum + a*(i+=1)}

Can anyone shade me in on what, if any, advantage [1] has over [2]:
-- in terms of:

a) Readability
b) Ease of use and understanding
c) Performance
d) Flexibility
e) Accuracy
f) Memory usage
g) Anything else you care to throw in ;-)

I can tell at a glance that it's going to require more typing
because it contains the string '_with_index' ...

## keystrokes (including shifts on UK kbd)
# _with_index #-> 11/13
# i=-1; i+=1; #-> 10/11

But there seem to be so many other overheads in comparison,
that I can't focus on whether or not you're saying something
that I need to get my head around.

TIA,

daz

daz

unread,
Jul 13, 2005, 6:18:35 AM7/13/05
to

Brian Candler wrote:
>
> << Rummages around >> - OK, so this is a C extension, [...]
>

Nobu has transformed enumerator.c -- Try the latest:
http://tinyurl.com/7ljdw/ext/enumerator/enumerator.c?rev=1.6;content-type=text%2Fplain


daz


Brian Candler

unread,
Jul 13, 2005, 6:35:05 AM7/13/05
to
> [1]; obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
>
> [2]; i=-1; obj.inject(0) {|sum, a| sum + a*(i+=1)}
>
> Can anyone shade me in on what, if any, advantage [1] has over [2]:
> -- in terms of:
>
> a) Readability
> b) Ease of use and understanding
> c) Performance
> d) Flexibility
> e) Accuracy
> f) Memory usage
> g) Anything else you care to throw in ;-)

1. It retains the enapsulation of "iterating with an index"; you are
reimplementing it in your inner block. (Violation of DRY principle).

2. It's more general; it works with other iteration methods. Consider
Hash#each_value and Hash#each_key, or String#each_byte for example.

require 'enumerator'
"abc".to_enum(:each_byte).collect #=> [97, 98, 99]

Of course you could rewrite this too, but then you're reimplementing
'collect':

res = []
"abc".each_byte { |x| res << x }
res

3. If you were using the index in more than one place in the inner block, it
would become more verbose.

i=0; obj.inject(0) { ... use i ... use i ...; i+=1 }

Regards,

Brian.


Martin DeMello

unread,
Jul 13, 2005, 6:31:54 AM7/13/05
to
daz <do...@d10.karoo.co.uk> wrote:
>
> I can tell at a glance that it's going to require more typing
> because it contains the string '_with_index' ...

I've long since started renaming them to ewi and mwi in my own code.

martin

daz

unread,
Jul 13, 2005, 9:40:02 AM7/13/05
to

Brian Candler wrote:
> >
> > [1]; obj.enum(:each_with_index).inject(0) {|sum,(a,i)| sum + a*i}
> >
> > [2]; i=-1; obj.inject(0) {|sum, a| sum + a*(i+=1)}
> >
> > Can anyone shade me in on what, if any, advantage [1] has over [2]:
> > -- in terms of:
> >
> > a) Readability
> > b) Ease of use and understanding
> > c) Performance
> > d) Flexibility
> > e) Accuracy
> > f) Memory usage
> > g) Anything else you care to throw in ;-)
>
> 1. It retains the enapsulation of "iterating with an index";
>

OK. That's why I want it provided automatically inside the block.
It's information known to, but not revealed by, the iterator. We're the
programmers ... they're our blocks ... why can't we discover a fundamental
detail like which iteration we're on ??

> you are reimplementing it in your inner block.

Both examples implement an index. One as an external variable, the other
as an extra block parameter provided by #each_with_index.
I can't see anything I've reimplemented. We may be at cross-purposes ?


> (Violation of DRY principle).

Not so. There's no index, and I need one, so I have to create it.
So do you. It's no more a violation of DRY than having to
open a file _every_ flippin' time I want to read it.


> 2. It's more general; it works with other iteration methods. Consider
> Hash#each_value and Hash#each_key, or String#each_byte for example.
>

I've considered. I can't think of any that it wouldn't work with.
Is there anything more general than that ?

> require 'enumerator'
> "abc".to_enum(:each_byte).collect #=> [97, 98, 99]
>
> Of course you could rewrite this too, but then you're reimplementing
> 'collect':
>
> res = []
> "abc".each_byte { |x| res << x }
> res

OK, _that_ would be a reimplementation of 'collect' but I can't respond
with an example of what I'd do because you haven't used an index.
I'm talking index only, albeit within a slightly expanded topic.


However, dawning is in progress, perhaps :-)

# "abc".to_enum(:each_byte).collect #=> [97, 98, 99]

I was about to describe the #each_byte block as a "proxy receiver"
for #collect, but I think I'll wait before spreading untruths
because it feels reversed (?)

>
> 3. If you were using the index in more than one place in the inner block, it
> would become more verbose.
>
> i=0; obj.inject(0) { ... use i ... use i ...; i+=1 }
>

Absurd ;-)
If your block had 30 uses of the index, my version would have 31.

I'll say again - we may be mixing subjects (accessing an index
versus Enumeration and it's probably just me who doesn't know the
difference, yet).

>
> Brian.
>

Thanks BC,

daz

Brian Candler

unread,
Jul 14, 2005, 5:02:53 AM7/14/05
to
On Wed, Jul 13, 2005 at 07:20:53PM +0900, daz wrote:
> Nobu has transformed enumerator.c -- Try the latest:
> http://tinyurl.com/7ljdw/ext/enumerator/enumerator.c?rev=1.6;content-type=text%2Fplain

The documentation in enumerator.txt has not been merged in. How is it
different functionally?

I've diffed this file against ruby-1.8.2 and I see a number of changes but I
don't know what they're doing. Although I do see a couple of nice buffer
overflows :-)

+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_slice;
+ args[2] = n;

Doh!

Regards,

Brian.


nobuyoshi nakada

unread,
Jul 15, 2005, 1:46:39 AM7/15/05
to
Hi,

At Thu, 14 Jul 2005 18:02:53 +0900,
Brian Candler wrote in [ruby-talk:148087]:


> > Nobu has transformed enumerator.c -- Try the latest:
> > http://tinyurl.com/7ljdw/ext/enumerator/enumerator.c?rev=1.6;content-type=text%2Fplain
>
> The documentation in enumerator.txt has not been merged in. How is it
> different functionally?

Moved it to the top level and merged the document.

> I've diffed this file against ruby-1.8.2 and I see a number of changes but I
> don't know what they're doing. Although I do see a couple of nice buffer
> overflows :-)
>
> + VALUE args[2];
> + args[0] = obj;
> + args[1] = sym_each_slice;
> + args[2] = n;
>
> Doh!

Thank you, but the code has been rewritten. ;-)

--
Nobu Nakada


nobu....@softhome.net

unread,
Jul 15, 2005, 10:32:15 AM7/15/05
to
Hi,

At Fri, 15 Jul 2005 14:46:39 +0900,
nobuyoshi nakada wrote in [ruby-talk:148205]:


> At Thu, 14 Jul 2005 18:02:53 +0900,
> Brian Candler wrote in [ruby-talk:148087]:
> > > Nobu has transformed enumerator.c -- Try the latest:
> > > http://tinyurl.com/7ljdw/ext/enumerator/enumerator.c?rev=1.6;content-type=text%2Fplain
> >
> > The documentation in enumerator.txt has not been merged in. How is it
> > different functionally?
>
> Moved it to the top level and merged the document.

But I found rdoc can't handle a module/class enclosed in a
built-in module/class.

enum.c: m..........................
No definition for enum_join

enumerator.c: c
Enclosing class/module 'rb_mEnumerable' for class Enumerator not known
............


Index: lib/rdoc/parsers/parse_c.rb
===================================================================
RCS file: /cvs/ruby/src/ruby/lib/rdoc/parsers/parse_c.rb,v
retrieving revision 1.30
diff -U2 -p -r1.30 parse_c.rb
--- lib/rdoc/parsers/parse_c.rb 14 May 2005 02:23:45 -0000 1.30
+++ lib/rdoc/parsers/parse_c.rb 15 Jul 2005 14:30:23 -0000
@@ -228,4 +228,11 @@ module RDoc
enclosure = @classes[in_module]
unless enclosure
+ if enclosure = @known_classes[in_module]
+ handle_class_module(in_module, (/^rb_m/ =~ in_module ? "module" : "class"),
+ enclosure, nil, nil)
+ enclosure = @classes[in_module]
+ end
+ end
+ unless enclosure
warn("Enclosing class/module '#{in_module}' for " +
"#{class_mod} #{class_name} not known")

--
Nobu Nakada


0 new messages