Aggiungi un elemento a un array se non è già presente


92

Ho una lezione su 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 = []

Voglio spingere MyClass#item1a unique_array_of_item1, ma solo se unique_array_of_item1non lo contiene item1ancora. C'è una soluzione semplice che conosco: basta scorrere my_arraye controllare se unique_array_of_item1contiene già la corrente item1o meno.

Esiste una soluzione più efficiente?

Risposte:


82

Puoi usare Set invece di Array.


Mentre è vero che i documenti dicono che i set non sono in ordine, in realtà sono ordinati (a partire da Ruby 1.9). Se guardi il codice, i metodi principali che useresti per ottenere l'ordine (come Set#eache Set#to_a) delegano a @hash. E a partire da Ruby vengono ordinati 1.9 hash. "Gli hash enumerano i propri valori nell'ordine in cui sono state inserite le chiavi corrispondenti." ruby-doc.org/core-1.9.1/Hash.html
phylae

Non c'era mai una novità come un set. Sono fantastici, grazie mille
Brad

123

@Coorasse ha una buona risposta , anche se dovrebbe essere:

my_array | [item]

E per aggiornare my_arraysul posto:

my_array |= [item]

63
o my_array |= [item]che verrà aggiornato my_arraysul posto
andorov

2
Forse mi manca qualcosa qui, ma l'operatore | = non sembra funzionare per me? Sto eseguendo Ruby 2.1.1
Viet

@Viet |=funziona perfettamente nei miei test con 2.1.1. Descrivi il tuo caso di test o apri una nuova domanda.
depquid

Testandolo di nuovo e ora funziona. Non so cosa stavo facendo prima da quando il mio commento è stato fatto molti mesi fa.
Viet

1
Qual è la complessità di questo?
Nobita

40

Non è necessario scorrere my_arraymanualmente.

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

Modificare:

Come sottolinea Tombart nel suo commento, l'utilizzo Array#include?non è molto efficiente. Direi che l'impatto sulle prestazioni è trascurabile per i piccoli array, ma potresti optare Setper quelli più grandi.


6
sicuramente non vuoi farlo! array.include?(item)ha complessità O(n), quindi è come iterare l'intero array. dai un'occhiata a questo benchmark: gist.github.com/deric/4953652
Tombart

32

Puoi convertire item1 in array e unirli:

my_array | [item1]

1
Questo dovrebbe essere |non ||(vedi risposta di Jason)
Seth

1
Colpa mia. Scusate. Risposta modificata
coorasse

3

È importante tenere presente che la classe Set e la classe | (chiamato anche "Set Union") produrrà una serie di elementi unici , il che è fantastico se non vuoi duplicati, ma che sarà una spiacevole sorpresa se hai elementi non univoci nella tua matrice originale in base alla progettazione.

Se hai almeno un elemento duplicato nell'array originale che non vuoi perdere, l'iterazione attraverso l'array con un ritorno anticipato è O (n) nel caso peggiore, che non è poi così male nel grande schema delle cose .

class Array
  def add_if_unique element
    return self if include? element
    push element
  end
end

0

Non sono sicuro che sia la soluzione perfetta, ma ha funzionato per me:

    host_group = Array.new if not host_group.kind_of?(Array)
    host_group.push(host)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.