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
题
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.
解决方案
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
其他提示
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.