get an array of arrays with unique elements
Asked Answered
C

5

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.

Compo answered 21/2, 2015 at 19:3 Comment(2)
Take a look at https://mcmap.net/q/131223/-ruby-list-minus. It's not the same question, but it should get you started.Bindman
It is not clear how the elements are grouped.Noemi
L
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]]
Lucania answered 21/2, 2015 at 19:26 Comment(4)
Thank you. It works right¡¡ But It works as well without require 'pp'. 'p' it comes out the same result.Compo
Yeah, requiring 'pp' is a habit of mine :)Lucania
I would vote you up but I can't because it says that my reputation has to be 15 points. I'm sorry.Compo
Don't worry about it! 😀Lucania
C
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]]
Cyrstalcyrus answered 21/2, 2015 at 19:30 Comment(0)
V
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]]
Vexed answered 21/2, 2015 at 19:40 Comment(5)
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.Briny
No man....the language is designed to allow open every class anytime on your code. That one of the beatiful features of Ruby :)Vexed
@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. ;)Livestock
Wait, this method leaves the original array empty. That's bad.Livestock
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 accVexed
L
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]]
Livestock answered 21/2, 2015 at 19:46 Comment(0)
N
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]]
Noemi answered 21/2, 2015 at 19:50 Comment(1)
a=[[1]]; a.find{|b| !b.include?(2)}.push(2); a=[[1,2]],why?Agony

© 2022 - 2024 — McMap. All rights reserved.