Forum: Ruby Allow additional parameters to Enumerable#inject

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Levin A. (Guest)
on 2006-02-03 17:00
(Received via mailing list)
Hi,

I wanted to count consecutive elements in an array:

  [1,1,1,2,2,2,2,3,4,4,4,4].count_streams #=> [3,4,1,4]

  module Enumerable
    def count_streams
      last_seen = nil
      self.inject([]) { |a, elem|
        if last_seen == elem then a[-1] += 1 else a << 1 end
        last_seen = elem
        a
      }
    end
  end

However, the assignment to "last" outside of the block seems ugly, so
I changed inject to take additional parameters:

  module Enumerable
    # works just like regular inject, but passes the additional
parameters
    # to the block
    def inject_with_state(memo, *other)
      self.each { |obj|
        memo, *other = yield(memo, obj, *other)
      }
      memo
    end

    def count_streams
      self.inject_with_state([], nil) { |a, elem, last_seen|
        if last_seen == elem then a[-1] += 1 else a << 1 end
        [a, elem]
      }
    end
  end

Is this a good solution to the problem?

I think that inject_with_state could be made fully backwards
compatible to inject, it would be nice if inject could be changed to
support this.  What do you think?


Viele Grü�e,
Levin
Marcin MielżyÅ?ski (Guest)
on 2006-02-03 17:18
(Received via mailing list)
An array can be used as a workaround:

[1,2,3].inject([[],arg]) { |(a,b), elem|
     ...
     [a,arg]
}

lopex
Robert K. (Guest)
on 2006-02-03 17:24
(Received via mailing list)
Levin A. wrote:
>         if last_seen == elem then a[-1] += 1 else a << 1 end
>     # works just like regular inject, but passes the additional
>         if last_seen == elem then a[-1] += 1 else a << 1 end
>         [a, elem]
>       }
>     end
>   end
>
> Is this a good solution to the problem?
>
> I think that inject_with_state could be made fully backwards
> compatible to inject, it would be nice if inject could be changed to
> support this.  What do you think?

You don't need to redefine Enumerable#inject for this.

>> def count_streams(enum)
>>   enum.inject([[],nil]) {|(a, last),e| e == last ? a[-1]+=1 : a<<1; [a,
e]}[0]
>> end
=> nil
>> count_streams [1,1,1,2,2,2,2,3,4,4,4,4]
=> [3, 4, 1, 4]

You can as well do somehting like this:

>> def count_streams_2(enum)
>>   enum.inject([]) do |a, e|
?>     a.empty? || e != a[-1][0] ? a << [e,1] : a[-1][1]+=1
>>     a
>>   end
>> end
=> nil
>> count_streams_2 [1,1,1,2,2,2,2,3,4,4,4,4]
=> [[1, 3], [2, 4], [3, 1], [4, 4]]
>> count_streams_2([1,1,1,2,2,2,2,3,4,4,4,4]).map {|a| a[1]}
=> [3, 4, 1, 4]

Also, I'm not sure it's a good idea to put this method in Enumerable.
Is
it really general enough?

Kind regards

    robert
Levin A. (Guest)
on 2006-02-03 18:10
(Received via mailing list)
On 2/3/06, Robert K. <removed_email_address@domain.invalid> wrote:
> > I think that inject_with_state could be made fully backwards
> > compatible to inject, it would be nice if inject could be changed to
> > support this.  What do you think?
>
> You don't need to redefine Enumerable#inject for this.
>
> >> def count_streams(enum)
> >>   enum.inject([[],nil]) {|(a, last),e|

Thanks, I was not aware that parameters can be grouped like that.

But I think that an Enumerable#inject with additional parameters would
also be useful for many other things, like removing duplicates:

  def remove_duplicates1(enum)
    enum.inject([[], nil]) {|(arr, last),elem| arr << elem unless elem
== last; [arr, elem] }[0]
  end
  def remove_duplicates2(enum)
    enum.inject([], nil) {|(arr, elem, last| arr << elem unless elem
== last; [arr, elem] }
  end

The second one seems looks a lot cleaner.

> Also, I'm not sure it's a good idea to put this method in Enumerable.  Is
> it really general enough?

No, it probably is not.  This was just a for small experiment (*), i
would not put this into a library.
(However, it seems to be preferred to put methods into the class
"where they belong" in Ruby instead of using helper classes)

Thank you,
Levin


(*) <http://www.rexswain.com/benford.html>
"...It showed, he said, that the overwhelming odds are that at some
point in a series of 200 tosses, either heads or tails will come up
six or more times in a row."

  (0..10_000).select {
    (0..200).map { rand 2 }.inject([[],nil]) {|(a,last),e|
      if e==last then a[-1]+=1 else a << 1 end
      [a,e]
    }[0].partition { |e| e >= 6 }.first.empty?
  }.length / 10_000.0
  #=> 0.0349

"overwhelming odds" --> 97%
Matthew M. (Guest)
on 2006-02-03 19:10
(Received via mailing list)
Not really about inject specifically...  just attempting another way

a = [1,1,1,2,2,2,2,3,4,4,4,4]

a.zip([0] + a).map { |i, j| i - j }.inject([]) do |s, k|
    k.zero? ? s[0...-1] + [s[-1] + 1] : s << k
end
This topic is locked and can not be replied to.