2014-12-03 135 views
0

我有一个问题,我似乎无法找到记录或解释任何地方,所以我希望有人在这里可以帮助我。我已经验证了三个Ruby版本的意外行为,所有2.1+,都证实它不会发生在早期版本上(尽管它是通过tryruby.org进行的,我不知道它们使用的是哪个版本)。无论如何,对于这个问题,我只会发布一些结果的代码,希望有人可以帮助我调试它。奇怪的Ruby 2+行为与“选择!”

arr = %w(r a c e c a r)    #=> ["r","a","c","e","c","a","r"] 
arr.select { |c| arr.count(c).odd? } #=> ["e"] 
arr.select! { |c| arr.count(c).odd? } #=> ["e","r"] <<<<<<<<<<<<<<< ?????? 

我觉得对我来说是混乱的部分是清晰的标志,如果任何人都可以解释,如果这是一个错误,或者如果有一些逻辑的话,我会非常感激。谢谢!

+3

检查被突变的同一对象上的东西('Array#select!'是一个增变器)总是会导致奇怪的行为。 – avlazarov 2014-12-03 18:39:20

回答

6

你修改而你从它你迭代它读取阵列。我不确定结果是否定义了行为。该算法不需要在运行时保持对象处于任何类型的理智状态。

迭代过程中的一些调试打印说明为什么你的特定结果的发生:

irb(main):005:0> x 
=> ["r", "a", "c", "e", "c", "a", "r"] 
irb(main):006:0> x.select! { |c| p x; x.count(c).odd? } 
["r", "a", "c", "e", "c", "a", "r"] 
["r", "a", "c", "e", "c", "a", "r"] 
["r", "a", "c", "e", "c", "a", "r"] 
["r", "a", "c", "e", "c", "a", "r"] # "e" is kept... 
["e", "a", "c", "e", "c", "a", "r"] # ... and moved to the start of the array 
["e", "a", "c", "e", "c", "a", "r"] 
["e", "a", "c", "e", "c", "a", "r"] # now "r" is kept 
=> ["e", "r"] 

您可以通过最后的迭代看到,有只有一个r,那e已经移到前面的阵列。推测该算法就地修改阵列,将匹配元素移到前面,覆盖已经通过测试的元素。它跟踪有多少元素被匹配和移动,然后将数组截断为多个元素。因此,使用select


匹配多个元素更长的例子使这个问题更清楚一点:

irb(main):001:0> nums = (1..10).to_a 
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
irb(main):002:0> nums.select! { |i| p nums; i.even? } 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
[2, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
[2, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
[2, 4, 3, 4, 5, 6, 7, 8, 9, 10] 
[2, 4, 3, 4, 5, 6, 7, 8, 9, 10] 
[2, 4, 6, 4, 5, 6, 7, 8, 9, 10] 
[2, 4, 6, 4, 5, 6, 7, 8, 9, 10] 
[2, 4, 6, 8, 5, 6, 7, 8, 9, 10] 
[2, 4, 6, 8, 5, 6, 7, 8, 9, 10] 
=> [2, 4, 6, 8, 10] 

你可以看到,它确实动匹配的元素添加到数组的前面,覆盖不匹配元素,然后截断数组。

+0

优秀的答案+1我只是写了一个类似的事情与上面的评论相结合。 – engineersmnky 2014-12-03 18:40:15

+1

我不敢相信我在每一步都没有想到'p'这个数组,并且自己弄清楚了这一点,但这是完全合理的,尽管我不知道这是Ruby开发者的意图。我在tryruby.org上运行你的代码,并且在每一步中,'x'只是原始数组,没有任何替代。我只能推测,新版本的Ruby的就地修改是为了提高速度而实现的,但我觉得旧的行为更期待并应该保留下来。无论如何,感谢您考虑在每一步检查数组并为我找出问题! – 2014-12-03 18:47:41

+0

我个人更喜欢旧的行为,所以我重写了使用它的爆炸方法,尽管新的行为从空间复杂性的角度来看是有道理的。这感觉就像一个反模式(就Ruby而言)必须编写“arr = arr.select等”。 – 2014-12-03 18:55:19

0

只给你完成你正在做的事情的其他一些方法:

arr = %w(r a c e c a r) 
arr.group_by{ |c| arr.count(c).odd? }   
# => {false=>["r", "a", "c", "c", "a", "r"], true=>["e"]} 
arr.group_by{ |c| arr.count(c).odd? }.values 
# => [["r", "a", "c", "c", "a", "r"], ["e"]] 
arr.partition{ |c| arr.count(c).odd? }  
# => [["e"], ["r", "a", "c", "c", "a", "r"]] 

如果你想更易读键:

arr.group_by{ |c| arr.count(c).odd? ? :odd : :even } 
# => {:even=>["r", "a", "c", "c", "a", "r"], :odd=>["e"]} 

partitiongroup_by是分离的基本构建模块数组中的元素变成了某种分组,所以熟悉它们是很好的。