How to remove duplicate elements in a 2D array

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

  1. Li Chen

    Li Chen Guest

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

  2. Li Chen

    Paul Smith Guest

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

    ?

    --=20
    Paul Smith
    http://www.nomadicfun.co.uk

     
    Paul Smith, Sep 25, 2009
    #2
    1. Advertisements

  3. Li Chen

    Greg Donald Guest


    Something like this perhaps:

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



    --=20
    Greg Donald
    http://destiney.com/
     
    Greg Donald, Sep 25, 2009
    #3
  4. [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
    #4
  5. Li Chen

    Paul Smith Guest

    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
    http://www.nomadicfun.co.uk

     
    Paul Smith, Sep 25, 2009
    #5
  6. [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
    #6
  7. Li Chen

    Paul Smith Guest

     
    Paul Smith, Sep 25, 2009
    #7
  8. 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
    #8
  9. Li Chen

    Li Chen Guest

    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
    #9
  10. Hi --

    Another way, at least in 1.9:

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


    David
     
    David A. Black, Sep 25, 2009
    #10
  11. 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.
     
    Thairuby ->a, b {a + b}, Sep 26, 2009
    #11
  12. 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
    #12
  13. Li Chen

    Li Chen Guest


    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
     
    Li Chen, Sep 26, 2009
    #13
  14. 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

    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
     
    Rick DeNatale, Sep 26, 2009
    #14
  15. 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
    #15
  16. 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
    #16
  17. Li Chen

    pharrington Guest

    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
    #17
  18. Li Chen

    pharrington Guest

    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
    #18
  19. 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
    #19
  20. Li Chen

    Li Chen Guest

    Thank you all for the inputs.

    Li
     
    Li Chen, Sep 26, 2009
    #20
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.