How to remove duplicate elements in a 2D array

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

On Fri, Sep 25, 2009 at 4:34 PM, Li Chen [email protected] wrote:

...

together with other rows.
result = []
arr.each { |x| result = x if x[0] == ‘a’ }
result

?


Paul S.
http://www.nomadicfun.co.uk

[email protected]

On Fri, Sep 25, 2009 at 10:34 AM, Li Chen [email protected] wrote:

...

together with other rows.
Something like this perhaps:

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

Morning Li,

On Fri, Sep 25, 2009 at 8:34 AM, Li Chen [email protected] wrote:

...

together with other rows.

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

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

Sorry about the last response - hit the wrong button…

On Fri, Sep 25, 2009 at 8:34 AM, Li Chen [email protected] wrote:

...

together with other rows.

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

On Fri, Sep 25, 2009 at 4:52 PM, Paul S. [email protected]
wrote:

['c',3,2,1],

is possible to return a new array which contains the latest row having
problem. If you’re intending to eliminate duplicates, then use that
first element as the key to a hash.

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

I knew there was a a better way to do this:

arr.each{|x| result[x[0]] = x[1…-1]}

is a more succinct second line.


Paul S.
http://www.nomadicfun.co.uk

[email protected]

On Fri, Sep 25, 2009 at 4:39 PM, Paul S. [email protected]
wrote:

['a',1.3,2.2,3,3],

‘a’ ,
together with other rows.

result = []
arr.each { |x| result = x if x[0] == ‘a’ }
result

?

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 = {}
arr.each{|x| result[x[0]] = x[1,x.length-1]}

?


Paul S.
http://www.nomadicfun.co.uk

[email protected]


Paul S.
http://www.nomadicfun.co.uk

[email protected]

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

Thanks,

Li

Thairuby ->a, b {a + b} wrote:

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]]

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]]

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[]”

I change your arr

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.

Hi –

On Sat, 26 Sep 2009, Thairuby ->a, b {a + b} wrote:

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]]

Another way, at least in 1.9:

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

David

Hi –

On Sat, 26 Sep 2009, Thairuby ->a, b {a + b} wrote:

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]]

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

Thairuby ->a, b {a + b} wrote:

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

Thanks.

But I still have some doubts about this line:

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

On Sat, Sep 26, 2009 at 10:56 AM, Li Chen [email protected] wrote:

         }.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.

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

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

So that the result of the block is the hash and not y[1…-1] which is
the value of the assignment.

  1. #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}

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 = arr.inject({}){|x,y| x[y[0]]=y; 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.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

On Sat, Sep 26, 2009 at 12:34 AM, Li Chen [email protected] wrote:

...

together with other rows.

Thanks,

Li

Posted via http://www.ruby-forum.com/.

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

On Sep 26, 10:56 am, Li Chen [email protected] wrote:

          }.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.

The argument passed to inject is the initial value. Thus, we just want
the routine to start off with an empty hash.

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

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.

  1. #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.

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]]

Hi –

On Sun, 27 Sep 2009, Rick DeNatale wrote:

I like David Black’s alternative suggestion:

result = arr.inject({}){|x,y| x[y[0]]=y; 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.

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

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}

Thank you all for the inputs.

Li

Hi –

On Sun, 27 Sep 2009, pharrington wrote:

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}

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

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

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

David