Request for two methods in Array class

M

Mike Hall

Two small requests:

1. A new method, Array.combine (needs a better name for general use).
It takes entries from two (or many) arrays and combines them.
(like a combination of Array.zip, fetch, and map)

For my use, I needed multiplication, but a block would be more general.

a = [1,2,3]
b = [1,3,5]
a.combine(b) => [1,6,15]

Or with a block:

a.combine(b) {|x,y| x*y}

The new method would replace this sequence:

a.zip(b).map {|e| e[0]*e[1]}
or
a.zip(b,c,d).map {|e| e.inject(1) {|prod, n| prod*n} }


2. A block initializer for Array.new, like that for Hash.new.
Good for self-initializing a table of recurrence relations.

a = Array.new() {|self, i| 2*self[i-1] - self[i-2]}

a[0] = 1 # define the first two ...
a[1] = x

a[2] => 2x-1 # the rest are derived on the fly as needed
a[3] => ...


Hopefully, I'm not blind and haven't missed some obvious use
of the existing methods. My apologies if that's the case.

Whatever... just some thoughts. Are they worth adding?

Thanks!
 
S

Sean O'Dell

1. A new method, Array.combine (needs a better name for general use).
It takes entries from two (or many) arrays and combines them.
(like a combination of Array.zip, fetch, and map)

For my use, I needed multiplication, but a block would be more general.

a = [1,2,3]
b = [1,3,5]
a.combine(b) => [1,6,15]

Or with a block:

a.combine(b) {|x,y| x*y}

Or perhaps:

c = a.weave(b)
c #=> [[1, 1], [2, 3], [3, 5]]

...then you could:

c.collect! do {|item| item[0] * item[1]}

c #=> [1, 6, 15]

Sean O'Dell
 
M

Michael Neumann

Mike said:
Two small requests:

1. A new method, Array.combine (needs a better name for general use).
It takes entries from two (or many) arrays and combines them.
(like a combination of Array.zip, fetch, and map)

For my use, I needed multiplication, but a block would be more general.

a = [1,2,3]
b = [1,3,5]
a.combine(b) => [1,6,15]

Or with a block:

a.combine(b) {|x,y| x*y}

The new method would replace this sequence:

a.zip(b).map {|e| e[0]*e[1]}

It's the same length:

a.zip(b).map {|x,y| x*y}
# vs.
a.combine(b) {|x,y| x*y}

But I must say, that I like the name combine a lot.

Regards,

Michael
 
A

Ara.T.Howard

It's the same length:
a.zip(b).map {|x,y| x*y}
# vs.
a.combine(b) {|x,y| x*y}

but one less object creation
But I must say, that I like the name combine a lot.

ditto.

-a
--
===============================================================================
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| A flower falls, even though we love it;
| and a weed grows, even though we do not love it.
| --Dogen
===============================================================================
 
C

Charles Mills

but one less object creation


ditto.
Seems like a good idea. After reading the chapter of Meyers book
Lothar sent, the idea seems even better.
Another name idea is collect_wtih
c = a.collect_with(b) { |x,y| x*y }
a.collect_with!(b) { |x,y| x*y } # looks bad?
d = a.collect_with(b, c) { |x,y,z| x*y*z } # multiple params would be
useful
-Charlie
-a
--
=======================================================================
========
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| A flower falls, even though we love it;
| and a weed grows, even though we do not love it. | --Dogen
=======================================================================
========
 
C

Charles Mills

Seems like a good idea. After reading the chapter of Meyers book
Lothar sent, the idea seems even better.
Another name idea is collect_wtih
c = a.collect_with(b) { |x,y| x*y }
a.collect_with!(b) { |x,y| x*y } # looks bad?
d = a.collect_with(b, c) { |x,y,z| x*y*z } # multiple params would be
useful
Looks like this has been talked about by others:
http://www.ai.mit.edu/~gregs/ll1-discuss-archive-html/msg03386.html
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/29671

still seems useful to me though
c = a.collect_with(keys) { ... }
-Charlie
-a
--
======================================================================
=========
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| A flower falls, even though we love it;
| and a weed grows, even though we do not love it. | --Dogen
======================================================================
=========
 
Y

Yukihiro Matsumoto

Hi,

In message "Request for two methods in Array class"

|1. A new method, Array.combine (needs a better name for general use).
| It takes entries from two (or many) arrays and combines them.
| (like a combination of Array.zip, fetch, and map)

Sounds nice. But I'm not sure "combine" is a proper name for the
method. We need more discussion, for example:

* the best name for the behavior.
* whether it takes only one argument or any number of arguments.
* whether "multiplication" should be the default.

|2. A block initializer for Array.new, like that for Hash.new.
| Good for self-initializing a table of recurrence relations.

Array.new already takes a block, with little bit different behavior
than what you've describe. In addition,

