兩個 Ruby 建構 Array 和 Hash 的小技巧

Array

我想你可能寫過以下這樣的程式。其中 params[:a] 可以只有一個元素,也可以是陣列。但是為了接下來能夠處理,我們需要轉成陣列 array 變數:


array = (params[:a].is_a? Array)? params[:a] : [params[:a]]

這裡我們手動判斷了 params[:a] 是不是陣列,實在是有點 ugly。其實 Ruby 內建的 API 就可以支援下述寫法:


array = Array(params[:a])

無論 params[:a] 是陣列還是單一元素,Array(params[:a])會確保出來一定是陣列。

Hash

你有一個物件或是陣列,你想要轉成 Hash,最常見基本的作法會是先初始一個 hash,然後迭代設定它:


hash = {}
data.each { |d| hash[d.foo] =  d.bar }

高級一點的,也許會思考怎樣寫成一行,然後想到用 inject:


hash = data.inject({}) { |h,d| h[d.foo] =  d.bar; h }

不過,這裡我要介紹一種我的最愛:


hash = Hash[ data.map {|d| [d.foo, d.bar]} ]

Hash[]是一個Ruby內建的API可以把陣列轉成Hash,而且效能非常好,比前兩個方法都好。inject想當然是最慢的,我最不推薦使用。

有人跟我抱怨Hash[]有點 magic 可讀性不佳。可是啊,這是 Ruby “原生”的 Hash API,一點都不 magic。你不知道看不懂跟抱怨程式碼可讀性不佳,我個人認為是兩件事情哩。

參與討論

4 則留言

  1. 其實理論上 inject (fold) 會比較快,因為只有一次線性搜尋,
    而 Hash 因為有一次 map 又一次 Hash, 需要兩次線性搜尋,
    再加上有 intermediate 的 array 產生,成本應該是高不少。

    稍微跑了 benchmark, 在 MRI 中 inject (fold) 只稍微快一點點,
    我想原因就像你說的,由於 Hash 是由 C 實作的,所以有優勢在。
    不過在 Rubinius 中就差很多了,inject (fold) 快了一倍。
    有趣的是,JRuby 實在是比其他的快很多,而且真的是
    Hash+map 比較快,在 –server 下差得不少。

    以下單位是秒,在我的電腦上。
    MRI:
    inject(fold): 6.23
    Hash+map: 6.5
    Rubinius:
    inject(fold): 14.51
    Hash+map: 27.51
    JRuby: (不可思議地快)
    inject(fold): 2.47
    Hash+map: 2.29
    測資 *5 下的 JRuby:
    inject(fold): 16.38
    Hash+map: 16.6

    all code and result: gist.github.com/1065318

  2. hash = Hash[ data.map {|d| [d.foo, d.bar]} ]

    After I tried it, get the result that is not what I want
    我试了这个方法后,得到的怎么不是想要的结果;

    data = Trainer.all(:limit => 3)

    h2 = data.inject({}) { |h,d| h[d.id] = d.login; h}
    => {36097=>”jhill153″, 37632=>”rhenderson165″, 36100=>”snaazir156″}

    h3 = Hash[ data.map {|t| [t.id,t.login]} ]
    =>{[37632, “rhenderson165”]=>[37632, “rhenderson165”], [36097, “jhill153”]=>[36097, “jhill153”], [36100, “snaazir156”]=>[36100, “snaazir156”]}

    is it my fault?
    是我错了吗?

發佈留言

發表迴響