Enumerable#zip
is good at making pairs. In a way that is intuitive and easy to grasp.
#zip
is taking a block. Guess what happens.
(1..4).zip(1..4) {|a,b| a*b }
nil
. It shows up that zip is commonly being used for multiway each! Thats a broken metaphor, which give people bad habits. See [] for discussion.
I propose that zip
concatenates and returns the result of the supplied block.
(4..6).zip(10..12) {|a,b| a*b } #=> [40, 55, 72] %w(a b c).zip(1..9) {|i| i.reverse } #=> [[1, 'a'], [2, 'b'], [3, 'c']]
callcc
/enumerator
instead of this bad habbit.
I'm not sure about all this 'bad habit' and 'broken' stuff, but I do find it odd that #zip returns nil with a block.
Just for fun, here's an implementation:
module Z def Z.included(c) c.class_eval { alias oldzip zip def zip(*others,&block) if block res = [] newblock = Proc.new { |*args| res << block.call(*args) } oldzip(*others,&newblock) return res else oldzip(*others) end end } end end
class Array; include Z; end class Range; include Z; end
# and perhaps track down every Enumerable and do likewise....
(BTW, your 'reverse' example doesn't need the square brackets around i.reverse. Each element will already be an array.)
reverse
example. Thanks David.
I made another implementation (didn't use each). Buts thats not of interest []. Lacks time to do a nice implementation with enumerator/callcc.. maybe when I get really tired I do that ;-)
BTW: I cannot seem to get links working inside this RCR comment.
-- Simon Strandgaard
Just looked at the source code and I think your proposal would lead to unnecessary array creation in case you don't need one.
If you need an Array, you can type 4 letters extra: a.zip(b).map {...} => [...]
It's the same like #each returning self.
Although I'm not a native speaker, I don't think, zip is semantically demanding an array as return value.
Could you explain, how enumerator can be used, I have no idea ?
-- Matthias Georgi
I have made an iterator class, which is useful for multiway each. See .
require 'iterator' data_a = %w(a b c d) data_b = (0..3) ia = Iterator::Continuation.new(data_a, :each) ib = Iterator::Continuation.new(data_b, :each) result = [] while ia.has_next? and ib.has_next?
result << ia.current result << ib.current ia.next ib.next
end ia.close ib.close p result # ["a", 0, "b", 1, "c", 2, "d", 3]
OK, looks like Generator, SyncEnumerator from stdlib. But I would only use them to pass Iterators around or to avoid recursion in combination with a stack. For general use it's too javalike, i was so confident not to type these "while next?" thing anymore. So probably it's personal taste, for me it's a good habit to write
[ 1, 2, 3 ].zip([ 10, 20, 30 ]).map {|i,j| i * j }
I use this very often and Iteration based on Continuation is also not very performant.
-- Matthias Georgi
Have you ever felt wanting to supply a block to zip itself?
BTW: How do you convince a Perl girl to use Ruby, if you have to explain to her, that zip, is not zip in that paticular case, and you have to do a map afterwards!
-- Simon Strandgaard
OK, i see the inconsistency. I'm not sure, what i expect from zip. open() for example returns also different values, with and without a block.
-- Matthias Georgi
Usually open
is some kind of factory method, and factories can build many kinds of products. zip
is not really in that category.
For instance map
returns the same as if you had supplied a simple block. The same would be nice with zip
.
server> irb irb(main):001:0> a = %w(a b c) => ["a", "b", "c"] irb(main):002:0> a.map => ["a", "b", "c"] irb(main):003:0> a.map{|i| i} => ["a", "b", "c"] irb(main):004:0> a.zip => [ ["a"], ["b"], ["c"] ] irb(main):005:0> a.zip{|i| i} => nil ## [ ["a"], ["b"], ["c"] ] is what this RCR is about. irb(main):006:0>
The last line is what this RCR is about.
-- Simon Strandgaard
Back to RCRchive.
RCR Submission page and RCRchive powered by Ruby, Apache, RuWiki (modified), and RubLog