Ruby-dev summary 18613-18710

Hi –

In article Pine.LNX.4.44.0211142157370.25867-100000@candle.superlink.net,

Hi –

[…]

[a, b, c].zip { |x, y, z| x*y + z } #=> [5, 2, 7]

Now zip is like lisp’s map.

The optional block is cool, but I don’t know about having the return
value of the method being different depending on the presence or
absence of a block. I’m not sure, though.

Thanks for the reply David. By return value, do you mean nil vs exception
for block vs non-block?

No, I mean the actual return value of the method call, which is a
separate matter from the block. In your example, if there were no
block, the return value would be different. In other words, given:

n = [a,b,c].zip
m = [a,b,c].zip { |x,y,z| x*y+z }

n and m would not be the same. That’s where I’m scratching my head
and wondering if that’s the best way.

With methods whose blocks are not optional, you can just decide and
then the behavior will be consistent; for example, #map returns an
array consisting of the aggregate of what happened in the block, while
#each returns the receiver. My instinct is to think that methods with
optional blocks should also have consistent return values, but I’m not
entirely sure.

David

···

On Fri, 15 Nov 2002, Tim Sutherland wrote:

dblack@candle.superlink.net wrote:

On Fri, 15 Nov 2002, Tim Sutherland wrote:


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

The first case is equivalent to the second case with the block
{ |*tuple| tuple }. If no block is given, zip just uses the array of
parameters of this block.

In fact that’s exacly the way I implemented zip and unzip while
converting some Haskell functions into Ruby. Haskell has zip, zip3,
zipWith and zipWith3 and all of this functions can be replaced by a
single Ruby method which even can do zipWithN:

module Enumerable

def zip(&block)
	block ||= lambda { |*tuple| tuple }
	size = nil
	map { |x| x.size }.each do |s|
		size ||= s
		s == size or raise "zip with unequal length lists"
	end
	result = []
	i = 0
	catch(:stop) do
		tuple = []
		each do |l|
			l[i] or throw :stop
			tuple << l[i]
		end
		result << block.call(*tuple)
		i += 1
		redo
	end
	result
end

def unzip(&block)
	block ||= lambda { |*tuple| tuple }
	result = []
	size = nil
	each do |x|
		tuple = block.call(*x)
		i = 0
		size ||= tuple.size
		size == tuple.size or raise "unzip with unequal length pairs"
		tuple.each do |t|
			(result[i] ||= []) << t
			i += 1
		end
	end
	result
end

end

To implement this in C is unfortunately a bit more difficult, I fear.

···

On 2002-11-15 21:37:46 +0900, dblack@candle.superlink.net wrote:

No, I mean the actual return value of the method call, which is a
separate matter from the block. In your example, if there were no
block, the return value would be different. In other words, given:

n = [a,b,c].zip
m = [a,b,c].zip { |x,y,z| x*y+z }

n and m would not be the same. That’s where I’m scratching my head
and wondering if that’s the best way.


Programs must be written for people to read, and only incidentally for
machines to execute.
– Abelson/Sussman, “The Structure and Interpretation of Computer Programs”

Hi –

No, I mean the actual return value of the method call, which is a
separate matter from the block. In your example, if there were no
block, the return value would be different. In other words, given:

n = [a,b,c].zip
m = [a,b,c].zip { |x,y,z| x*y+z }

n and m would not be the same. That’s where I’m scratching my head
and wondering if that’s the best way.

The first case is equivalent to the second case with the block
{ |*tuple| tuple }. If no block is given, zip just uses the array of
parameters of this block.

