iT邦幫忙

2021 iThome 鐵人賽

DAY 6
0
Modern Web

初階 Rails 工程師的養成系列 第 6

Day6. Array & Hash 之間的組合應用

  • 分享至 

  • xImage
  •  

Hash 在其他語言稱為Object, Dictionary,但無論是在哪個程式語言中,HashArray 都為組成資料中兩個很重要的元素。許多資料結構為不單單只是陣列,也不單單只是Hash,甚至有很多複雜的Hash搭配Array的結構等等。

今天,我們會仔細的介紹ArrayHash 之間的關係

Array to Hash

論陣列轉Hash,最簡單的方式就是先把資料型態為湊為倆倆一對,之後再to_h 便很容易

[%w(a b), %w(c d), %w(e f)].to_h   #=> {"a"=>"b", "c"=>"d", "e"=>"f"}
%w(a b c d e f).to_h               #=> 會壞掉

將上述的觀念建立起來以後,我們來開始看其他轉 Hash 的情境

⭐️ 自我配對

雖然我們不能寫成 %w(item1 item2 item3 item4).to_h,但我們可以使用下列的方式轉為hash

a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] 
#=> { "item 1" => "item 2", "item 3" => "item 4" }

⭐️ 將陣列使用#zip作配對以後,轉為雜湊

不僅想要將兩個陣列湊成Hash,還希望當中的key 為符號的話,我們該怎麼做?以下提供三種轉Hash的方法。

keys = %w[a b c d e]
values = %w(apple bear cat dog elephant)

Hash[keys.map(&:to_sym).zip(values)]
#=> {:a=>"apple", :b=>"bear", :c=>"cat", :d=>"dog", :e=>"elephant"}

# Ruby 2.1.0 以上可以用
keys.zip(values).to_h
keys.map(&:to_sym).zip(values).to_h

# Ruby 2.5 以上可以用 transform_keys
Hash[keys.zip(values)].transform_keys { |k| k.to_sym }

⭐️ 搭配 #inject #reduce 使用

Day4 講過,當我們要轉換為比較複雜的資料型態如hash, array的話,比起使用inject, reduce,使用each_with_object會比較簡潔。

下列提供inject, each_with_object兩種方法比對,雖然Day4已經提過,但還是要強調兩者之間的accumulator位置不一樣。inject在前面,而each_with_object 在後面

#======= 情境1: 使用inject
[:foo, :bar, :jazz].inject({}) do |hash, item|
  hash[item] = item.to_s.upcase
  hash
end
#=> {:foo=>"FOO", :bar=>"BAR", :jazz=>"JAZZ"}

#======= 情境1: 使用each_with_object
[:foo, :bar, :jazz].each_with_object({}) { |item, hash| hash[item] = item.to_s.upcase }
#=> {:foo=>"FOO", :bar=>"BAR", :jazz=>"JAZZ"}
#======= 情境2: 使用inject
array = [['A', 'a'], ['B', 'b'], ['C', 'c']]
array.inject({}) do |memo, values|
  memo[values.first] = values.last
  memo
end
# => {'A' => 'a', 'B' => 'b', 'C' => 'c'}

#======= 情境2: 使用each_with_object
array = [['A', 'a'], ['B', 'b'], ['C', 'c']]
array.each_with_object({}) do |values, memo|
  memo[values.first] = values.last
end
# => {'A' => 'a', 'B' => 'b', 'C' => 'c'}

Hash to Array

Hash 轉為 Array 處理的話相對單純,Hash只要to_a 就會轉為Array

Invoice.statuses.to_a
#=> [["unissued", 0], ["issued", 1], ["allowance", 2], ["allowance_failed", 3]]

#each_with_object

我們也可以使用each_with_object 將原本為Hash的資料轉為型態更複雜的Hash

#======= 情境1
t = {0=> "a", 1=> "b", 5=> "c"}
t.each_with_object([]) {|(k,v), a| a[k]=v}
#=> ["a", "b", nil, nil, nil, "c"]


#======= 情境2: 轉為array of object
{ foo: 1, bar: 2, jazz: 3 }.each_with_object([]) do |(key, value), array|
  array << { id: value, name: key }
end
# => [{:id=>1, :name=>:foo}, {:id=>2, :name=>:bar}, {:id=>3, :name=>:jazz}]

⭐️ each_with_object 裡頭只能放陣列與雜湊等可以擴充的容器,若把each_with_object當作inject使用的話,則會出現淒慘情況。

 (1..5).each_with_object(1) { |value, accu| value + accu }  #=> 1 (不是15)

⭐️ 使用rails enum helper,搭配下拉式選單,我們可以用以下方式將Hash 轉為陣列

class Order < ApplicationRecord
  # 情境1. module in class
  module Status
    UNPAID      = :unpaid       # 未付款
    PROCESSING  = :processing   # 處理中
    WAITING     = :waiting      # 已出貨
    DONE        = :done         # 已完成
    CANCELED    = :canceled     # 已取消
    RETURNED    = :returned     # 退貨/退款
  end

  # 情境2. enum
  enum shipping_type: {
    home: 0,  # 宅配
    cvs: 1    # OK/萊爾富/全家
  }
end

通常要使用enum helper轉陣列的情境,都是在要將enum轉換為前端下拉式選單的資料結構用。

module Admin::OrdersHelper
  #=> [["所有訂單狀態", nil], ["退貨/退款", :returned], ["未付款", :unpaid], 
  #    ["處理中", :processing], ["已完成", :done], ["已出貨", :waiting], ["已取消", :canceled]]
  def order_status
    Order::Status.constants
                 .map { |c| Order::Status.const_get(c) }
                 .map { |k| [t("orders.status.#{k}"), k] }
                 .prepend ['所有訂單狀態', nil]
  end
  
  #=> [['所有配送方式', nil], ["宅配", "home"], ["超商取貨", "cvs"]]
  def order_shipping_type(prepend: true)
    options = Order.shipping_types.map { |k, v| [t("orders.shipping_type.#{k}"), k] }
    # radio-button
    return options unless prepend
    # 下拉式選單
    options.prepend ['所有配送方式', nil]
  end
end

結論

今天介紹了Array, Hash之間轉換的關係,明天讓大家瞧瞧Ruby的時間魔術師。

參考資料


上一篇
Day5. 活用Hash,掌握資料處理的訣竅
下一篇
Day7. 活用Ruby的Time,人人都可以成為時間魔術師
系列文
初階 Rails 工程師的養成34
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言