earlyadopter
earlyadopter

Reputation: 1547

ruby: how to find non-unique elements in array and print each with number of occurrences?

I have

a = ["a", "d", "c", "b", "b", "c", "c"]

and need to print something like (sorted descending by number of occurrences):

c:3
b:2

I understand first part (finding NON-unique) is:

b = a.select{ |e| a.count(e) > 1 }
=> ["c", "b", "b", "c", "c"] 

or

puts b.select{|e, c| [e, a.count(e)] }.uniq

c
b

How to output each non-unique with number of occurrences sorted backwards?

Upvotes: 21

Views: 12939

Answers (8)

Ja͢ck
Ja͢ck

Reputation: 173532

From Ruby 2.7, you can utilise Enumerable#tally and numbered block arguments:

a = ["a", "d", "c", "b", "b", "c", "c"]
puts a.tally.filter { _2 > 1 }.sort_by { -_2 }.map &:first

Here, Enumerable#tally returns a hash like { 'a' => 1, 'b' => 2, ... }, which you then have to filter and sort. After sorting, the hash would've collapsed to a nested array, e.g. [['b', 2], ...]. The last step is to take the first argument of each array element, using &:first.

Upvotes: 3

drhenner
drhenner

Reputation: 2230

I personally like this solution:

 a.inject({}) {|hash, val| hash[val] ||= 0; hash[val] += 1; hash}.
   reject{|key, value| value == 1}.sort.reverse.
   each_pair{|k,v| puts("#{k}:#{v}")}

Upvotes: 1

undur_gongor
undur_gongor

Reputation: 15954

puts a.uniq.
       map { | e | [a.count(e), e] }.
       select { | c, _ | c > 1 }.
       sort.reverse.
       map { | c, e | "#{e}:#{c}" }

Upvotes: 22

oldergod
oldergod

Reputation: 15010

puts a.uniq.
     map { |e| a.count(e) > 1 ? [e, a.count(e)] : nil }.compact.
     sort { |a, b| b.last <=> a.last }

Upvotes: 0

pguardiario
pguardiario

Reputation: 54984

How about:

a.sort.chunk{|x| a.count(x)}.sort.reverse.each do |n, v|
  puts "#{v[0]}:#{n}" if n > 1
end

Upvotes: 1

Huluk
Huluk

Reputation: 874

This will give you a hash with element => occurrences:

b.reduce(Hash.new(0)) do |hash, element|
  hash[element] += 1
  hash
end

Upvotes: 0

the Tin Man
the Tin Man

Reputation: 160551

The group_by method is used for this often:

a.group_by{ |i| i }
{
    "a" => [
        [0] "a"
    ],
    "d" => [
        [0] "d"
    ],
    "c" => [
        [0] "c",
        [1] "c",
        [2] "c"
    ],
    "b" => [
        [0] "b",
        [1] "b"
    ]
}

I like:

a.group_by{ |i| i }.each_with_object({}) { |(k,v), h| h[k] = v.size }
{
    "a" => 1,
    "d" => 1,
    "c" => 3,
    "b" => 2
}

Or:

Hash[a.group_by{ |i| i }.map{ |k,v| [k, v.size] }]
{
    "a" => 1,
    "d" => 1,
    "c" => 3,
    "b" => 2
}

One of those might scratch your itch. From there you can reduce the result using a little test:

Hash[a.group_by{ |i| i }.map{ |k,v| v.size > 1 && [k, v.size] }]
{
    "c" => 3,
    "b" => 2
}

If you just want to print the information use:

puts a.group_by{ |i| i }.map{ |k,v| "#{k}: #{v.size}" }
a: 1
d: 1
c: 3
b: 2

Upvotes: 8

maerics
maerics

Reputation: 156384

a.reduce(Hash.new(0)) { |memo,x| memo[x] += 1; memo } # Frequency count.
  .select { |_,count| count > 1 } # Choose non-unique items.
  .sort_by { |x| -x[1] } # Sort by number of occurrences descending.
# => [["c", 3], ["b", 2]]

Also:

a.group_by{|x|x}.map{|k,v|[k,v.size]}.select{|x|x[1]>1}.sort_by{|x|-x[1]}
# => [["c", 3], ["b", 2]]

Upvotes: 0

Related Questions