Allow additional parameters to Enumerable#inject

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

An array can be used as a workaround:

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

lopex

Levin Alexander wrote:

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?

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

> 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&gt;
"...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%

···

On 2/3/06, Robert Klemme <bob.news@gmx.net> wrote:

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