# How to remove duplicate elements in a 2D array

Discussion in 'Ruby' started by Li Chen, Sep 25, 2009.

1. ### Li ChenGuest

Hi all,

I have a 2D array as follow:

arr=[

[ 'a',1,2,3],
['b',4,5,6],
['c',3,2,1],
['a',1.3,2.2,3,3],
...
['a', 2,1,3],
...
['a',2.1,1.5,3]

]

As you can see there are several rows containing element 'a',
and the last row containing 'a'' is the lastest one. I wonder if it
is possible to return a new array which contains the latest row having
'a' ,
together with other rows.

Thanks,

Li

Li Chen, Sep 25, 2009

2. ### Paul SmithGuest

result =3D []
arr.each { |x| result =3D x if x[0] =3D=3D 'a' }
result

?

--=20
Paul Smith

Paul Smith, Sep 25, 2009

3. ### Greg DonaldGuest

Something like this perhaps:

arr.collect{ |a| a.include?( 'a' ) ? a : nil }.compact

--=20
Greg Donald
http://destiney.com/

Greg Donald, Sep 25, 2009
4. ### John W HigginsGuest

[Note: parts of this message were removed to make it a legal post.]

Morning Li,

This would do it (not sure if it's optimal or not)

seen = []
arr.reverse.select{ |x|
check = !(seen.include?(x[0]))

John W Higgins, Sep 25, 2009
5. ### Paul SmithGuest

Sorry, this only solves the "Last row containing a" part of the
problem. If you're intending to eliminate duplicates, then use that
first element as the key to a hash.

result =3D {}
arr.each{|x| result[x[0]] =3D x[1,x.length-1]}

?

--=20
Paul Smith

Paul Smith, Sep 25, 2009
6. ### John W HigginsGuest

[Note: parts of this message were removed to make it a legal post.]

Sorry about the last response - hit the wrong button.....

Lets try this again shall we....

seen = [] # this stores the first elements as we pass through them
#we reverse the array because you want the last elements to be the ones
returned
#select will add an element to the return value if the block is true
arr.reverse.select{ |x|
check = !(seen.include?(x[0])) #we check to see if we've seen this first
element before
seen << x[0] #add the first element to our "seen" list - we don't care if
it's in here multiple times
check #the block equals the check value
}.reverse #since we reversed once we want to do so again to get the correct
order back

John

John W Higgins, Sep 25, 2009
7. ### Paul SmithGuest

Paul Smith, Sep 25, 2009
8. ### Thairuby ->a, b {a + b}Guest

I think you want an array with unique member. If the member is not
unique
then return the last occur of it.

arr=[[ 'a',1,2,3],
['b',4,5,6],
['c',3,2,1],
['a',1.3,2.2,3,3],
['a', 2,1,3],
['a',2.1,1.5,3]]

result = arr.inject({}){|x,y| x[y[0]]=y[1..-1]; x}.map(&:flatten)
p result

# [["a", 2.1, 1.5, 3], ["b", 4, 5, 6], ["c", 3, 2, 1]]

Thairuby ->a, b {a + b}, Sep 25, 2009
9. ### Li ChenGuest

Yes this is what I want. But can you explain the code line in a little
bit more?

Thanks,

Li

Li Chen, Sep 25, 2009
10. ### David A. BlackGuest

Hi --

Another way, at least in 1.9:

result = Hash[arr.map {|a| [a.first, a] }].values

David

David A. Black, Sep 25, 2009
11. ### Thairuby ->a, b {a + b}Guest

The main idea is to use Hash and use the first letter ("a","b","c") as a
key. I do it with "inject" and David do it with "Hash[]"

arr=[[ 'a',1,2,3],
['b',4,5,6],
['c',3,2,1],
['a',1.3,2.2,3,3],
['a', 2,1,3],
['a',2.1,1.5,3]]

to a hash

hash={'a'=>[1,2,3],
'b'=>[4,5,6],
'c'=>[3,2,1],
'a'=>[1.3,2.2,3,3],
'a'=>[2,1,3],
'a'=>[2.1,1.5,3]}

You can see that key "a" does repeat 4 times in hash. So, the last one
will replace the first one automatically.

finally,

hash = {"a"=>[2.1, 1.5, 3], "b"=>[4, 5, 6], "c"=>[3, 2, 1]}

The method ".map(&:flatten)" is use to converse hash back to array.

Thairuby ->a, b {a + b}, Sep 26, 2009
12. ### David A. BlackGuest

Hi --

You could economize on intermediate objects and method calls like
this:

result = arr.inject({}){|x,y| x[y[0]]=y; x}.values

That way, you don't take the first element out, so you don't have to
put it back.

David

David A. Black, Sep 26, 2009
13. ### Li ChenGuest

Thanks.

result = arr.inject({}){|x,y| x[y[0]]=y[1..-1];
x
}.map(&:flatten)

1) When I look at #inject method, I notice it only takes either no
augument or
an augument with initial value. But in your code it takes a hash. It is
kind of new/strange to me.

2) Why do you write an x alone after this line x[y[0]]=y[1..-1]; ?

