2010-06-09 6 views
10
k = [1,2,3,4,5] 
for n in k 
    puts n 
    if n == 2 
    k.delete(n) 
    end 
end 
puts k.join(",") 

# Result: 
# 1 
# 2 
# 4 
# 5 
# [1,3,4,5] 

# Desired: 
# 1 
# 2 
# 3 
# 4 
# 5 
# [1,3,4,5] 

Questo stesso effetto accade con l'altro iteratore matrice, k.each:Soluzione pulita per questo trucco furioso iteratore?

k = [1,2,3,4,5] 
k.each do |n| 
    puts n 
    if n == 2 
    k.delete(n) 
    end 
end 
puts k.join(",") 

ha la stessa uscita.

Il motivo per cui questo accade è abbastanza chiaro ... Ruby non esegue iterazione degli oggetti archiviati nell'array, ma piuttosto lo trasforma in un iteratore di indice piuttosto array, a partire dall'indice 0 e aumentando ogni volta il valore. indice fino a quando non è finita. Ma quando elimini un oggetto, esso incrementa comunque l'indice, quindi non valuta lo stesso indice due volte, il che lo voglio.

Questo potrebbe essere non essere quello che sta succedendo, ma è il meglio che riesco a pensare.

C'è un modo pulito per farlo? C'è già un iteratore integrato che può farlo? O dovrò sporcarlo e fare un iteratore di indice di array, e non incrementare quando l'elemento viene cancellato? (O di scorrere un clone della matrice, ed eliminare dalla matrice originale)


Chiarimento

non mi vogliono semplicemente eliminare gli elementi da un array; scusa se era chiaro. Quello che vorrei fare è iterare attraverso ogni elemento e "processarlo"; questo processo potrebbe talvolta cancellarlo. Per essere più precisi:

class Living_Thing 

    def initialize tracker,id 
    @tracker = tracker 
    @id = id 

    @tracker << self 
    end 

    def process 
    do_stuff 
    puts @id 
    if @id == 2 
     die 
    end 
    end 

    def die 
    do_stuff_to_die 
    @tracker.delete(self) 
    end 

    def inspect 
    @id 
    end 
end 

tracking_array = Array.new() 

foo = Living_Thing.new(tracking_array,1) 
bar = Living_Thing.new(tracking_array,2) 
rab = Living_Thing.new(tracking_array,3) 
oof = Living_Thing.new(tracking_array,4) 

puts tracking_array.join(",")    # => [1, 2, 3, 4] 

for n in tracking_array 
    n.process 
end 

# result: only foo, bar, and oof are processed 

Idealmente, vorrei tutti gli elementi in tracking_array da lavorare.

Quando Living_Thing viene rimosso da tracking_array, Living_Thing # die deve essere chiamato; do_stuff_to_die pulisce le cose che devono essere raggruppate.

+0

è questo per un'applicazione Rails o solo Ruby? – Anurag

+0

questo è per Ruby puramente –

risposta

4

questo potrebbe essere più adatto per la lavorazione (Rif. chiarimento aggiornato)

k = [1,2,3,4,5] 
k.dup.each do |n| 
    puts n 
    if n == 2 
    k.delete(n) 
    end 
end 
puts k.join(",") 

it side fa la domanda che hai avuto (riguardo l'iterazione attraverso oggetti vs iterazione attraverso indici)

+0

grazie; non avevo pensato di usare k.dup con .each in una riga, invece di usare una variabile temporanea; questa è la soluzione che userò per ora ... anche se, sì, fa il passo alla domanda di eliminazione dell'iterazione effettiva. –

3

Okay, diciamo che si vuole eliminare tutti i 2 S da l'array:

arr = [1,2,3,4,5] 
arr.delete(2) 
puts arr.join(", ") 
# => "1, 3, 4, 5" 
arr = [1,2,3,2,4,2,5,2] 
arr.delete(2) 
puts arr.join(", ") 
# => "1, 3, 4, 5" 

ma ho il sospetto che si desidera iterare, quindi vorrei:

arr = [1,2,3,4,5] 
arr.each {|x| a[a.index(x)] = nil if x == 2}.compact! 

Forse è troppo sporca? L'assegnazione a zero mantiene il conteggio degli iteratori corretto e lo compact! cancella i nils dopo il fatto. Corso map mantiene un po 'più corto ed è più pulita:

arr.map {|x| x if x != 2}.compact! 
15

Si tratta di un errore nella maggior parte delle lingue di mutare una raccolta, mentre si sta iterazione esso. Nella maggior parte delle lingue la soluzione è creare una copia e mutarla o creare un elenco di indici ed eseguire le operazioni su quegli indici quando hai terminato l'iterazione, ma gli iteratori di Ruby ti danno un po 'di più. Un paio di soluzioni sono ovvie.Il più idiomatica IMO:

puts k 
puts k.reject {|n| n == 2}.join(',') 

Altro tradotto direttamente dal vostro esempio:

k.delete_if do |n| 
    puts n 
    n == 2 
end 
puts k.join(',') 

(delete_if è sostanzialmente la versione distruttiva di reject, che restituisce un array di oggetti che il blocco non ha restituito vero per.)

+0

bello, mi piace delete_if soluzione molto pulita –

+0

Utilizzando delete_if per eseguire un blocco di codice completo per ogni elemento è piuttosto elegante; Mi piace molto =) Sarò sicuro di tenerlo nel mio arsenale; tuttavia, per il mio problema attuale, non funziona affatto =/ –

+0

Oppure usa 'reject!' che è equivalente a 'delete_if'. – Anurag