Yes, that’s the very behavior that raises the question (to the extent
that it is a question) about the presence/absence of the block
affecting the return value of the method. Which for some reason seems
anomalous to me… I’m still not entirely sure why. I have to do
some digging and refresh my memory as to how blocks with optional
methods usually behave. (Hmmm, what did we do in scanf? :slight_smile:

In fact that’s exacly the way I implemented zip and unzip while
converting some Haskell functions into Ruby. Haskell has zip, zip3,
zipWith and zipWith3 and all of this functions can be replaced by a
single Ruby method which even can do zipWithN:

module Enumerable

def zip(&block)
block ||= lambda { |*tuple| tuple }
size = nil
map { |x| x.size }.each do |s|
size ||= s
s == size or raise “zip with unequal length lists”
end
result =
i = 0
catch(:stop) do

(Don’t you like regular Ruby iterators? :slight_smile:

  	tuple = []
  	each do |l|
  		l[i] or throw :stop

That test is going to prevent you from zipping arrays that have nil or
false as elements, for example:

a = [1,2,3]
b = [4,nil,6]

p [a,b].zip # [[1,4]]

I notice that you’ve made this a method of Enumerable, rather than
Array. What would the effect be of calling #zip on a non-Array
Enumerable, for example a String?

“abcde”.zip # => ?

(I gather from the original post that there was some discussion of
Enumerable#zip in the Japanese list… not sure what was said,
though, and I’m not clear on how it would work.)

David

···

On Sat, 16 Nov 2002, Florian Frank wrote:

On 2002-11-15 21:37:46 +0900, dblack@candle.superlink.net wrote:


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

Did you mean methods with optional blocks?

FWIW, this is how the #scan method in my Archive::Reader::XYZ classes
behaves:

io = File.open('archive.ar')
reader = Archive::Reader::Ar.new(io)

# block provided; method returns nothing

reader.scan do |entry|
  do_something_with(entry)
end

# no block; method returns an Enumerable of entries

entries = reader.scan

This way, when you just want sequential access once (e.g. for
extracting all entries), #scan doesn’t build an extra object. If you
want to access it randomly or more than once, you don’t pass a block
and it builds and returns the Enumerable.

Massimiliano

···

On Sat, Nov 16, 2002 at 06:07:50AM +0900, dblack@candle.superlink.net wrote:

Yes, that’s the very behavior that raises the question (to the extent
that it is a question) about the presence/absence of the block
affecting the return value of the method. Which for some reason seems
anomalous to me… I’m still not entirely sure why. I have to do
some digging and refresh my memory as to how blocks with optional
methods usually behave.

module Enumerable

def zip(&block)
  block ||= lambda { |*tuple| tuple }
  size = nil
  map { |x| x.size }.each do |s|
  	size ||= s
  	s == size or raise "zip with unequal length lists"
  end
  result = []
  i = 0
  catch(:stop) do

(Don’t you like regular Ruby iterators? :slight_smile:

I like them, but I can’t use an iterator here. I needed to get the i-th
element of every object l in the enumerable self. It would be better to
use while instead.

This solves the problem with nil elements, too. I used catch in a
previous version before I decided to first check the sizes and raise an
exception. In the previous version I wanted to stop after first reaching
the end of one list. The nil test was a stupid idea to find the end in
the first place.

I notice that you’ve made this a method of Enumerable, rather than
Array.

I just needed the each iterator, so Array would perhaps be to
restrictive. This way its possible to iterate over a tree of objects for
example if it includes Enumerable.

What would the effect be of calling #zip on a non-Array Enumerable,
for example a String?

“abcde”.zip # => ?

“abcde”.zip
==>[[97], [98], [99], [100], [101]]

The index of a string is a number. This only works because numbers
happen to have a “size” method. This nice property doesn’t hold for your
example either:

[[1,2,3],[2,3,4]].zip.unzip == [[1,2,3],[2,3,4]]
==>true

You can get strange results if you use “\n” in a string because “each”
iterates over the lines of a string:

“abcdef\nABCDEFG”.zip
==>[[97, 65], [98, 66], [99, 67], [100, 68], [101, 69], [102, 70], [10, 71]]

Strings aren’t a good example of a collection anyway. :wink:

(I gather from the original post that there was some discussion of
Enumerable#zip in the Japanese list… not sure what was said,
though, and I’m not clear on how it would work.)

BTW: It would be nice to be able to pass an arbitrary iterator to zip:

module Enumerable

def zip(iterator = :each, &block)
	block ||= lambda { |*tuple| tuple }
	size = nil
	__send__(iterator) do |s|
		size ||= s.size
		s.size == size or raise "zip with unequal length lists"
	end
	result = []
	i = 0
	while i < size do
		tuple = []
		__send__(iterator) do |l|
			tuple << l[i]
		end
		result << block.call(*tuple)
		i += 1
	end
	result
end

end

···

On 2002-11-16 06:07:50 +0900, dblack@candle.superlink.net wrote:


Poverty is an anomaly to rich people. It is very difficult to make out why
people who want dinner do not ring the bell.
– Walter Bagehot

Did you mean methods with optional blocks?

FWIW, this is how the #scan method in my Archive::Reader::XYZ classes
behaves:

Well, it really depend on what you want to do. For example File::open can
take a block, but not File::new

Generally I try to mimic ruby, except when I forget and I make something
completely different :-)))

