2010-06-09 8 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] 

Este mismo efecto ocurre con el otro iterador array, k.each:¿Solución limpia a este truco de iterador de rubí?

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

tiene la misma salida.

La razón por la que esto está sucediendo es bastante clara ... Ruby en realidad no itera a través de los objetos almacenados en la matriz, sino que simplemente lo convierte en un iterador de índice de matriz bonito, comenzando en el índice 0 y aumentando cada vez índice hasta que se acabe. Pero cuando elimina un elemento, aún aumenta el índice, por lo que no evalúa el mismo índice dos veces, lo que yo quiero.

Esto podría no ser lo que está sucediendo, pero es lo mejor que se me ocurre.

¿Hay una manera limpia de hacer esto? ¿Ya hay un iterador incorporado que pueda hacer esto? ¿O tendré que ensuciarlo y hacer un iterador de índice de matriz, y no incrementarlo cuando se elimine? (O se puede recorrer un clon de la matriz, y eliminar de la matriz original)


Aclaración

Yo simplemente no quiero eliminar elementos de una matriz; lo siento si eso estuvo claro. Lo que me gustaría hacer es iterar a través de cada elemento y "procesarlo"; este proceso a veces puede eliminarlo. Para ser más precisos:

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, quisiera que todos los artículos en tracking_array fueran procesados.

Cuando se quita Living_Thing de tracking_array, se debe llamar a Living_Thing # die ; do_stuff_to_die limpia las cosas que tienen que clasificarse.

+0

es esto para una aplicación Rails o simplemente Ruby? – Anurag

+0

esto es para Ruby puramente –

Respuesta

4

esto podría ser más adecuado para el procesamiento (Ref. aclaración actualizado)

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

lado pasos la pregunta que tenía aunque (sobre la iteración a través de objetos vs iteración a través de índices)

+0

gracias; No había pensado usar k.dup con .each en una línea, en lugar de usar una variable de temperatura; esta es la solución que voy a usar por ahora ... aunque, sí, deja de lado la pregunta de eliminación de iteración real. –

3

bien, así que vamos a decir que desea eliminar todas las de 2 de la matriz:

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" 

pero sospecho que desea repetir, por lo que lo haría:

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

Tal vez es demasiado sucio? La asignación a nil mantiene el recuento de iteradores correcto, y el compact! borra los nils después del hecho. Por supuesto map mantiene un poco más corto y es más limpio:

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

Es un error en la mayoría de lenguajes de mutar una colección mientras se está iterando ella. En la mayoría de los lenguajes, la solución es crear una copia y mutar eso o crear una lista de índices y realizar las operaciones en esos índices cuando termine de iterar, pero los iteradores de Ruby le dan un poco más que eso. Un par de soluciones son obvias.El OMI más idiomática:

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

Más traducido directamente desde su ejemplo:

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

(delete_if es básicamente la versión destructiva de reject, que devuelve una matriz de los objetos que el bloque no volvió verdadera para.)

+0

nice, me gusta delete_if very clean solution –

+0

Usar delete_if para ejecutar un bloque de código completo para cada elemento es bastante elegante; Me gusta mucho =) Me aseguraré de mantenerlo en mi arsenal; sin embargo, para mi problema actual, no funciona =/ –

+0

O use 'reject!' que es equivalente a 'delete_if'. – Anurag

Cuestiones relacionadas