2015-08-27 2 views
3

У меня есть массив под названием @results, который состоит только из массивов. Я хочу, чтобы перебирать @results и навсегда удалить любой из внутренних массивов, размер которых меньше заданного размера:Каждый цикл работает не так, как ожидалось

Мой код:

def check_results limit 
    @results.each_with_index do |result, index| 
     @results.delete_at(index) if result.size < limit 
    end 
    end 

К сожалению, это только удаляет первый элемент, где длина массива меньше чем limit. Например, если limit = 4 и @results = [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1], [1, 1]], то check_results возвращает [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1]]

Я не могу понять, почему это происходит. Я использую неправильный цикл?

+0

Hello. Я отредактировал свой ответ, чтобы включить _why_, который вы испытываете. Я включил отладочные заявления. Пожалуйста, взгляните на это. Я также предлагаю альтернативы/решения вашей проблемы :-) – onebree

ответ

4

Вы должны сделать это, так как delete_at изменяет массив, и вы получите неожиданное поведение, если вы удаляете элементы в то время как итерация его

@results.reject { |i| i.size < limit } 

выше код будет исключить все элементы массива, размер которых меньше чем limit

+0

Я даже не думал о «отвержении»! Мне нравится, что она использует ее, поэтому я приму свой ответ, как только SO позволит мне! Спасибо! – SoSimple

3

Не рекомендуется изменять массив @results, поскольку это будет противоречить внешней итерации.

Вместо этого вы должны использовать select для построения нового массива.

def check_results(limit) 
    @result.select { |result| result.size > limit } 
end 
+0

Разве это не 'result.size> = limit' на основе описания проблемы OP? –

+0

Да, это прекрасно работает. Я просто использовал '==' вместо '>' :) Спасибо! – SoSimple

2

Согласно документации, #delete_at возвращает элемент по этому индексу.

a = ["ant", "bat", "cat", "dog"] 
a.delete_at(2) #=> "cat" 
a     #=> ["ant", "bat", "dog"] 
a.delete_at(99) #=> nil 

Я добавил некоторые отладочные, чтобы показать вам, что происходит на каждом шаге, предполагая, что предел составляет 4:

@results = [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1], [1, 1]] 
@results.each_with_index do |r, i| 
    puts "RESULT: #{r.to_s}" 
    puts "INDEX: #{i}" 
    @results.delete_at(i) if r.size < 4 
    puts "ARRAY: #{@results.to_s}" 
end 

RESULT: [1, 1, 1, 1] 
INDEX: 0 
ARRAY: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1], [1, 1]] 
RESULT: [1, 1, 1, 1] 
INDEX: 1 
ARRAY: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1], [1, 1]] 
RESULT: [1, 1, 1] 
INDEX: 2 
ARRAY: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1]] 
# @results == [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1]] 

Как вы можете видеть, элемент первоначально с индексом 2 был удален. Поскольку вы изменяете @results во время итерации через него, индекс 3 больше не существует, и индекс 2 уже проанализирован. Вот почему вы не должны изменять объект во время итерации через него.

В идеале вы хотите использовать #delete_if. Подобно методам, заканчивающимся на !, #delete_if будет изменять массив (не возвращать копию результата) на основе условий из блока (в качестве аргумента). Ниже будет как бы реализовать метод:

def check_results(limit) 
    @results.delete_if { |arr| arr.length < limit } 
end 

@results = [ ['foo', 'bar'], ['bizz', 'bazz'], ['kaboom'] ] 

check_results(2) 
# => @results == [ ['foo', 'bar'], ['bizz', 'bazz'] ] 

Если вы не хотите, чтобы изменить @results, то я предлагаю подобный метод, #reject. Опять же, @results не будет изменен, и вместо этого будет возвращена копия результатов.

def check_results(limit) 
    @results.reject { |arr| arr.length < limit } 
end 

@results = [ ['foo', 'bar'], ['bizz', 'bazz'], ['kaboom'] ] 

check_results(2) 
# => [ ['foo', 'bar'], ['bizz', 'bazz'] ] 
# => @results == [ ['foo', 'bar'], ['bizz', 'bazz'], ['kaboom'] ] 
Смежные вопросы