Hash
在其他語言稱為Object
, Dictionary
,但無論是在哪個程式語言中,Hash
和 Array
都為組成資料中兩個很重要的元素。許多資料結構為不單單只是陣列,也不單單只是Hash,甚至有很多複雜的Hash
搭配Array
的結構等等。
今天,我們會仔細的介紹Array
跟 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 }
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
轉為 Array
處理的話相對單純,Hash
只要to_a
就會轉為Array
Invoice.statuses.to_a
#=> [["unissued", 0], ["issued", 1], ["allowance", 2], ["allowance_failed", 3]]
我們也可以使用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
的時間魔術師。