Saubere Lösung für diesen rubin Iterator Verschlagenheit?
-
25-09-2019 - |
Frage
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]
Der gleiche Effekt tritt mit dem anderen Array Iterator, 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(",")
hat die gleiche Ausgabe.
Der Grund, warum dies geschieht ist ziemlich klar ... Rubin nicht wirklich Iterierte durch die Objekte in dem Array gespeichert, sondern dreht sich nur um es in einem hübschen Array-Index Iterator, beginnend bei Index 0 und jedes Mal Erhöhung der Index bis es vorbei ist. Aber wenn Sie ein Element löschen, erhöht er noch den Index, so dass es nicht zu bewerten den gleichen Index zweimal, was ich will es.
Das Macht nicht, was passiert ist, aber es ist das Beste, was ich denken kann.
Gibt es eine saubere Art und Weise, dies zu tun? Gibt es bereits einen eingebauten in Iterator, kann dies tun? Oder muss ich es zu schmutzig und ein Array-Index Iterator tun, und nicht erhöht werden, wenn das Element gelöscht wird? (Oder eine Iteration durch einen Klon des Arrays, und Löschen des ursprünglichen Arrays)
Klarstellung
Ich will nicht einfach Elemente aus einem Array löschen; sorry, wenn das war klar. Was ich will, würde zu tun ist, durchläuft jedes Element, und „Prozess“ es; Dieser Vorgang kann manchmal löschen. Um genauer zu sein:
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
Idealerweise würde ich alle Elemente in tracking_array will verarbeitet werden.
Wenn Living_Thing von tracking_array entfernt wird, # sterben Living_Thing muss genannt werden; do_stuff_to_die reinigt Dinge, die claned bis zu haben.
Lösung
Dies könnte mehr für die Verarbeitung geeignet sein (Ref. Aktualisiert Klärung)
k = [1,2,3,4,5]
k.dup.each do |n|
puts n
if n == 2
k.delete(n)
end
end
puts k.join(",")
es Seitenrammschutz die Frage, die Sie allerdings hatte (etwa Iteration durch Objekte vs Iteration durch Indizes)
Andere Tipps
Es ist ein Fehler in den meisten Sprachen eine Sammlung mutieren während Sie es laufen. In den meisten Sprachen ist die Lösung eine Kopie und mutieren, dass oder Aufbau einer Liste von Indizes zu erstellen und die Operationen auf diesen Indizes durchführen, wenn Sie fertig Iterieren sind, aber Rubys Iteratoren geben Sie ein bisschen mehr als das. Ein paar Lösungen liegen auf der Hand. Die meisten idiomatische IMO:
puts k
puts k.reject {|n| n == 2}.join(',')
Weitere direkt von Ihrem Beispiel übersetzt:
k.delete_if do |n|
puts n
n == 2
end
puts k.join(',')
(delete_if
ist im Grunde die zerstörerische Version von reject
, die eine Anordnung der Objekte zurückgibt, dass der Block nicht zurückkehrte gilt für.)
Okay, also lassen Sie uns sagen, Sie alle 2 des von Ihrem Array beseitigen wollen:
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"
Aber ich vermute, Sie Iterierte wollen, so würde ich:
arr = [1,2,3,4,5]
arr.each {|x| a[a.index(x)] = nil if x == 2}.compact!
Vielleicht ist das zu schmutzig? Die Zuordnung zu null hält den Iterator zählt richtig, und die compact!
tilgt den nils nach der Tat. Course map
hält es ein wenig kürzer und sauberer:
arr.map {|x| x if x != 2}.compact!