Ajouter un élément à un tableau s'il n'y est pas déjà

92

J'ai une classe Ruby

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

Je veux pousser MyClass#item1à unique_array_of_item1, mais seulement si unique_array_of_item1ne contient pas item1encore. Il existe une solution simple que je connais: il suffit de parcourir my_arrayet de vérifier si unique_array_of_item1contient déjà le courant item1ou non.

Existe-t-il une solution plus efficace?

Alan Coromano
la source

Réponses:

82

Vous pouvez utiliser Set au lieu de Array.

Jiří Pospíšil
la source
S'il est vrai que la documentation dit que les ensembles ne sont pas d'ordre, ils sont en fait (à partir de Ruby 1.9) ordonnés. Si vous regardez le code, les principales méthodes que vous utiliseriez pour obtenir la commande (telles que Set#eachet Set#to_a) sont déléguées à @hash. Et à partir de Ruby 1.9, des hachages sont commandés. "Les hachages énumèrent leurs valeurs dans l'ordre d'insertion des clés correspondantes." ruby-doc.org/core-1.9.1/Hash.html
phylae
Jamais nouveau, il n'y avait un ensemble. Ils sont géniaux, merci beaucoup
Brad
123

@Coorasse a une bonne réponse , même si elle devrait être:

my_array | [item]

Et pour mettre à jour my_arrayen place:

my_array |= [item]
Jason Denney
la source
63
ou my_array |= [item]qui sera mis à jour my_arrayen place
andorov
2
Il me manque peut-être quelque chose ici, mais l'opérateur | = ne semble pas fonctionner pour moi? J'utilise Ruby 2.1.1
Viet
@Viet |=fonctionne très bien dans mes tests avec 2.1.1. Veuillez décrire votre scénario de test ou ouvrir une nouvelle question.
depquid
Tester à nouveau et cela fonctionne maintenant. Je ne sais pas ce que je faisais plus tôt puisque mon commentaire a été fait il y a plusieurs mois.
Viet
1
Quelle est la complexité de cela?
Nobita
40

Vous n'avez pas besoin de parcourir à la my_arraymain.

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

Éditer:

Comme le souligne Tombart dans son commentaire, l'utilisation Array#include?n'est pas très efficace. Je dirais que l'impact sur les performances est négligeable pour les petits tableaux, mais vous voudrez peut-être opter Setpour les plus grands.

doesterr
la source
6
vous ne voulez certainement pas faire ça! array.include?(item)a de la complexité O(n)- c'est donc comme une itération de l'ensemble du tableau. jetez un œil à ce benchmark: gist.github.com/deric/4953652
Tombart
32

Vous pouvez convertir item1 en tableau et les joindre:

my_array | [item1]
coorasse
la source
1
Cela ne devrait |pas être ||(voir la réponse de Jason)
Seth
1
Ma faute. Désolé. Modifié la réponse
coorasse
3

Il est important de garder à l'esprit que la classe Set et le | La méthode (également appelée "Set Union") donnera un tableau d' éléments uniques , ce qui est génial si vous ne voulez pas de doublons, mais ce qui sera une mauvaise surprise si vous avez des éléments non uniques dans votre tableau d'origine par conception.

Si vous avez au moins un élément dupliqué dans votre tableau d'origine que vous ne voulez pas perdre, itérer dans le tableau avec un retour anticipé est le pire des cas O (n), ce qui n'est pas si mal dans le grand schéma des choses .

class Array
  def add_if_unique element
    return self if include? element
    push element
  end
end
elreimundo
la source
0

Je ne sais pas si c'est la solution parfaite, mais a fonctionné pour moi:

    host_group = Array.new if not host_group.kind_of?(Array)
    host_group.push(host)
witkacy26
la source