今、任意の個数のメールアドレスがあり、ドメイン別にメールアドレスが何個あるかを調べたい。
メールアドレスの配列 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 に含まれるメールアドレスは、途中にアットマーク(@)を1個だけ含む文字列で、ドメイン部分は英小文字、数字、ドット(.)により構成されているものとする。また、ary に重複したメールアドレスは含まれていないものとする。
解答と解説の表示・非表示
解答と解説
今回の問題のポイントは、次の2点です。
- メールアドレスからドメイン部分をどのようにして抜き出すか。
- 戻り値となるハッシュオブジェクトをどのようにしてカウンタとして利用するか。
前回と同じ方ですが、ペンネーム 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
1行目は、ハッシュ domains に変数 domain をキーとする値が存在しない場合に、そのキーと値 0 のペアをハッシュ domains に追加します。
2行目は、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 はとても便利ですが、ちょっと使い方が分かりにくいかもしれません。
オブジェクトを引数に取り、そのオブジェクトと配列要素をブロックに渡します。そしてブロックからの戻り値をオブジェクトと入れ替えます。それを繰り返すことで、目的のオブジェクトを作り出します。
この例では、空のハッシュ {} が最初のオブジェクトです。最初のブロック呼び出しの結果、{ 'sample.com' => 1 } というハッシュが返ってきます。このハッシュが最初の空のハッシュと入れ替わって、次のブロック呼び出しが実行されます。その戻り値は { 'sample.com' => 1, 'sample.jp' => 1 } というハッシュになって…と繰り返され、最終的には { 'sample.com' => 2, 'sample.jp' => 3, 'oiax.jp' => 1 } になる、というわけです。
なお、inject メソッドは Enumerable モジュールで定義されています。prototype.js にも同種同名のメソッドが存在していますので、Web を専門とするプログラマーであれば是非マスターしておきたいところです。