3) #map(&:flatten) doesn't work in my Ruby version(1.8.6). Which version
are you using? But I make some changes and it works.

result = arr.inject( { } ){|x,y| x[y[0]]=y[1..-1]; x}.to_a
r=result.collect { |row| row.flatten}

Li

Li Chen, Sep 26, 2009
14. ### Rick DeNataleGuest

It's giving it an empty hash as the initial value, nothing strange at all.

?

So that the result of the block is the hash and not y[1..-1] which is
the value of the assignment.
I'm guessing that he's using either 1.8.7 or 1.9 both of which define
Symbol#to_proc which allows this, or perhaps he's using the
activesupport gem which is part of Rails and defines this as well.

I like David Black's alternative suggestion:

result =3D arr.inject({}){|x,y| x[y[0]]=3Dy; x}.values

Note that neither of these preserve the ordering of the rows retained
from the original array in Ruby 1.8.x, but if you're happy I guess
that's not a requirement.

--=20
Rick DeNatale

WWR: http://www.workingwithrails.com/person/9021-rick-denatale

Rick DeNatale, Sep 26, 2009
15. ### Harry KakuekiGuest

Here is another way.
It works with Ruby 1.8.6.

p arr.map{|x| x[0]}.uniq.map{|y| arr.reverse.detect{|z| z[0] == y}}

# OR if order is important,
#

p arr.reverse.map{|x| x[0]}.uniq.reverse.map{|y|
arr.reverse.detect{|z| z[0] == y}}

Harry

Harry Kakueki, Sep 26, 2009
16. ### David A. BlackGuest

Hi --

And in 1.9, you can do this:

arr.map.with_object({}) {|e,h| h[e[0]] = e }.values

which gets rid of the need to force the accumulator to be the hash
every time through.

David

David A. Black, Sep 26, 2009
17. ### pharringtonGuest

The argument passed to inject is the initial value. Thus, we just want
the routine to start off with an empty hash.
The value of the first argument to the block passed to inject is set
to the return value of the block. In Ruby everything's an expression,
so the return value of a method or block is just that of the last
expression run.
I don't have Ruby 1.8.6 installed and its been forever since I
remember using it last, so (works in 1.8.7 and up though) :\

Also, here's a rather nasty version that retains the order of the
initial array's elements, if that's important:

irb(main):050:0> arry = [['a', 1, 2, 3], ['b', 4, 5, 6], ['c', 3, 2,
1], ['a', 1.3, 2.2, 3, 3], ['a', 2, 1, 3], ['a', 2.1, 1.5, 3]]
=> [["a", 1, 2, 3], ["b", 4, 5, 6], ["c", 3, 2, 1], ["a", 1.3, 2.2, 3,
3], ["a", 2, 1, 3], ["a", 2.1, 1.5, 3]]
irb(main):051:0> arry.reverse.inject([]) {|r, e| r.unshift(e) if r.map
{|x| x.first if x.first == e.first}.compact.size == 0; r}
=> [["b", 4, 5, 6], ["c", 3, 2, 1], ["a", 2.1, 1.5, 3]]

pharrington, Sep 26, 2009
18. ### pharringtonGuest

r.map {|x| x.first if x.first == e.first}.compact.size == 0

I'm an idiot, Ruby likes to call this pile of silliness "detect":

arry.reverse.inject([]) {|r, e| r.unshift(e) unless r.detect {|x|
x.first == e.first}; r}

pharrington, Sep 26, 2009
19. ### David A. BlackGuest

Hi --

Couldn't resist throwing in another 1.9-esque version:

arr.group_by(&:first).map(&:last).map(&:last)

Of course mapping to [-1][-1] would work too (and probably more
efficiently).

David

David A. Black, Sep 26, 2009
20. ### Li ChenGuest

Thank you all for the inputs.

Li

Li Chen, Sep 26, 2009