Guy Decoux

I notice that you’ve made this a method of Enumerable, rather than
Array.

I just needed the each iterator, so Array would perhaps be to
restrictive. This way its possible to iterate over a tree of objects for
example if it includes Enumerable.

You also needed the # method, that’s why…

“abcde”.zip
==>[[97], [98], [99], [100], [101]]

The index of a string is a number. This only works because numbers
happen to have a “size” method. This nice property doesn’t hold for your
example either:

…and…

You can get strange results if you use “\n” in a string because “each”
iterates over the lines of a string:

“abcdef\nABCDEFG”.zip
==>[[97, 65], [98, 66], [99, 67], [100, 68], [101, 69], [102, 70], [10, 71]]

Tried that with hashes already? :slight_smile:

For zip to work as an Enumerable’s method, you have to rely on #each
only. Just use Enumerable#to_a and you’ll have the elements like
#each would have passed them to you.

module Enumerable
def zip(*enumerables)
zipped =
# was like this:
# all = [self, *enumerables]
# now is:
all = [self.to_a, *enumerables.collect {|e| e.to_a}]
max_length = all.collect {|a| a.length}.max

0.upto(max_length-1) do |i|
  zipped << all.collect {|a| a[i]}
end
zipped

end
end

p [1, 2, 3].zip([4, 5, 6])
#=> [[1, 4], [2, 5], [3, 6]]

p “one\ntwo\n”.zip(“three\nfour\n”)
#=> [[“one\n”, “three\n”], [“two\n”, “four\n”]]

h = {:one => “two”}
p h.zip({:three => “four”})
#=> [[[:one, “two”], [:three, “four”]]]

You needn’t obey String#each default blindly, either:

$/ = ‘o’
p “one\ntwo\n”.zip(“three\nfour\n”)
#=> [[“o”, “three\nfo”], [“ne\ntwo”, “ur\n”], [“\n”, nil]]

Massimiliano

···

On Sat, Nov 16, 2002 at 09:30:12PM +0900, Florian Frank wrote:

Hi –

module Enumerable

def zip(&block)
  block ||= lambda { |*tuple| tuple }
  size = nil
  map { |x| x.size }.each do |s|
  	size ||= s
  	s == size or raise "zip with unequal length lists"
  end
  result = []
  i = 0
  catch(:stop) do

