现在,有任意个数的邮件地址,要求按域调查出邮件地址的个数。
将邮件地址的数组 ary 作为参数获取,写出返回散列对象的方法 foo。
该散列对象必须满足下面的条件。
- 每个键是邮件地址的域部分(@后面的部分)。
- 每个值是域部分和键一致的邮件地址的个数。
例如,考虑下面的邮件地址的集合。
john@sample.com
taro@sample.jp
nancy@sample.com
jiro@sample.jp
hermes@oiax.jp
saburo@sample.jp
创建将这些邮件地址作为元素的数组,传递到 foo,它会返回像下面这样的散列对象。
{
'sample.com' => 2,
'sample.jp' => 3,
'oiax.jp' => 1
}
此外,包含 ary 的邮件地址,是中间只含有一个(@)的字符串,域部分由英文小写字母,数字,点(.)构成。此外,ary 里不包括重复的邮件地址。
解答与说明的显示・隐藏
解答与说明
这次的问题具有以下两个要点。
- 如何从邮件地址中提取域部分。
- 成为返回值的散列表对象怎样作为计数器使用呢。
这个也由笔名为 MTG 的先生来解答。
def foo( mail_adds )
domains = Hash.new
mail_adds.each do |item|
domain = item.match(/([^@]+)$/).to_s
new_flag = true
if domains.size > 0
domains.each do |key,value|
if domain == key
domains[key] = value + 1
new_flag = false
break
end
end
end
domains[domain] = 1 if new_flag
end
return domains
end
提取域部分的处理出乎意料。
我以前是Perl的程序员,所以不知不觉中便想这样写。
item.match(/([^@]+)$/)
domain = $&
但是,Ruby 从 Perl 那里继承的像 $& 这样的全局变量很难记,在缺少别人的帮助的情况下,基本上就这样写了。
item.match(/([^@]+)$/)
domain = Regexp.last_match(0)
然后,MTG 君利用了 match 方法返回的 MatchData 对象的 to_s 方法,返回与正则表达式相匹配的那部分字符串的性质。我以前并不了解这个。
接下来,计算在不同域出现的次数的方法,MTG 君好像费了很大功夫。
好像一开始没有想到将 1 初始化的简单的方法。
实际上可以这样写。
domains[domain] ||= 0
domains[domain] += 1
第一行,散列表 domains 中如果不存在将变量 domain 作为键的值,就将键以及值 0 追加到散列表 domains。
第二行,domains[domain] 值上加 1 ,然而不能像其他语言一样写成 domain[domain]++ 。
下面,是我的示范解答。
def foo(ary)
ary.inject({}) do |hsh, email|
domain = email.match(/@/).post_match
hsh[domain] ||= 0
hsh[domain] += 1
hsh
end
end
Array 类的实例方法 inject 非常的方便,不过用法可能不太好理解。
在参数中获取对象,将该对象和数组元素传递到块。然后,用对象初始化block的第一个参数,数组元素迭代传入block的第二个参数。通过重复操作,便可生成目的对象。
在这个例子里,空的散列表 {} 是最初的对象。最初块调用的结果是散列表{ 'sample.com' => 1 }返回。将这个散列表用最初的空的散列表替换,执行下一个块调用。它的返回值是散列表{ 'sample.com' => 1, 'sample.jp' => 1 }…这样反复操作后,最终结果是{ 'sample.com' => 2, 'sample.jp' => 3, 'oiax.jp' => 1 }。
还有,inject 方法在 Enumerable 模块下被定义。因为在 prototype.js 也有同名同种类的方法存在,所以如果以 Web 为专业的程序员就必须掌握它。