| a = Array.new() {|self, i| 2*self[i-1] - self[i-2]}

I assume this accesses self[-1] and self[-2] during the array
initialization, which seem no good. Perhaps there should be better
way to express what you want.

matz.
 
C

Caio Chassot

Yukihiro said:
|1. A new method, Array.combine (needs a better name for general use).
| It takes entries from two (or many) arrays and combines them.
| (like a combination of Array.zip, fetch, and map)

I don't think we need another method for this. This sounds like the
domain of Enumerable#map (at least as far as how map works in lisp and
python).

Is there anything about map that prevents it from being extended to
accept 0 or more enumerables as parameters, then a block?

According to this rationale:
* the best name for the behavior.
map

* whether it takes only one argument or any number of arguments.

0 or more. (Much more powerful, and we don't have to give up anything
about only taking one arg to support taking many)
* whether "multiplication" should be the default.

If by "default" you mean "when no block is passed", as I see it, the
default should be identical or similar to zip.

in python, map with multiple lists and no function returns a list with
the length of the largest list, each element being a list of the
elements at its index in the lists passed in, None is used for elements
that don't exist in the other lists.

I like python's behavior, but I think in the case of ruby, using the
length of the receiver better matches expected behavior than using a
length from one of the parameters.

(on the other hand, using the longest length makes it more useful in
that it differs from zip, and it seems more useful in most cases)
 
R

Robert Klemme

Ara.T.Howard said:
but one less object creation

You can save that by using inject:

a = [1,2,3]
b = [1,3,5]
a.inject([]){|arr,x| arr << (x*b[arr.size])}

Regards

robert
 
R

Robert Klemme

Yukihiro Matsumoto said:
Hi,

In message "Request for two methods in Array class"

|1. A new method, Array.combine (needs a better name for general use).
| It takes entries from two (or many) arrays and combines them.
| (like a combination of Array.zip, fetch, and map)

Sounds nice. But I'm not sure "combine" is a proper name for the
method. We need more discussion, for example:

* the best name for the behavior.

I like "combine" but I'm open to suggestions.
* whether it takes only one argument or any number of arguments.

IMHO n>=0
* whether "multiplication" should be the default.

No IMHO.

I imageine behavior similar to this:

class Array
def combine(*arrays, &b)
result = []
each_with_index {|e,idx| result << b.call(e, *arrays.map {|ar|
ar[idx]})}
result
end
end

Note that arrays can contain anything that implements [], which might even
be a lambda (aka function):

a = [1,2,3]
a.combine(lambda {|i| i*2}) {|x,y| x*y}
=> [0, 4, 12]

So "arrays" might not be the ideal argument name.

Thinking a bit further about this it seems to me that the method might be
better put into Enumerable or Array as module method since arguments seem
to be symmetric:

class Array
def self.combine(*arrays, &b)
limit = arrays.inject(0){|l,a| s=a.size; s>l ? s : l}
result = Array.new limit
limit.times {|i| result = b.call(*arrays.map {|ar| ar}) }
result
end
end

irb(main):054:0> Array.combine([1,2,3],[1,3,5]) {|x,y| x*y}
=> [1, 6, 15]
irb(main):055:0> Array.combine([1,2,3],[1,3,5]) {|*x| x.inject(1){|z,i|
z*i} }
=> [1, 6, 15]

Kind regards

robert
 
M

Michael Neumann

Yukihiro said:
Hi,

In message "Request for two methods in Array class"

|1. A new method, Array.combine (needs a better name for general use).
| It takes entries from two (or many) arrays and combines them.
| (like a combination of Array.zip, fetch, and map)

Sounds nice. But I'm not sure "combine" is a proper name for the
method. We need more discussion, for example:

* the best name for the behavior.

combine sounds nice to me. it "combinates" two or more arrays (or
enumerables) into one. but one might argue against combine as name, as
it it's not clear that combine returns an array that is not larger than
it's arguments joined together.

IMO, combine should be equal to zip.map:

enum.combine(enum2, enum3,...) { block }
# equal
enum.zip(enum2, enum3,...).map { block }
* whether it takes only one argument or any number of arguments.
many

* whether "multiplication" should be the default.

no. default should be "{|arr| arr}" (the default of map)

Regards,

Michael
 
C

Christoph Neubauer

Yukihiro Matsumoto said:
Hi,

In message "Request for two methods in Array class"

|1. A new method, Array.combine (needs a better name for general use).
| It takes entries from two (or many) arrays and combines them.
| (like a combination of Array.zip, fetch, and map)

Sounds nice. But I'm not sure "combine" is a proper name for the
method. We need more discussion, for example:

Hi Matz !

I'm using Ruby 1.6.8.
Maybe my suggestions are already part of some later version,
or can be upgraded to some more fittable behaviour.
* the best name for the behavior.

