Zip block oddities/bug?

When supplying a block to zip, shouldn’t you
be able to affect the output produced by #zip ?

No matter what I do it outputs ‘nil’…

Is this the intented behavier ?

irb(main):020:0> (4…6).zip {|i| p i; nil }
[4]
[5]
[6]
=> nil
irb(main):021:0> (4…6).zip {|i| p i; }
[4]
[5]
[6]
=> nil
irb(main):022:0> (4…6).zip {|i| p i; 666 }
[4]
[5]
[6]
=> nil
irb(main):023:0> (4…6).zip(8) {|i| p i; 666 }
TypeError: cannot convert Fixnum into Array
from (irb):23:in zip' from (irb):23 irb(main):024:0> (4..6).zip(8) {|i| p i; nil } TypeError: cannot convert Fixnum into Array from (irb):24:in zip’
from (irb):24
irb(main):025:0> (4…6).zip([8]) {|i| p i; nil }
[4, 8]
[5, nil]
[6, nil]
=> nil
irb(main):026:0>

Except from the last line, I only get nil outputted
The block is mentioned in RI, but …

ri Enumerable.zip
--------------------------------------------------------- Enumerable#zip
enum.zip(arg, …) => array
enum.zip(arg, …) {|arr| block } => nil

···

from :0
from :0

 Converts any arguments to arrays, then merges elements of _enum_
 with corresponding elements from each argument. This generates a
 sequence of +enum#size+ _n_-element arrays, where _n_ is one more
 that the count of arguments. If the size of any arguemnt is less
 than +enum#size+, +nil+ values are supplied. If a block given, it
 is invoked for each output array, otherwise an array of arrays is
 returned.

    a = [ 4, 5, 6 ]
    b = [ 7, 8, 9 ]
 
    (1..3).zip(a, b)      #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
    "cat\ndog".zip([1])   #=> [["cat\n", 1], ["dog", nil]]
    (1..3).zip            #=> [[1], [2], [3]]


Simon Strandgaard

Simon Strandgaard wrote:

When supplying a block to zip, shouldn’t you
be able to affect the output produced by #zip ?

No matter what I do it outputs ‘nil’…

Is this the intented behavier ?

irb(main):020:0> (4…6).zip {|i| p i; nil }
[4]
[5]
[6]
=> nil
irb(main):021:0> (4…6).zip {|i| p i; }
[4]
[5]
[6]
=> nil
irb(main):022:0> (4…6).zip {|i| p i; 666 }
[4]
[5]
[6]
=> nil
irb(main):023:0> (4…6).zip(8) {|i| p i; 666 }
TypeError: cannot convert Fixnum into Array
from (irb):23:in zip' from (irb):23 from :0 irb(main):024:0> (4..6).zip(8) {|i| p i; nil } TypeError: cannot convert Fixnum into Array from (irb):24:in zip’
from (irb):24
from :0
irb(main):025:0> (4…6).zip([8]) {|i| p i; nil }
[4, 8]
[5, nil]
[6, nil]
=> nil
irb(main):026:0>

Except from the last line, I only get nil outputted
The block is mentioned in RI, but …

ri Enumerable.zip
--------------------------------------------------------- Enumerable#zip
enum.zip(arg, …) => array
enum.zip(arg, …) {|arr| block } => nil


 Converts any arguments to arrays, then merges elements of _enum_
 with corresponding elements from each argument. This generates a
 sequence of +enum#size+ _n_-element arrays, where _n_ is one more
 that the count of arguments. If the size of any arguemnt is less
 than +enum#size+, +nil+ values are supplied. If a block given, it
 is invoked for each output array, otherwise an array of arrays is
 returned.

    a = [ 4, 5, 6 ]
    b = [ 7, 8, 9 ]
 
    (1..3).zip(a, b)      #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
    "cat\ndog".zip([1])   #=> [["cat\n", 1], ["dog", nil]]
    (1..3).zip            #=> [[1], [2], [3]]


Simon Strandgaard

irb(main):009:0> (1…3).zip([4,5,6]) {|x| p x;}
[1, 4]
[2, 5]
[3, 6]
=> nil

I don’t follow why it was decided that the final result be nil, it would
be nice if returned the results from the block, meaning the above
example would result in:

