Reputation: 13
Given an array of objects, whats a more "manual" way of incrementing to find the object that occurs greater than 2 times? I've figured out two "shorter" methods of solving this but am wondering if there's a more abstract way, a way to "manually" increment with a counter of some sort... The result should return an array of contributors that occur more than twice.
i.e. [#<Author:0x00007ff447a94c58 @name="Alice Allison">, #<Author:0x00007ff447a94c08 @name="Bob Bryce">]
contributors =
=> #[<Author:0x00007fccbc220a48 @name="Dassey Davidson">,
#<Author:0x00007fe1d9374de0 @name="Alice Allison">,
#<Author:0x00007fe1d9374de0 @name="Alice Allison">,
#<Author:0x00007fe1d9374de0 @name="Alice Allison">,
#<Author:0x00007fe1d9374d90 @name="Bob Bryce">,
#<Author:0x00007fe1d9374d90 @name="Bob Bryce">,
#<Author:0x00007fe1d9374d90 @name="Bob Bryce">]
# method 1:
def contributing_authors
contributors.select do |contributor|
contributors.count(contributor) > 2
end.uniq
end
# method 2:
def new_contributing_authors
storage = []
contributors.each do |contributor|
if contributors.count(contributor) > 2
storage << contributor
end
end
storage.uniq
end
Upvotes: 1
Views: 212
Reputation: 110745
class Author
attr_reader :name
def initialize(name)
@name = name
end
end
contributors = [["Dassey Davidson", 1], ["Alice Allison", 3],
["Bob Bryce", 3]].
flat_map { |name, reps| [Author.new(name)] * reps }
#=> [#<Author:0x00005bd271073ce0 @name="Dassey Davidson">,
# #<Author:0x00005bd271073c68 @name="Alice Allison">,
# #<Author:0x00005bd271073c68 @name="Alice Allison">,
# #<Author:0x00005bd271073c68 @name="Alice Allison">,
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">,
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">,
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">]
We can object the desired array as follows.
contributors.group_by(&:itself).
select { |_, arr| arr.size > 2 }.
keys
#=> [#<Author:0x00005bd271073c68 @name="Alice Allison">,
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">]
See Enumerable#group_by, Object#itself and Hash#select (which, unlike Array#select
and Enumerable#select
, returns a hash).
The steps are as follows:
g = contributors.group_by(&:itself)
#=> {#<Author:0x00005bd271073ce0 @name="Dassey Davidson">=>
# [#<Author:0x00005bd271073ce0 @name="Dassey Davidson">],
# #<Author:0x00005bd271073c68 @name="Alice Allison">=>
# [#<Author:0x00005bd271073c68 @name="Alice Allison">,
# #<Author:0x00005bd271073c68 @name="Alice Allison">,
# #<Author:0x00005bd271073c68 @name="Alice Allison">],
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">=>
# [#<Author:0x00005bd271073bc8 @name="Bob Bryce">,
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">,
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">]}
h = g.select { |_, arr| arr.size > 2 }
#=> {#<Author:0x00005bd271073c68 @name="Alice Allison">=>
# [#<Author:0x00005bd271073c68 @name="Alice Allison">,
# #<Author:0x00005bd271073c68 @name="Alice Allison">,
# #<Author:0x00005bd271073c68 @name="Alice Allison">],
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">=>
# [#<Author:0x00005bd271073bc8 @name="Bob Bryce">,
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">,
# #<Author:0x00005bd271073bc8 @name="Bob Bryce">]}
h.keys
#=> <as above>
This approach, using Enumerable#group_by
, and @Amadan's, using the form of Hash::new
that uses a default value of zero, are the two standard ways of dealing with problems of this kind. The choice is largely a matter of taste. I generally prefer the Hash::new
method, but either will do.
Some time ago I proposed that a method Array#difference be added to the Ruby core.1 Alas, the proposal went nowhere. Here it would provide a simple solution:
(contributors.difference((contributors.uniq * 2).flatten)).uniq
1 Not to be confused with the method Array#difference that made its debut in v2.6.
Upvotes: 0
Reputation: 198476
Using Array#count
in a loop is suboptimal (complexity O(N^2)
).
To increment a variable, you use v += 1
. This is the natural way of doing it:
counts = contributors.each_with_object(Hash.new(0)) { |contributor, counts|
counts[contributor] += 1
}
more_than_twice = counts.select { |contributor, count| count > 2 }.keys
Upvotes: 2