Ruby中Hash的7个日常使用范例

March 2014 · 2 minute read

此文翻译自7 daily use cases of Ruby Hash,限于本人水平,翻译不当之处,敬请指教!

每一天,你都需要跟Hash相处。创建一个新的Hash或者是通过它的某一个键去检索其中的元素这样的工作,都是常见也是非常简单的。但是当你需要合并两个嵌套的Hash或者是从某一个Hash里边过滤某些键,你可能需要考虑得多一点。通过完整的文档,你可以找到对Hash中的每一个方法的充分解释。但是由于文档不是面向应用场景的,你可能没法很快找到你的解决方案。在下面,我分享了我日常中经常遇到的Hash中的7个常用场景,希望它们对你有用。

1. 如何将一个JSON转换为一个Hash?

假设你刚刚接收到一个用JSON表示的Twitter账号的资料信息:

data = '{
  "name": "Aaron Patterson",
  "screen_name": "tenderlove",
  "location": "Seattle, WA"
}'

你希望能够将它转化为一个Hash,这样会更方便你进行对数据的操作:

require 'json'

profile = JSON.parse(data)

** 在IRB中的输出结果:**

=> {
  "name"=>"Aaron Patterson",
  "screen_name"=>"tenderlove",
  "location"=>"Seattle, WA"
}

查看文档:JSON#parse

2. 如何将一个Hash转换为一个JSON?

在你的web应用程序中,你需要追踪当前星期每一天新注册用户的数量:

signups_of_the_week = {
    monday: 2,
    tuesday: 3,
    wednesday: 4,
    thursday: 20,
    friday: 5,
    saturday: 2,
    sunday: 5
}

你可以通过API的方式把它们以JSON格式提供给客户端:

require 'json'

signups_of_the_week.to_json

** 在IRB中的输出结果:**

=> "{\"monday\":2,\"tuesday\":3,\"wednesday\":4,\"thursday\":20,\"friday\":5,\"saturday\":2,\"sunday\":5}"

查看文档:JSON#generate 边注:JSON#pretty_generate对于更好的打印以及调试非常有用。

3. 如何为一个嵌套的Hash设置默认值?

你有一个以name为索引的联系人的集合,也就是一个嵌套的Hash:

contacts = {
  'John' => {
    name: 'John',
    email: 'john@doe.com'
  },
  'Freddy' => {
    name 'Freddy',
    email: 'freddy@mercury.com'
  }
}

当你在处理单个联系人的时候,你不需要每一次都检查它是否存在。你只需要写:

contacts['Jane'][:email] = 'jane@doe.com'
puts contacts['Jane']

** IRB输出 **:

=> {:name=>"Jane", :email=>"jane@doe.com"}

你可以在创建Hash的时候通过设置代码块来实现默认值:

contacts = Hash.new do |hsh, key|
  hsh[key] = {
    name: key,
    email: ''
  }
end

或者是使用:

contacts.default_proc = Proc.new do |hsh, key|
  hsh[key] = {
    name: key,
    email: ''
  }
end

查看文档:Hash#new, Hash#default_proc

4. 如何合并两个嵌套的Hash?

在一个在线商店里,你想要将一个心愿单与当前的购物篮进行合并,这两者都是以商品的id号作为索引:

wish_list = {
  8 => {
    title: "The Color of Magic",
  },
  42 => {
    title: "The Hitch-Hiker's Guide to the Galaxy",
    price: 5
  }
}
 
basket = {
  8 => {
    price: 10
  },
  1729 => {
    title: "Ramanujan:  Twelve Lectures on Subjects Suggested by His Life and Work",
  price: 28
  }
}

借助于ActiveSupport,你可以简单地实现你的目标:

require 'active_support/core_ext/hash' # not necessary if in Rails
 
basket.deep_merge(wish_list)

又或者,在没有ActiveSupport的情况下:

def deep_merge(h1, h2)
  h1.merge(h2) { |key, h1_elem, h2_elem| deep_merge(h1_elem, h2_elem) }
end
 
deep_merge(basket, wish_list)

** IRB输出: **

=> {
  8=>{:price=>10, :title=>"The Color of Magic"},
  1729=>{:title=>"Ramanujan:  Twelve Lectures on Subjects Suggested by His Life and Work", :price=>28},
  42=>{:title=>"The Hitch-Hiker's Guide to the Galaxy", :price=>5}
}

查看文档:Hash#merge, Hash#deep_merge

5. 如何过滤掉一个Hash中的某些key?

你已经创建了一个表示日销售额的矩形图,并且你将它以Hash的方式存储,每一天就是一个key:

histogram = {
  monday: 5,
  tuesday: 7,
  wednesday: 10,
  thursday: 18,
  friday: 7,
  saturday: 2,
  sunday: 0
}

你想从中过滤掉Saturday以及Sunday。通过ActiveSupport,你可以像下面这样做:

require 'active_support/core_ext/hash' # not necessary if Rails
 
histogram.except(:saturday, :sunday)

或者在没有ActiveSupport的情况下:

def filter(hsh, *keys)
  hsh.dup.tap do |h|
    keys.each { |k| h.delete(k) }
  end
end
 
filter(histogram, :saturday, :sunday)

另一个简洁点实现则是基于reject方法的:

def filter2(hsh, *keys)
  hsh.reject { |k, _| keys.include? k }
end

请注意,如果你正在处理一个比较大的集合,你最好是先衡量下你的实现,一次选择最好的其中一个实现。 ** IRB输出:**

=> {:monday=>5, :tuesday=>7, :wednesday=>10, :thursday=>18, :friday=>7}

查看文档:Hash#except, Hash#delete, Hash#reject, Object#dup, Object#tap

6. 如何通过value对一个Hash进行“排序”?

在一个骰子类游戏中,你在Hash中储存了每一个选手的得分:

scores = {
  'The Lady' => 3,
  'Fate' => 2,
  'Death' => 10
}

你想要通过他们的得分对他们进行排序。你可以这样做:

leaderboard = scores.sort_by { |_, score| -score }

** IRB输出:**

=> [["Death", 10], ["The Lady", 3], ["Fate", 2]]

查看文档:Enumerable#sort_by 边注:Hash通过元素插入时的顺序去枚举它们的值。

7. 如何找出两个Hash中的不同?

假设你定期地从RSS订阅源中读取数据,并且将他们放在了一个Hash里边:

entries = {
  1372284000 => "CVE-2013-4073",
  1368482400 => "CVE-2013-2065"
}

当你更新了之后,你可能得到另一个Hash:

updated_entries = {
  1385074800 => "CVE-2013-4164",
  1372284000 => "CVE-2013-4073",
  1368482400 => "CVE-2013-2065"
}

你想要查找出哪一条记录才是新加的,这样你就可以通过email的方式将它们发送出去。最好的解决方案是:

new_entries = updated_entries.reject { |k, _| entries.include? k }

** IRB输出:**

=> {1385074800=>"CVE-2013-4164"}

查看文档:Hash#include?