Чистое решение этой хитрости итератора рубина? - PullRequest
10 голосов
/ 09 июня 2010
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 убирает вещи, которые должны быть найдены.

Ответы [ 3 ]

15 голосов
/ 09 июня 2010

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

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, которая возвращает массив объектов, для которых блок не вернул true.)

4 голосов
/ 09 июня 2010

это может быть более подходящим для обработки (см. Уточненное пояснение)

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

это побочный шаг к вопросу, который у вас был (об итерации по объектам против итерации по индексам)

3 голосов
/ 09 июня 2010

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

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"

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

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