irb(main):009:0> (1…3).zip([4,5,6]) {|x| p x; x}
[1, 4]
[2, 5]
[3, 6]
=> [[1, 4],[2, 5], [3, 6]]

But I guess that was just the original concept.

Charles Comstock

Simon Strandgaard wrote:

When supplying a block to zip, shouldn’t you
be able to affect the output produced by #zip ?

The “output” is inside the block.

No matter what I do it outputs ‘nil’…

Is this the intented behavier ?

I hope so :wink:

irb(main):020:0> (4…6).zip {|i| p i; nil }
[4]
[5]
[6]
=> nil

Does what you asked. Zips 4…6 with your omitted argument
and passes the results one at a time to the block.
If an array was built at the same time with, say,
(4…6000000000) you’d probably run out of memory, disk space
for swapping and not be able to complete the operation.

Since an array is deliberately not being built, what
could usefully be returned outside the block ?
(“nil” seems like a good choice).

daz

P.S. Swapfiles can be reset by a reboot.
May also need a defrag, which is often a good idea.

Think of the block zip as equivalent to ‘each’ - it iterates over the
result set, but never actually collects it.

martin

···

Charles Comstock cc1@cec.wustl.edu wrote:

irb(main):009:0> (1…3).zip([4,5,6]) {|x| p x;}
[1, 4]
[2, 5]
[3, 6]
=> nil

I don’t follow why it was decided that the final result be nil, it would
be nice if returned the results from the block, meaning the above
example would result in:

irb(main):009:0> (1…3).zip([4,5,6]) {|x| p x; x}
[1, 4]
[2, 5]
[3, 6]
=> [[1, 4],[2, 5], [3, 6]]

[snip]

Since an array is deliberately not being built, what
could usefully be returned outside the block ?
(“nil” seems like a good choice).

zip should simply just concatenate the data it gets back from
the block.

I have made a pseudo-implementation, where blocks is working.
It differs sligthly from the real zip, because I am cheating
and using the :, rather than each.

ruby a.rb
[[“a”], [“b”], [“c”]]
[[“a”, 1, 3, 1], [“b”, 2, 4, 1], [“c”, nil, 5, 1], [“d”, nil, 6, 0]]
[[4, 1, “a”], [5, 1, “b”], [nil, 0, “c”]]
[[0, :lo], [1, :lo], [2, :hi], [3, :hi], [4, :hi]]
[9, “a”, 8, “b”, 7, “c”]
expand -t2 a.rb
module Enumerable
def zip2(*args)
res =
self.each_with_index do |element, index|
data = [element] + args.map{|i| i.respond_to?(:) ? i[index] : i}
res += block_given? ? yield(data) : [data]
end
res
end
end
p %w(a b c).zip2
p %w(a b c d).zip2([1, 2], [3, 4, 5, 6], 7)
p %w(a b c).zip2(3, [4, 5]) {|i| [i.reverse]}
p (0…4).zip2 {|i| [i << (i[0] < 2 ? :lo : :hi)]}
p %w(a b c).zip2([9, 8, 7]) {|i| i.reverse}

It wakes memories of inject.

···

On Mon, 12 Apr 2004 07:42:35 +0100, daz wrote:


Simon Strandgaard

[snip]

irb(main):009:0> (1…3).zip([4,5,6]) {|x| p x; x}
[1, 4]
[2, 5]
[3, 6]
=> [[1, 4],[2, 5], [3, 6]]

Agree, thats how 'zip’s output should be.

Think of the block zip as equivalent to ‘each’ - it iterates over the
result set, but never actually collects it.

Its opfuscation to to use ‘zip as each’.

To me it looks like concatenation of block result was
forgotten. Therefore I ask… is it intended?

IMHO The ‘each’ behavier is othogonal from ‘zip’.
Then its better to remove the behaver completely or
fix it. Leaving it unmodified confuses more than it helps.

:wink:

···

On Mon, 12 Apr 2004 10:20:34 +0000, Martin DeMello wrote:

Charles Comstock cc1@cec.wustl.edu wrote:


Simon Strandgaard

Note the difference:

a.zip(b,c).map {|i| … } # builds the array, then iterates over it
a.zip(b,c) {|i| … } # generates values one by one and calls block

If the latter functionality were not built into zip, there’d be no way
to do it from ‘outside’, short of using a continuation. As daz points
out, there are definitely times you’d want to iterate without building
either an intermediate or an output array.

