Solução limpa para esse enigma do iterador de rubi?
-
25-09-2019 - |
Pergunta
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]
Esse mesmo efeito acontece com o outro iterador de matriz, K.Each:
k = [1,2,3,4,5]
k.each do |n|
puts n
if n == 2
k.delete(n)
end
end
puts k.join(",")
tem a mesma saída.
A razão pela qual isso está acontecendo é bem claro ... Ruby não itera através dos objetos armazenados na matriz, mas apenas transforma -o em um iteador de índice de matriz bonito, começando no índice 0 e cada vez que aumenta o índice até que termine . Mas quando você exclui um item, ele ainda aumenta o índice, para que não avalie o mesmo índice duas vezes, o que eu quero.
este poderia Não seja o que está acontecendo, mas é o melhor que consigo pensar.
Existe uma maneira limpa de fazer isso? Já existe um iterador embutido que pode fazer isso? Ou terei que sujar e fazer um iterador de índice de matriz, e não incrementar quando o item é excluído? (ou itera através de um clone da matriz e exclua da matriz original)
Esclarecimento
Eu simplesmente não quero excluir itens de uma matriz; Desculpe se isso estava claro. O que eu gostaria de fazer é iterar em cada elemento e "processar"; Esse processo às vezes pode excluí -lo. Para ser mais preciso:
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, eu gostaria que todos os itens em rastreamento_array fossem processados.
Quando Living_thing é removido de rastrear_array, Living_thing#Die devo ser chamado; do_stuff_to_die limpa as coisas que precisam ser clanadas.
Solução
Isso pode ser mais adequado para o processamento (ref. esclarecimento atualizado)
k = [1,2,3,4,5]
k.dup.each do |n|
puts n
if n == 2
k.delete(n)
end
end
puts k.join(",")
Side etapa a pergunta que você teve (sobre iteração através de objetos versus iteração através de índices)
Outras dicas
É um erro na maioria dos idiomas para mudar uma coleção enquanto você itera. Na maioria dos idiomas, a solução é criar uma cópia e mudar isso ou criar uma lista de índices e executar as operações nesses índices quando você está fazendo iteração, mas os iteradores de Ruby oferecem um pouco mais do que isso. Algumas soluções são óbvias. A IMO mais idiomática:
puts k
puts k.reject {|n| n == 2}.join(',')
Mais diretamente traduzido do seu exemplo:
k.delete_if do |n|
puts n
n == 2
end
puts k.join(',')
(delete_if
é basicamente a versão destrutiva de reject
, que retorna uma matriz dos objetos para os quais o bloco não retornou verdadeiro.)
Ok, então digamos que você queira eliminar todos os 2 da sua 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"
Mas eu suspeito que você queira iterar, então eu faria:
arr = [1,2,3,4,5]
arr.each {|x| a[a.index(x)] = nil if x == 2}.compact!
Talvez isso esteja muito sujo? A tarefa a nil mantém a contagem de iteradores corretamente, e o compact!
Limpe os nilos após o fato. Curso map
Mantém um pouco mais curto e é mais limpo:
arr.map {|x| x if x != 2}.compact!