--> match
* whether it takes only one argument or any number of arguments.

--> one or more
* whether "multiplication" should be the default.

If no (or an empty) block is given, what would be the most common way to
'match' array elements ?
Regarding to Sean O'Dells 'weave example', I think it's like this:

natural = [1,2,3]
odd = [1,3,5]
even = [2,4,6]

matched = natural.match(odd,even)
matched #=> [[1, 1, 2], [2, 3, 4], [3, 5, 6]]


Greetings,
Chris

<snip>
 
C

Caio Chassot

Mike said:
1. A new method, Array.combine (needs a better name for general use).
It takes entries from two (or many) arrays and combines them.
(like a combination of Array.zip, fetch, and map)

Following on my suggestion that combine is just an extension of map, how
do you feel about this code?:

class Array
alias :__map :map
def map *args, &block
identity = proc{ |*items| items.length==1 ? items[0] : items }
self.zip(*args).__map{ |line| (block or identity).call *line }
end
alias :collect :map
end


irb(main):003:0> [1,2,3].map
=> [1, 2, 3]
irb(main):004:0> [1,2,3].map [1,3,5]
=> [[1, 1], [2, 3], [3, 5]]
irb(main):005:0> [1,2,3].map([1,3,5]){|x,y|x*y}
=> [1, 6, 15]
 
M

Martin DeMello

Yukihiro Matsumoto said:
Hi,

In message "Request for two methods in Array class"

|1. A new method, Array.combine (needs a better name for general use).
| It takes entries from two (or many) arrays and combines them.
| (like a combination of Array.zip, fetch, and map)

Sounds nice. But I'm not sure "combine" is a proper name for the
method. We need more discussion, for example:

* the best name for the behavior.
* whether it takes only one argument or any number of arguments.

Any number, I'd say
* whether "multiplication" should be the default.

Almost certainly not.

Getting a bit complicated, but what I'd like is to check if the last or
two last args are a symbol and an optional seed, which are injected
across the current tuple.

e.g. the multiplication case would be a.combine(b,c,d, :*) which would mean
a.zip(b,c,d) {|*args| args.inject {|a,v| a.send:)*, v)}}

If both a symbol and a block are provided, the symbol is injected first
and the result passed to the block.

Default (no symbol and no block) should be to just zip the arrays.

martin
 
D

David A. Black

Hi --

Hi,

In message "Request for two methods in Array class"

|1. A new method, Array.combine (needs a better name for general use).
| It takes entries from two (or many) arrays and combines them.
| (like a combination of Array.zip, fetch, and map)

Sounds nice. But I'm not sure "combine" is a proper name for the
method. We need more discussion, for example:

I know I'm being the voice of possibly excessive conservatism
here... but I'm just wondering what this method would give us, since
we can already do zip+map. As Ara said, it would create one less
object. But chaining methods always creates intermediate objects. If
zip+map deserves to be combined into a new, single method, what about
flatten+map, or compact+map? And then (flatten+map)+compact....

I realize that zip+map is more likely to be actually used, and people
have often said they wish zip's block mapped the elements. But I
still wonder whether it's worth a new method and method name.


David
 
G

Gavin Sinclair

I know I'm being the voice of possibly excessive conservatism
here... but I'm just wondering what this method would give us, since
we can already do zip+map. As Ara said, it would create one less
object. But chaining methods always creates intermediate objects. If
zip+map deserves to be combined into a new, single method, what about
flatten+map, or compact+map? And then (flatten+map)+compact....
I realize that zip+map is more likely to be actually used, and people
have often said they wish zip's block mapped the elements. But I
still wonder whether it's worth a new method and method name.

That's good thinking. The most compelling examples so far have been
the ones where #zip actually has map-like functionality. No need for
a new method. *Definitely* don't touch #map !!

arr.zip { ... } # same behaviour as arr.map { ... }
arr.zip(x) { ... } # " " " " arr.zip(x).map { ... }
etc.

Gavin
 
D

David A. Black

Hi --

That's good thinking. The most compelling examples so far have been
the ones where #zip actually has map-like functionality. No need for
a new method. *Definitely* don't touch #map !!

arr.zip { ... } # same behaviour as arr.map { ... }
arr.zip(x) { ... } # " " " " arr.zip(x).map { ... }
etc.

The problem with having zip do auto-mapping is that you lose the
ability to do zip with a block but without mapping (which might be
desireable for performance or other reasons). Whereas if it stays the
way it is, you can always map the results.


David
 
K

Kristof Bastiaensen

That's good thinking. The most compelling examples so far have been the
ones where #zip actually has map-like functionality. No need for a new
method. *Definitely* don't touch #map !!

