Ruby reverse.each_with_index和delete_at导致最新的Ruby / Rails出现问题
所以,我很清楚在迭代块中移除项目的危险(这是反向循环),我知道Matz提到了一些关于迭代中的突变导致稳定性问题,但我似乎无法想象这一点。
这个例子有点令人费解,我不确定即使解决它也会完全复制这个例子,但我必须尝试。
arr1 = [1, 2, 3, 4, 5] arr2 = [3, 4, 5] puts arr1.inspect puts arr2.inspect arr2.each do |i| arr1.reverse.each_with_index do |j, index| if i == j arr1.delete_at(index) end end end puts arr1.inspect puts arr2.inspect
输出:
[1, 2, 3, 4, 5] [3, 4, 5] [4, 5] [3, 4, 5]
应该是什么时候:
[1, 2, 3, 4, 5] [3, 4, 5] [1, 2] [3, 4, 5]
将delete_at(index)更改为delete(j)修复此问题,但在数组是对象时不起作用。 我也将对象复制到临时数组以使事情变得更复杂。
在我的现实生活场景中,我有两个数组填充了不同类型的Model对象但共享一个公共属性(可能在这里使用连接,但我试图避免使用特殊的连接表)。 我想要的是删除array1中具有array2中的公共属性的任何对象。 我已经尝试了许多不同的东西,没有解决方案……太多了,无法放在这里。
@arr1 = [] original_arr1 = Model1.where(...) original_arr1.each { |original| @arr1 << original.dup } @arr2 = Model2.where(...) @arr2.each do |object1| @arr1.reverse.each_with_index do |object2, index| if object1.color == object2.color @arr1.delete_at(version_index) end end end
如果没有上面的额外复制,模型关联将保留,我将最终删除表中的记录,这不应该发生。 这只是一个临时名单。 这似乎是一个愚蠢的问题,我花了太多时间在它上面。
您正在使用反向索引删除,但是从原始数组中删除。
要获得“真实”索引,而不是从数组末尾开始计算,您需要翻转它:
arr1.delete_at(-index - 1)
…但你几乎肯定会使用reject!
或者delete_if
代替:
require "set" unwanted_colors = @arr2.map(&:color).to_set @arr1.reject! { |el| unwanted_colors.include?(el.color) }
您的问题有多种解决方案, @matthewds答案显示了一个干净的例子。 但是,以下两种解决方案也可以解决您的问题
首先,我想告诉您,您可以减少前几行代码:
@arr1 = [] original_arr1 = Model1.where(...) original_arr1.each { |original| @arr1 << original.dup } # to @arr1 = Model1.where(...).map(&:dup) # but since you're not saving the Model1.where(...) result in a variable # (enabling one to use them later), there is not need to dup at all @arr1 = Model1.where(...)
问题
你得到的实际结果是正确的。 原因如下:
a1 = [1, 2] a2 = [2] a2.each { |n2| a1.reverse.each_with_index { |n1, i| a1.delete_at(i) if n2 == n1 } } # a1 = [1, 2] # a2 = [2] # iterate over a2 # n2 = 2 # create an new array with the reversed elements of a1 # ra1 = a1.reverse (eq [2, 1] and a1 is still [1, 2]) # iterate over ra1 with index # n1 = 2, i = 0 # does n2 (2) equals n1 (2)? yes # delete in a1 ([1, 2]) at the index i (0) # resulting in a1 = [2] # next iteration ra1 # n1 = 1, i = 1 # does n2 (2) equals n1 (1)? no # ra1 iteration finishes # a2 iteration finishes # resulting in a1 = [2]
#1保持当前的代码结构
如果您想为当前的代码结构提供最简单的解决方案,那么只需删除#reverse
调用即可。 似乎无论如何都不需要反转数组,因为你没有保存结果,或者在#each_with_index
代码块中使用。
#2仅获取所需的记录
第二个解决方案解决了数据库查询级别的问题。 如果您不希望Model1
的记录与当前集中的颜色具有相同的颜色,则不要从数据库中获取它们。
@arr2 = Model2.where(...) @arr1 = Model1.where(...).where.not(color: @arr2.pluck(:color))
如果color不是属性,但关联实例使用:color_id
。
注意 :您可以使用.select(:color)
代替.pluck(:color)
。 使用select方法将产生子查询,但是由于您可能要使用@arr2
因此无论如何都需要加载完整的记录。 从@arr2
提取值并将它们作为纯色而不是子查询提供,可以为数据库节省一些工作量。 如果您不打算再使用@arr2
我会使用select变体。
我没有用复杂的数据结构测试它,但也许这可能是另一种方式。
a = [1, 2, 3, 4, 5] b = [3, 4, 5] p a+b-(a&b) p a&b # [1, 2] # [3, 4, 5]
在以下情况下也适用:
a = [3, 4, 5] b = [1, 2, 3, 4, 5]