(Don’t you like regular Ruby iterators? :slight_smile:

I like them, but I can’t use an iterator here. I needed to get the i-th
element of every object l in the enumerable self. It would be better to
use while instead.

If you want the i-th element of each object, just iterate through the
objects and grab the i-th element :slight_smile: In fact you can save yourself a
lot of typing. Here’s a rewrite:

module Enumerable
def zip(&block)
block ||= lambda { |*tuple| tuple }
if map {|e| e.size}.uniq.size > 1
raise “zip with unequal length lists”
else
(0…size).map {|i| block.call(*map {|l| l[i]})}
end
end
end

Mind you, I’m not currently a fan of the exception for unequal
lengths, and I actually prefer this:

ary.zip(ary2, ary3)

to this:

[ary1, ary2, ary3].zip

but anyway, you can definitely get things more concise, for your
version, by letting Ruby doing the work for you.

What would the effect be of calling #zip on a non-Array Enumerable,
for example a String?

“abcde”.zip # => ?

“abcde”.zip
==>[[97], [98], [99], [100], [101]]

The index of a string is a number. This only works because numbers
happen to have a “size” method. This nice property doesn’t hold for your
example either:

[[1,2,3],[2,3,4]].zip.unzip == [[1,2,3],[2,3,4]]
==>true

You can get strange results if you use “\n” in a string because “each”
iterates over the lines of a string:

“abcdef\nABCDEFG”.zip
==>[[97, 65], [98, 66], [99, 67], [100, 68], [101, 69], [102, 70], [10, 71]]

Strings aren’t a good example of a collection anyway. :wink:

Heh – maybe not, but that might also indicate that Enumerable-ness
isn’t a good sign of zip-ableness. Actually one reason I prefer:

ary.zip(ary2, ary3)

is that there’s an equivalent for other Enumerables:

“abc”.zip(“def”,“ghi”)

   # => [[97, 100, 103], [98, 101, 104], [99,102, 105]]

Yes, you can do this:

[ “abc”,“def”,“ghi” ].zip

but then you’re calling zip on an array, not a string – which then
re-raises the question of why bother creating Enumerable#zip instead
of Array#zip.

David

···

On Sat, 16 Nov 2002, Florian Frank wrote:

On 2002-11-16 06:07:50 +0900, dblack@candle.superlink.net wrote:


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

You also needed the # method, that’s why…

I needed # only for the elements of the enumerable not for the
enumerable that Enumerable#zip iterates over aka self.

You can get strange results if you use “\n” in a string because “each”
iterates over the lines of a string:
[…]
Tried that with hashes already? :slight_smile:

I just did:

h = Hash[*(1…10)]
==>{5=>6, 1=>2, 7=>8, 3=>4, 9=>10}
h.zip
==>[[5, 1, 7, 3, 9], [6, 2, 8, 4, 10]]
h.zip == [h.keys, h.values]
==>true

For zip to work as an Enumerable’s method, you have to rely on #each
only.

I did rely on Enumerable#each only. The reason I used #each at all is
that I wanted to zip an arbitrary amount of “lists”. In Haskell its only
possible to zip two or three “lists” with the standard library
functions. In my Ruby zip it’s in addition possible to zip over every
object that understands #. There is a difference between the
collection of “lists” and the “lists”.

Just use Enumerable#to_a and you’ll have the elements like
#each would have passed them to you.

I know.

module Enumerable
def zip(*enumerables)
zipped =
# was like this:
# all = [self, *enumerables]
# now is:
all = [self.to_a, *enumerables.collect {|e| e.to_a}]
max_length = all.collect {|a| a.length}.max

0.upto(max_length-1) do |i|
  zipped << all.collect {|a| a[i]}
end
zipped

end
end

That’s another possibility. The construction of “all” wastes a lot of
memory in some cases, but it is perhaps more flexible because the
arguments only have to be enumerables. I don’t know what happens more
often that an object can # or includes Enumerable and/or can #each.
If it can’t # you can always do #to_a to them before using zip.

Your implementation would probably be better for a module method:

def Enumerable.zip(*enumerables)
zipped =
all = *enumerables.collect { |e| e.to_a }
max_length = all.collect { |a| a.length }.max || 0

0.upto(max_length - 1) do |i|
	zipped << all.collect { |a| a[i] }
end
zipped

end

You needn’t obey String#each default blindly, either:

$/ = ‘o’

You can’t shock me – I confess: I’m a perl programmer. g

p “one\ntwo\n”.zip(“three\nfour\n”)
#=> [[“o”, “three\nfo”], [“ne\ntwo”, “ur\n”], [“\n”, nil]]

To pass another iterator has perhaps an advantage, this comes to my
mind:

tree.zip(:preorder)
tree.zip(:inorder)
tree.zip(:postorder)

···

On 2002-11-16 23:18:47 +0900, Massimiliano Mirra wrote:


It makes no difference who you vote for - the two parties are really one party
representing four percent of the people.
– Gore Vidal

Hi –

If you want the i-th element of each object, just iterate through the
objects and grab the i-th element :slight_smile: In fact you can save yourself a
lot of typing. Here’s a rewrite:

And, just for fun, here’s another rewrite, this time one which isn’t
completely and utterly flawed (I hope):

module Enumerable
def zip(&block)
block ||= lambda { |*tuple| tuple }
top,rest = map {|e| e.size}.uniq
if rest
raise “zip with unequal length lists”
else
(0…top.to_i).map {|i| block.call(*map {|e| e[i]})}
end
end
end

David

···


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

Tried that with hashes already? :slight_smile:

I just did:

h = Hash[*(1…10)]
==>{5=>6, 1=>2, 7=>8, 3=>4, 9=>10}
h.zip
==>[[5, 1, 7, 3, 9], [6, 2, 8, 4, 10]]
h.zip == [h.keys, h.values]
==>true

That’s because you happen to have numeric keys. Try with {:one =>
‘two’, :three => ‘four’}.

In Haskell its only possible to zip two or three “lists” with the
standard library functions.

(Just curious: why?)

There is a difference between the collection of “lists” and the
“lists”.

Sorry I don’t understand what you mean. :frowning:

module Enumerable
def zip(*enumerables)
zipped =
# was like this:
# all = [self, *enumerables]
# now is:
all = [self.to_a, *enumerables.collect {|e| e.to_a}]
max_length = all.collect {|a| a.length}.max

0.upto(max_length-1) do |i|
  zipped << all.collect {|a| a[i]}
end
zipped

end
end

That’s another possibility. The construction of “all” wastes a lot of
memory in some cases,

True. That’s why I called it `a quick hack’ in another thread.

but it is perhaps more flexible because the
arguments only have to be enumerables. I don’t know what happens more
often that an object can # or includes Enumerable and/or can #each.
If it can’t # you can always do #to_a to them before using zip.

You’re assuming that # returns the element of a collection.
While this is often so, there’s no assurance. Strings and hashes are
the most notable cases. I’d say, rely on enumerables instead.

You needn’t obey String#each default blindly, either:

$/ = ‘o’

You can’t shock me – I confess: I’m a perl programmer. g

LOL Well, confession heard, my son, now you can repent… (just
kidding)

p “one\ntwo\n”.zip(“three\nfour\n”)
#=> [[“o”, “three\nfo”], [“ne\ntwo”, “ur\n”], [“\n”, nil]]

To pass another iterator has perhaps an advantage, this comes to my
mind:

tree.zip(:preorder)
tree.zip(:inorder)
tree.zip(:postorder)

Cool. Maybe, since each gets extra parameters already in String, one
could also have Tree#each(order), where order can be any of the above
three, and zip would just forward the parameter to #each.

Massimiliano

···

On Sun, Nov 17, 2002 at 05:18:40AM +0900, Florian Frank wrote:

That’s because you happen to have numeric keys. Try with {:one =>
‘two’, :three => ‘four’}.

I did, and there’s no difference. yield is called with the arguments
[key, value], so the result of {:one => ‘two’, :three => ‘four’}.zip is
equivalent to [[:one, ‘two’], [:three, ‘four’]].zip .

In Haskell its only possible to zip two or three “lists” with the
standard library functions.
(Just curious: why?)

In Prelude.hs the following zip functions are defined:

zip :: [a] → [b] → [(a,b)]
zip = zipWith (\a b → (a,b))

zip3 :: [a] → [b] → [c] → [(a,b,c)]
zip3 = zipWith3 (\a b c → (a,b,c))

zipWith :: (a->b->c) → [a]->[b]->[c]
zipWith z (a:as) (b:bs) = z a b : zipWith z as bs
zipWith _ _ _ =

zipWith3 :: (a->b->c->d) → [a]->[b]->[c]->[d]
zipWith3 z (a:as) (b:bs) (c:cs)
= z a b c : zipWith3 z as bs cs
zipWith3 _ _ _ _ =

In List.hs zip4, zip5, etc. are defined in a similar fashion.

In Haskell it doesn’t seem to be possible to use arbitrary length
argument lists in functions because every function has a finite function
signature, the type sequence after the “::”. Perhaps it would be
possible to define a function that has got a domain of [[a]]. The best I
could come up with is that:

myZip :: [[a]] → [[a]]
myZip =
myZip args = iter 0 args
where
iter i l

i < length l = row i l : iter (i + 1) l
otherwise =
row i xs = map (\x → x !! i) xs

Now there remains the problem to build tuples from the lists of the
codomain, but tuples have to be of a finite type again. To program a
myZipWith a apply function for lists is required, but that seems to be
difficult because of similiar reasons. Perhaps there are people that
learned more of the LOTY on this mailinglist who know how to do this if
possible? BTW: This year isn’t over yet… :wink:

You can’t shock me – I confess: I’m a perl programmer. g
LOL Well, confession heard, my son, now you can repent… (just
kidding)

And I haven’t even confessed yet that I started programming with BASIC.

···

On 2002-11-17 19:16:30 +0900, Massimiliano Mirra wrote:


Beware of bugs in the above code; I have only proved it correct, not tried it.
– Donald Knuth, “A memo to Peter van Emde Boas”