Why not? I find the idea of Caio Chassot very interesting.
And it stays in the spirit of map/collect. You can then
collect from multiple Enumerables. As a bonus, it doesn't
break anything, since it is an addition.

Regards,
KB
 
A

Ara.T.Howard

That's a strong point. Elaborating a bit more on that, perhaps what is
needed is not a new method (or a collection of them), but a way of piping
objects instead of creating intermediate ones. Then methods could be still
chained, but with better spatial efficiency.
-CWS

generic iterator functionality will give something close to this - using the
example the OP gave:

~/eg/ruby > cat a.rb
require 'enumiter'

a = 1,2,3
b = 1,3,5

prod = Enumerable.map(a,b){|x,y| x * y}
p prod

~/eg/ruby > ruby a.rb
[1, 6, 15]

a sample impl of enumerable iterators and assoc Enumerable.each/map/collect:

<cut>

#
# iterator and multi-each/collect support for enumerables
#
module Enumerable
#{{{
#
# generic callcc based iterator wrapper class for enumerables
#
class Iterator
#{{{
public
attr_reader :enumerable, :has_next
def initialize enumerable, end_value = nil, &end_block
#{{{
@enumerable = enumerable
@end_value = end_value
@end_block = end_block
initialize_fetch_block
#}}}
end
def next
#{{{
@has_next ? fetch_next_element : fetch_end_value
#}}}
end
def rewind
#{{{
initialize_fetch_block
self
#}}}
end
protected
def initialize_fetch_block
#{{{
callcc do |@after_fetch|
@has_next = true
@enumerable.each do |@next_element|
callcc do |@next_fetch| @after_fetch.call end
end
@has_next = false
@next_fetch = nil
@after_fetch.call
end
@after_fetch = nil
#}}}
end
def fetch_next_element
#{{{
result = @next_element
callcc do |@after_fetch| @next_fetch.call end
@after_fetch = nil
result
#}}}
end
def fetch_end_value
#{{{
@end_block ? @end_block.call : @end_value
#}}}
end
#}}}
end
#
# additional enumerable instance methods
#
def iterator; @iterator = Iterator.new self; end
#
# additional enumerable module methods
#
# enumerables class methods
class << self
#{{{
def each(*enumerables, &block)
#{{{
iterators = enumerables.collect{|e| e.iterator}
while true
args = iterators.collect{|i| i.next}
if args.detect{|arg| arg}
block.call *args
else
return enumerables
end
end
#}}}
end
def collect(*enumerables, &block)
#{{{
ret = []
each(*enumerables){|*args| ret << (block.call *args)}
ret
#}}}
end
alias map collect
#}}}
end
#}}}
end

if $0 == __FILE__
#{{{
a = ['fee','fie','foe','fum']
h = {'k' => 'v', 'K' => 'V'}

ai = a.iterator
hi = h.iterator

while ((n = ai.next)) do
puts n.inspect
end

while ((n = hi.next)) do
puts n.inspect
end

Enumerable::each a, h do |elem, kv|
puts elem.inspect
puts kv.inspect
end

p Enumerable::collect(a,h){|e,kv| [e,kv]}
#}}}
end


__END__

OUTPUT:

"fee"
"fie"
"foe"
"fum"
["K", "V"]
["k", "v"]
"fee"
["K", "V"]
"fie"
["k", "v"]
"foe"
nil
"fum"
nil
[["fee", ["K", "V"]], ["fie", ["k", "v"]], ["foe", nil], ["fum", nil]]

</cut>


regards.

-a
--
===============================================================================
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| A flower falls, even though we love it;
| and a weed grows, even though we do not love it.
| --Dogen
===============================================================================
 
K

Kristof Bastiaensen

That's a strong point. Elaborating a bit more on that, perhaps what is
needed is not a new method (or a collection of them), but a way of piping
objects instead of creating intermediate ones. Then methods could be still
chained, but with better spatial efficiency.
-CWS

That's possible using the enumerator module.
The code would be then:

require 'enumerator'

a = [1, 2, 3]
b = [1, 3, 5]
a.to_enum:)zip, b).map{|x, y| x*y}
=> [1, 6, 15]

#the new map method:
class Array
alias __map__ map
def map(*args)
enum = (args.empty? ? self : enum_for:)zip, *args))
if block_given?
enum.__map__ {|args| yield(*args)}
else
enum.to_a
end
end
alias collect map
end

module Enumerable
alias __map__ map
define_method:)map, Array.new.method:)map))
alias collect map
end

a.map(b) { |x, y| x*y }
=> [1, 6, 15]

no intermediate object is created (except for the enumerable)
The enumerable acts like a pipe.
Let me take this opportunity to shamelessly point to my
RCR for more Enumerator functionality:
<http://www.rcrchive.net/rcr/RCR/RCR262>

Regards,
KB
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,149
Latest member
Vinay Kumar Nevatia0
Top