user4187589
user4187589

Reputation:

Counting several elements inside an array

I just wrote a method that I'm pretty sure is terribly written. I can't figure out if there is a better way to write this in ruby. It's just a simple loop that is counting stuff.

Of course, I could use a select or something like that, but that would require looping twice on my array. Is there a way to increment several variables by looping without declaring the field before the loop? Something like a multiple select, I don't know. It's even worst when I have more counters.

Thank you!

failed_tests = 0
passed_tests = 0
tests.each do |test|
  case test.status
  when :failed
    failed_tests += 1
  when :passed
    passed_tests +=1
  end
end

Upvotes: 0

Views: 146

Answers (5)

lllllll
lllllll

Reputation: 4855

Assuming Rails 4 ( using 4.0.x here). I would suggest:

tests.group(:status).count
# -> {'passed' => 2, 'failed' => 34, 'anyotherstatus' => 12}

This will group all records by any possible :status value, and count each individual ocurrence.

Edit: adding a Rails-free approach

Hash[tests.group_by(&:status).map{|k,v| [k,v.size]}]
  1. Group by each element's value.
  2. Map the grouping to an array of [value, counter] pairs.
  3. Turn the array of paris into key-values within a Hash, i.e. accessible via result[1]=2 ....

Upvotes: 1

hash = test.reduce(Hash.new(0)) { |hash,element| hash[element.status] += 1; hash }

this will return a hash with the count of the elements. ex:

class Test
  attr_reader :status

  def initialize
    @status = ['pass', 'failed'].sample
  end
end

array = []
5.times { array.push Test.new }

hash = array.reduce(Hash.new(0)) { |hash,element| hash[element.status] += 1; hash }

=> {"failed"=>3, "pass"=>2}

Upvotes: 1

KARASZI István
KARASZI István

Reputation: 31477

You can use the #reduce method:

failed, passed = tests.reduce([0, 0]) do |(failed, passed), test|
  case test.status
  when :failed
    [failed + 1, passed]
  when :passed
    [failed, passed + 1]
  else
    [failed, passed]
  end
end

Or with a Hash with default value, this will work with any statuses:

tests.reduce(Hash.new(0)) do |counter, test|
  counter[test.status] += 1
  counter
end

Or even enhancing this with @fivedigit's idea:

tests.each_with_object(Hash.new(0)) do |test, counter|
  counter[test.status] += 1
end

Upvotes: 2

Dinesh Panchananam
Dinesh Panchananam

Reputation: 694

res_array = tests.map{|test| test.status}
failed_tests = res_array.count :failed
passed_tests = res_array.count :passed

Upvotes: -1

fivedigit
fivedigit

Reputation: 18702

You could do something clever like this:

tests.each_with_object(failed: 0, passed: 0) do |test, memo|
  memo[test.status] += 1
end
# => { failed: 1, passed: 10 }

Upvotes: 2

Related Questions