Domanda

If we have an array

array = [1, 1, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3 ]

How can we identify the run (amount of consecutive numbers with same value) of a given number? By example:

run_pattern_for(array, 0) -> 2
run_pattern_for(array, 3) -> 1
run_pattern_for(array, 1) -> 1
run_pattern_for(array, 2) -> 0

There are no runs for 2 because there are no consecutive apparitions of two. There are one run for 3 because there are only one apparition with the tree as consecutive numbers.

È stato utile?

Soluzione

try:

class Array
  def count_runs(element)
    chunk {|n| n}.count {|a,b| a == element && b.length > 1}
  end
end

a = [1, 1, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3 ]
a.count_runs 0   #=> 2
a.count_runs 3   #=> 1
a.count_runs 1   #=> 1
a.count_runs 2   #=> 0

Altri suggerimenti

I agree with @BroiSatse that Enumerable#chunk should be used here, but I would like to show how an enumerator could be employed directly to solve this problem, using the methods Enumerator#next and Enumerator#peek.

Code

def count_em(array)
  return [] if array.empty?
  h = Hash.new(0)
  enum = array.each
  loop do
    x = enum.next
    if x == enum.peek
      h[x] += 1
      enum.next until (enum.peek != x)
    else
      h[x] = 0 unless h.key?(x)
    end
  end
  h
end  

Example

array = [1, 1, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3 ]
count_em(array) #=> {1=>1, 0=>2, 2=>0, 3=>1}

Explanation

Suppose

array = [1, 1, 1, 0, 2, 2]
h = Hash.new(0)
enum = array.each
  #=> #<Enumerator: [1, 1, 1, 0, 2, 2]:each>

x = enum.next #=> 1
enum.peek     #=> 1

so x == enum.peek #=> true, meaning there is a run of at least two 1's, so wish execute:

h[x] += 1 #=> h[1] += 1

which means

h[1] = h[1] + 1

Since h does not have a key 1, h[x] on the right side of the equality set to zero, the default value we established when creating the hash. Therefore, the hash h is now { 1=>1 }. Now we want need to enumerate and discard any more 1's in the run:

enum.next until (enum.peek != x)

enum.next #=> 1
enum.peek #=> 1
enum.next #=> 1
enum.peek #=> 0

Now go back to the top of the loop:

x = enum.next #=> 0
enum.peek     #=> 2

Since (x == enum.peek) => (0 == 2) => false, and h.key?(x) => false, we set

h[0] = 0

and the hash is now { 1=>1, 0=>0 }. Returning again to the top of the loop,

x = enum.next #=> 2
enum.peek     #=> 2

Since (x == enum.peek) => (2 == 2) => true, we execute:

h[2] += 1 #=> 1

so now h => {1=>1, 0=>0, 2=>1}. Now when we execute

x = enum.next #=> 2
enum.peek #=> StopIteration: iteration reached an end

The exception is rescued by Kernel#loop. That is, raising a StopIteration error is one way to break out of the loop, causing the last line of the method to be executed and returned:

h #=> {1=>1, 0=>0, 2=>1}

(Note this result differs from that in the example above because it is for a different array.)

Ruby 2.2, which was released roughly seven months after this question was posted, gave us a method that has application here, Enumerable#slice_when:

array.slice_when { |i,j| i != j }.each_with_object(Hash.new(0)) { |a,h|
  h[a.first] += (a.size > 1) ? 1 : 0 }  
  #=> {1=>1, 0=>2, 2=>0, 3=>1}

It's a simple task; Here are two different ways I've done it:

array = [1, 1, 0, 0, 2, 3, 0, 0, 0, 3, 3, 3 ]

hash = Hash[array.group_by { |e| e }.map{ |k, v| [k, v.size] }] 
# => {1=>2, 0=>5, 2=>1, 3=>4}

And:

hash = Hash.new{ |h,k| h[k] = 0 }
array.each { |e| hash[e] += 1 }
hash # => {1=>2, 0=>5, 2=>1, 3=>4}

Once you have the hash the rest is easy:

hash[0] # => 5
hash[1] # => 2
hash[2] # => 1
hash[3] # => 4

If it's possible you'll request a count for a number that didn't exist in the array, and want a numeric response instead of nil, use something like:

Integer(hash[4]) # => 0

Integer(...) converts nil to 0 for you.

In the first example above, group_by will do the heavy lifting, and results in:

array.group_by { |e| e } # => {1=>[1, 1], 0=>[0, 0, 0, 0, 0], 2=>[2], 3=>[3, 3, 3, 3]}

The map statement simply converts the array to its size.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top