David Dsr
David Dsr

Reputation: 347

get an array of arrays with unique elements

I have an array like this:

[1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]

I want to know if there's a method to get this:

[[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]

I know there is Array.uniq but this removes the duplicate elements, and I would like to keep them.

Upvotes: 0

Views: 553

Answers (5)

Cary Swoveland
Cary Swoveland

Reputation: 110675

Here are a couple of ways of doing it.

arr = [1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]

#1

b = []
a = arr.dup
while a.any?
  u = a.uniq
  b << u
  a = a.difference u
end
b
  #=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]] 

The helper Array#difference is defined in my answer here.

#2

arr.map { |n| [n, arr.count(n)] }
   .each_with_object({}) { |(n,cnt),h|
     (1..cnt).each { |i| (h[i] ||= []) << n } }
   .values
   .map(&:uniq)
  #=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]

The steps, for:

arr = [1, 2, 3, 3, 6, 6, 6, 7]

a = arr.map { |n| [n, arr.count(n)] }
  #=> [[1, 1], [2, 1], [3, 2], [3, 2], [4, 2], [4, 2],
  #    [5, 1], [6, 3], [6, 3], [6, 3], [7, 1]] 
enum = a.each_with_object({})
  #=> #<Enumerator: [[1, 1], [2, 1], [3, 2], [3, 2], [4, 2], [4, 2],
  #   [5, 1], [6, 3], [6, 3], [6, 3], [7, 1]]:each_with_object({})> 

To view the elements of enum:

enum.to_a
  #=> [[[1, 1], {}], [[2, 1], {}],...[[7, 1], {}]] 

Now step through the enumerator and examine the hash after each step:

(n,cnt),h = enum.next
    #=> [[1, 1], {}] 
n   #=> 1 
cnt #=> 1 
h   #=> {} 
(1..cnt).each { |i| (h[i] ||= []) << n }
h   #=> {1=>[1]} 

(n,cnt),h = enum.next
  #=> [[2, 1], {1=>[1]}] 
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2]} 

(n,cnt),h = enum.next
  #=> [[3, 2], {1=>[1, 2]}] 
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3], 2=>[3]} 

(n,cnt),h = enum.next
  #=> [[3, 2], {1=>[1, 2, 3], 2=>[3]}] 
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3], 2=>[3, 3]} 

(n,cnt),h = enum.next
  #=> [[6, 3], {1=>[1, 2, 3, 3], 2=>[3, 3]}] 
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3, 6], 2=>[3, 3, 6], 3=>[6]} 

(n,cnt),h = enum.next
  #=> [[6, 3], {1=>[1, 2, 3, 3, 6], 2=>[3, 3, 6], 3=>[6]}] 
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3, 6, 6], 2=>[3, 3, 6, 6], 3=>[6, 6]} 

(n,cnt),h = enum.next
  #=> [[6, 3], {1=>[1, 2, 3, 3, 6, 6], 2=>[3, 3, 6, 6], 3=>[6, 6]}] 
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3, 6, 6, 6], 2=>[3, 3, 6, 6, 6], 3=>[6, 6, 6]} 

(n,cnt),h = enum.next
  #=> [[7, 1], {1=>[1, 2, 3, 3, 6, 6, 6], 2=>[3, 3, 6, 6, 6], 3=>[6, 6, 6]}] 
(1..cnt).each { |i| (h[i] ||= []) << n }
h #=> {1=>[1, 2, 3, 3, 6, 6, 6, 7], 2=>[3, 3, 6, 6, 6], 3=>[6, 6, 6]} 

Lastly, extract and uniqify the values of the hash:

b = h.values
  #=> [[1, 2, 3, 3, 6, 6, 6, 7], [3, 3, 6, 6, 6], [6, 6, 6]]
b.map(&:uniq)
  #=> [[1, 2, 3, 6, 7], [3, 6], [6]]

Upvotes: 0

David Lilue
David Lilue

Reputation: 631

On ruby you could add a method to the class Array. Like this:

class Array
    def uniqA (acc)
        return acc if self.empty?
        # return self.replace acc if self.empty? #to change the object itself
        acc << self.uniq

        self.uniq.each { |x| self.delete_at(self.index(x)) }

        uniqA(acc)
    end
end

b = [1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]

print b.uniqA([])
 #=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]

print b
 #=> []

Or you could do this, to keep the elements on b:

b = b.uniqA([])
 #=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]

print b
 #=> [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]

Upvotes: 0

sawa
sawa

Reputation: 168081

[1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
.each.with_object([]){|e, a| (a.find{|b| !b.include?(e)} || a.push([]).last).push(e)}
# => [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]

Upvotes: 0

zwippie
zwippie

Reputation: 15515

A simple solution, but I'm sure it will not have the best performance:

def array_groups(arr)
  result = []
  arr.uniq.each do |elem|
    arr.count(elem).times do |n|
      result[n] ||= []
      result[n] << elem
    end
  end
  result
end

array_groups [1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
# [[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]

Upvotes: 0

Philip Hallstrom
Philip Hallstrom

Reputation: 19879

Not sure about performance, but this works:

Code:

$ cat foo.rb
require 'pp'

array = [1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7]
result = []

values = array.group_by{|e| e}.values

while !values.empty?
  result << values.map{|e| e.slice!(0,1)}.flatten
  values = values.reject!{|e| e.empty?}
end

pp result

Output:

$ ruby foo.rb
[[1, 2, 3, 4, 5, 6, 7], [3, 4, 6], [6]]

Upvotes: 1

Related Questions