martin

···

Simon Strandgaard neoneye@adslhome.dk wrote:

To me it looks like concatenation of block result was
forgotten. Therefore I ask… is it intended?

IMHO The ‘each’ behavier is othogonal from ‘zip’.
Then its better to remove the behaver completely or
fix it. Leaving it unmodified confuses more than it helps.

I think thats a broken metaphor, to do multiway each by means
of zip.

I don’t wanna defend my idea any longer (too much resistance).
Isn’t there any which can say something positive… or agree with
me on this ?

Thanks for all your comments.

···

On Mon, 12 Apr 2004 15:40:06 +0000, Martin DeMello wrote:

Note the difference:

a.zip(b,c).map {|i| … } # builds the array, then iterates over it
a.zip(b,c) {|i| … } # generates values one by one and calls block

If the latter functionality were not built into zip, there’d be no way
to do it from ‘outside’, short of using a continuation. As daz points
out, there are definitely times you’d want to iterate without building
either an intermediate or an output array.


Simon Strandgaard

I would have to agree with you on this… it doesn’t make sense to me
that the block version doesn’t collect the output of yield.

That said, I can definitely see the benefit of having it not collect
the output. I would just tend to think that the non-collecting one
should not have the same name as the collecting one. Perhaps:

(4…6).zip(10…12) #=> [[4, 10], [5, 11], [6, 12]]
(4…6).zip(10…12) {|a| a[0]*a[1] } #=> [40, 55, 72]

and

(4…6).zip_each(10…12) {|a| puts a[0]*a[1] } #=> nil

But then this would break a lot. Even if people agree with this, I
wouldn’t expect to see it before 2.0

–Mark

···

On Apr 12, 2004, at 2:39 PM, Simon Strandgaard wrote:

On Mon, 12 Apr 2004 15:40:06 +0000, Martin DeMello wrote:

Note the difference:

a.zip(b,c).map {|i| … } # builds the array, then iterates over it
a.zip(b,c) {|i| … } # generates values one by one and calls
block

If the latter functionality were not built into zip, there’d be no way
to do it from ‘outside’, short of using a continuation. As daz points
out, there are definitely times you’d want to iterate without building
either an intermediate or an output array.

I think thats a broken metaphor, to do multiway each by means
of zip.

I don’t wanna defend my idea any longer (too much resistance).
Isn’t there any which can say something positive… or agree with
me on this ?

Note the difference:

a.zip(b,c).map {|i| … } # builds the array, then iterates over it
a.zip(b,c) {|i| … } # generates values one by one and calls
block

If the latter functionality were not built into zip, there’d be no way
to do it from ‘outside’, short of using a continuation. As daz points
out, there are definitely times you’d want to iterate without building
either an intermediate or an output array.

I think thats a broken metaphor, to do multiway each by means
of zip.

I don’t wanna defend my idea any longer (too much resistance).
Isn’t there any which can say something positive… or agree with
me on this ?

I would have to agree with you on this… it doesn’t make sense to me
that the block version doesn’t collect the output of yield.

Great. (I was beginning to worry if it were a silly idea of mine)

That said, I can definitely see the benefit of having it not collect
the output. I would just tend to think that the non-collecting one
should not have the same name as the collecting one. Perhaps:

(4…6).zip(10…12) #=> [[4, 10], [5, 11], [6, 12]]
(4…6).zip(10…12) {|a| a[0]*a[1] } #=> [40, 55, 72]

or a little shorter

(4…6).zip(10…12) {|a,b| a*b } #=> [40, 55, 72]

and

(4…6).zip_each(10…12) {|a| puts a[0]*a[1] } #=> nil

But then this would break a lot. Even if people agree with this, I
wouldn’t expect to see it before 2.0

Agree… thats the reason I mentioned it, so we can break as
much as possible in the transition from 1 to 2.

···

On Tue, 13 Apr 2004 08:01:41 +0900, Mark Hubbart wrote:

On Apr 12, 2004, at 2:39 PM, Simon Strandgaard wrote:

On Mon, 12 Apr 2004 15:40:06 +0000, Martin DeMello wrote:


Simon Strandgaard

Just submitted a RCR about the subject… vote on it here
http://rcrchive.net/rcr/RCR/RCR245

···


Simon Strandgaard