Ruby reverse.each_with_index 和 delete_at 在最近的 Ruby/Rails 中导致问题
Ruby reverse.each_with_index and delete_at causing problems in latest 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) 修复了此问题,但在数组为对象时不起作用。我还将对象复制到临时数组,使事情变得更复杂。
在我的现实生活中,我有两个数组,其中填充了不同类型的模型对象,但共享一个共同的属性(这里可能使用连接,但我试图避免特殊连接 table ).我想要的是删除 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
如果没有上面的额外复制,模型关联将保留,我最终将从 table 中删除记录,这是不应该发生的。这只是一个临时列表。这似乎是一个愚蠢的问题,我在这上面花了太多时间。
您正在使用反向索引删除,但从原始数组中删除。
要获得 "real" 索引,而不是从数组末尾开始计数,您需要翻转它:
arr1.delete_at(-index - 1)
...但您几乎肯定应该使用 reject!
or delete_if
来代替:
require "set"
unwanted_colors = @arr2.map(&:color).to_set
@arr1.reject! { |el| unwanted_colors.include?(el.color) }
您的问题有多种解决方案, 显示了一个干净的示例。但是,以下两种解决方案也可以解决您的问题。
首先我想让你知道你可以减少前几行代码:
@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]
所以,我很清楚在迭代块中删除项目的危险(这是反向循环),我知道 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) 修复了此问题,但在数组为对象时不起作用。我还将对象复制到临时数组,使事情变得更复杂。
在我的现实生活中,我有两个数组,其中填充了不同类型的模型对象,但共享一个共同的属性(这里可能使用连接,但我试图避免特殊连接 table ).我想要的是删除 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
如果没有上面的额外复制,模型关联将保留,我最终将从 table 中删除记录,这是不应该发生的。这只是一个临时列表。这似乎是一个愚蠢的问题,我在这上面花了太多时间。
您正在使用反向索引删除,但从原始数组中删除。
要获得 "real" 索引,而不是从数组末尾开始计数,您需要翻转它:
arr1.delete_at(-index - 1)
...但您几乎肯定应该使用 reject!
or delete_if
来代替:
require "set"
unwanted_colors = @arr2.map(&:color).to_set
@arr1.reject! { |el| unwanted_colors.include?(el.color) }
您的问题有多种解决方案,
首先我想让你知道你可以减少前几行代码:
@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]