Add element to an array if it's not there already
Asked Answered
V

6

114

I have a Ruby class

class MyClass
  attr_writer :item1, :item2
end

my_array = get_array_of_my_class() #my_array is an array of MyClass
unique_array_of_item1 = []

I want to push MyClass#item1 to unique_array_of_item1, but only if unique_array_of_item1 doesn't contain that item1 yet. There is a simple solution I know: just iterate through my_array and check if unique_array_of_item1 already contains the current item1 or not.

Is there any more efficient solution?

Vinavinaceous answered 22/12, 2012 at 16:7 Comment(0)
D
91

You can use Set instead of Array.

Delineator answered 22/12, 2012 at 16:9 Comment(2)
While it is true that the docs say that Sets are not order, they are in fact (as of Ruby 1.9) ordered. If you look at the code, the main methods you'd use to get at the order (such as Set#each and Set#to_a) delegate to @hash. And as of Ruby 1.9 Hashes are ordered. "Hashes enumerate their values in the order that the corresponding keys were inserted." ruby-doc.org/core-1.9.1/Hash.htmlSouth
Never new there was such a thing as a set. They are awesome, thank you so muchRuble
C
164

You can use | (union):

my_array | [item]

And to update my_array in place:

my_array |= [item]
Camber answered 5/11, 2013 at 19:28 Comment(2)
or my_array |= [item] which will update my_array in placeBighorn
What's the complexity of this?Unlookedfor
D
91

You can use Set instead of Array.

Delineator answered 22/12, 2012 at 16:9 Comment(2)
While it is true that the docs say that Sets are not order, they are in fact (as of Ruby 1.9) ordered. If you look at the code, the main methods you'd use to get at the order (such as Set#each and Set#to_a) delegate to @hash. And as of Ruby 1.9 Hashes are ordered. "Hashes enumerate their values in the order that the corresponding keys were inserted." ruby-doc.org/core-1.9.1/Hash.htmlSouth
Never new there was such a thing as a set. They are awesome, thank you so muchRuble
E
42

You don't need to iterate through my_array by hand.

my_array.push(item1) unless my_array.include?(item1)

Edit:

As Tombart points out in his comment, using Array#include? is not very efficient. I'd say the performance impact is negligible for small Arrays, but you might want to go with Set for bigger ones.

Eighteen answered 23/12, 2012 at 2:17 Comment(3)
you definitely don't wanna do that! array.include?(item) has complexity O(n) - so it's like iterating the whole array. have a look at this benchmark: gist.github.com/deric/4953652Danforth
Beautiful solution :). Readable! In my case, I cannot have a set, so I'm using this solution.Perceval
You can also use binary search to increase the performance if the array is ordered. [1, 2, 3, 4, 5].bsearch { |e| e == 3 }Perceval
U
35

You can convert item1 to array and join them:

my_array | [item1]
Utrecht answered 20/8, 2013 at 9:4 Comment(0)
R
4

Important to keep in mind that the Set class and the | method (also called "Set Union") will yield an array of unique elements, which is great if you want no duplicates but which will be an unpleasant surprise if you have non-unique elements in your original array by design.

If you have at least one duplicate element in your original array that you don't want to lose, iterating through the array with an early return is worst-case O(n), which isn't too bad in the grand scheme of things.

class Array
  def add_if_unique element
    return self if include? element
    push element
  end
end
Rowlock answered 14/3, 2014 at 8:43 Comment(0)
P
0

I'm not sure if it's perfect solution, but worked for me:

    host_group = Array.new if not host_group.kind_of?(Array)
    host_group.push(host)
Pteryla answered 8/1, 2016 at 17:24 Comment(1)
This did not work for you, unless uniqueness isn't a constraint. It does not uniquely add elements to the array. >> a = [1,2,3]; => [1, 2, 3] >> a.push(3); => [1, 2, 3, 3] >> a.push(3); => [1, 2, 3, 3, 3] >> a.push(3); => [1, 2, 3, 3, 3, 3]Esqueda

© 2022 - 2024 — McMap. All rights reserved.