Чистое решение этой рубиновой ловкости итератора?

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]

Этот тот же эффект происходит с другим итератором массива, 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(",")

имеет тот же вывод.

Причина это происходит, довольно ясна... Ruby на самом деле не выполняет итерации через объекты, хранившие в массиве, а скорее просто превращает его в итератор довольно индекса массива, запускающийся в индексе 0 и каждый раз увеличивая индекс, пока это не закончено. Но когда Вы удаляете объект, он все еще увеличивает индекс, таким образом, он не оценивает тот же индекс дважды, к которому я хочу его.

Это не могло бы быть тем, что происходит, но лучше, чтобы я мог думать.

Существует ли очевидный способ, чтобы сделать это? Уже существует ли встроенный итератор, который может сделать это? Или я должен буду запачкать его и сделать итератор индекса массива и не увеличить, когда объект удален? (или выполните итерации через клон массива и удалите из исходного массива),


Разъяснение

Я просто не хочу удалять объекты из массива; извините, если это было ясно. То, что я хотел бы сделать, выполняют итерации через каждый элемент и "обрабатывают" его; этот процесс мог бы иногда удалять его. Быть более точным:

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

Идеально, я хотел бы, чтобы все объекты в tracking_array были обработаны.

Когда Living_Thing удален из tracking_array, Living_Thing#die нужно назвать; do_stuff_to_die очищает вещи, которые должны быть claned.

10
задан Justin L. 9 June 2010 в 04:22
поделиться

3 ответа

это может быть более подходящим для обработки (Ref. updated clarification)

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

это обходит вопрос, который вы задали (об итерации через объекты против итерации через индексы)

4
ответ дан 3 December 2019 в 17:57
поделиться

В большинстве языков ошибка при изменении коллекции при ее повторении. В большинстве языков решение состоит в том, чтобы создать копию и изменить ее или создать список индексов и выполнить операции с этими индексами, когда вы закончите итерацию, но итераторы Ruby дают вам немного больше, чем это. Пара решений очевидна. Самый идиоматичный IMO:

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

Более прямо переведено из вашего примера:

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

( delete_if - это в основном деструктивная версия reject , которая возвращает массив объектов, которые блокируются не вернул истину для.)

15
ответ дан 3 December 2019 в 17:57
поделиться

Хорошо, допустим, вы хотите удалить все двойки из своего массива:

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"

Но я подозреваю, что вы хотите повторить итерацию, поэтому я бы:

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

Может, это слишком грязно? Присваивание nil сохраняет количество итераторов правильным, а компактно! стирает нули постфактум. Карта курса делает его немного короче и чище:

arr.map {|x| x if x != 2}.compact!
3
ответ дан 3 December 2019 в 17:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: