2014-12-03 180 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是分離的基本構建模塊數組中的元素變成了某種分組,所以熟悉它們是很好的。