我有一个名为@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 :(得分:4)
你应该这样做,因为delete_at
修改了数组,如果在迭代时删除元素,你会得到意想不到的行为
@results.reject { |i| i.size < limit }
上面的代码将排除所有大小小于limit
答案 1 :(得分:3)
修改@results
数组并不是一个好主意,因为这会与外部迭代冲突。
你应该做的是使用select来构建一个新数组。
def check_results(limit)
@result.select { |result| result.size > limit }
end
答案 2 :(得分: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'] ]