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]