0

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.

sawa
  • 165,429
  • 45
  • 277
  • 381
David Dsr
  • 327
  • 2
  • 5
  • 19

5 Answers5

1

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]]
Philip Hallstrom
  • 19,673
  • 2
  • 42
  • 46
0

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]]
Community
  • 1
  • 1
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

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]]
David Lilue
  • 597
  • 2
  • 14
  • Why did you open `Array` class ? If Matz get to know that you are opening his class, he will feel bad! Take it out from there. – Arup Rakshit Feb 21 '15 at 19:56
  • No man....the language is designed to allow open every class anytime on your code. That one of the beatiful features of Ruby :) – David Lilue Feb 21 '15 at 20:24
  • 1
    @DavidLilue I like your answer, `Array` method or not. :) Giving `acc` a default value of `[]` might be nice. Also, I think you could scrap all references to `self`. (Like `acc << uniq`) Oh, and your method name is not very Rubyesque. ;) – zwippie Feb 21 '15 at 20:24
  • 1
    Wait, this method leaves the original array empty. That's bad. – zwippie Feb 21 '15 at 20:32
  • Depend on what you what. But you could change this line: `return acc if self.empty?` for `return self.replace acc if self.empty?`. And give the opcional value to `acc` – David Lilue Feb 21 '15 at 20:42
0

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]]
zwippie
  • 15,050
  • 3
  • 39
  • 54
0
[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]]
sawa
  • 165,429
  • 45
  • 277
  • 381