Struggling with variable arguments to block

Discussion in 'Ruby' started by Gavin Sinclair, Oct 24, 2003.

  1. Hi -talk,

    I'm having trouble dealing with a block that yields one parameter but
    declares two. I've pasted complete test code below. Basically, I've
    defined "sum" on Enumerable that sums the result of yielding each value.
    This is for demonstration of the problem only.

    When I define it like this

    def sum
    result = 0
    self.each { |elt| result += yield(elt) }
    result
    end

    {1=>2}.sum { |k,v| v } works but gives a warning.
    [[1,2]].sum { |a,b| b } is fine.

    When I define it like this

    def sum
    result = 0
    self.each { |*elt| result += yield(*elt) }
    result
    end

    {1=>2}.sum { |k,v| v } is fine.
    [[1,2]].sum { |a,b| b } gives an error.


    Below are the two definitions and a complete test case. In the test case,
    the range examples have no problems so are uncommented. Comments are
    given in the other cases.

    I would really like to avoid the warning associated with the hash in
    test_sum1h.

    Thanks for any advice,
    Gavin

    --------------------------------------------------------------------------


    module Enumerable
    #
    # Yield each element and return the (strictly numerical) sum of the
    # results. sum1 and sum2 are different implementations.
    #

    def sum1
    result = 0
    self.each do |elt|
    result += yield(elt)
    end
    result
    end

    def sum2
    result = 0
    self.each do |*elt|
    result += yield(*elt)
    end
    result
    end
    end


    require 'test/unit'

    #
    # Test case: set up 3 enumerable objects, and try sum1 and sum2 on
    # each of them.
    #
    class TC_Sum < Test::Unit::TestCase
    def setup
    @r = (1..10) # We'll sum the squares (385)
    @a = [ [1,2], [3,4], [5,6] ] # We'll sum the products (44)
    @h = { :a => 1, :b => 2, :c => 3 } # We'll sum the values (6)
    end

    # ======== First implementation

    def test_sum1r
    puts "test_sum1r"
    sum = @r.sum1 { |x| x ** 2 }
    assert_equal(385, sum)
    end

    def test_sum1a
    puts "test_sum1a"
    sum = @a.sum1 { |x,y| x * y } # Passes.
    assert_equal(44, sum)
    end

    def test_sum1h
    puts "test_sum1h"
    sum = @h.sum1 { |k,v| v } # Passes, with warning:
    assert_equal(6, sum) # multiple values for a block
    end # parameter (2 for 1)

    # ======== Second implementation

    def test_sum2r
    puts "test_sum2r"
    sum = @r.sum2 { |x| x ** 2 }
    assert_equal(385, sum)
    end

    def test_sum2a
    puts "test_sum2a"
    sum = @a.sum2 { |x,y| x * y } # Error: no implicit conversion
    assert_equal(44, sum) # from nil to integer
    end

    def test_sum2h
    puts "test_sum2h"
    sum = @h.sum2 { |k,v| v } # Passes.
    assert_equal(6, sum)
    end
    end
     
    Gavin Sinclair, Oct 24, 2003
    #1
    1. Advertisements

  2. Gavin Sinclair

    Dan Doel Guest

    Which version of ruby are you using?

    I copied, pasted and got no errors with the mswin32 1.8.0 build.

    - Dan
     
    Dan Doel, Oct 24, 2003
    #2
    1. Advertisements

  3. Ah, good question.

    ruby 1.8.0 (2003-09-01) [i386-cygwin]
    Thanks for the report. I'll try the very latest version.

    Gavin
     
    Gavin Sinclair, Oct 24, 2003
    #3
  4. Nope, c.l.r here. :)
    [snip]

    Could it be that the heart of your problem is, that you're trying to sum
    things up that can't be summed up? Consider this:

    elems = [ [1,2,3], [10], [23,145,23] ]

    elems contains Arrays and there is no such thing as Array#+ - for good
    reasons. If you're after the sum, you typically need rather something
    like this:

    sum = elems.map {|e| extract_a_numeric_value(e)}.inject( 0 ){|c,e|c+e}

    Where extract_a_numeric_value is the crucial part.

    Just my EUR 0.02...

    Regards

    robert
     
    Robert Klemme, Oct 24, 2003
    #4
  5. Gavin Sinclair

    ts Guest

    R> elems contains Arrays and there is no such thing as Array#+

    svg% ruby -e 'p [1]+[2]'
    [1, 2]
    svg%

    R> - for good reasons.

    which are ?
     
    ts, Oct 24, 2003
    #5
  6. :)

    Apparently I wasn't clear enough:

    irb(main):002:0> [2]+[3,4]
    => [2, 3, 4]
    irb(main):003:0>

    That's not the plus Gavin was looking for. His example was about adding
    numeric values from arrays. Something whose functionality would resemble
    this line:

    irb(main):003:0> [[2],[3,4]].map{|e|e[0]}.inject(0){|c,e|c+e}
    => 5
    irb(main):004:0>

    However, the point was that there were problems with hashes and nested
    arrays when used with his defined method sum

    def sum
    result = 0
    self.each { |*elt| result += yield(*elt) }
    result
    end

    My point was that these problems resulted from the attempt to write a
    generalized sum method for things that are not easily summed, i.e.,
    arrays, which can have differing lengths and can contain arbitrary types
    that are not necessarily useful for numerical summing up.

    IOW, he is looking for a solution to the yield problem while I suggested,
    that the real problem here was an inappropriate approach. Hopefully this
    has become clearer now...

    Regards

    robert
     
    Robert Klemme, Oct 24, 2003
    #6
  7. Hi,

    In message "Struggling with variable arguments to block"

    |I'm having trouble dealing with a block that yields one parameter but
    |declares two. I've pasted complete test code below. Basically, I've
    |defined "sum" on Enumerable that sums the result of yielding each value.
    |This is for demonstration of the problem only.

    I smell something wrong. Hmm...<thinking it a while>

    I get it. Hash#each should have passed single value to the block,
    unlike Hash#each_pair. It will be fixed soon.

    matz.
    c
     
    Yukihiro Matsumoto, Oct 24, 2003
    #7
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.