Natali K
Natali K

Reputation: 13

How to manually increment in ruby?

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

Answers (2)

Cary Swoveland
Cary Swoveland

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

Amadan
Amadan

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

Related Questions