検索機能の実装

データをプログラム中の変数に取り込んだだけでは 情報を利用できない。利用者に提供するためには, 必要なものを絞り込んで提示する機能が必要である。

先述の例の履修科目をCSVファイルから読み取ったものから、 利用者が必要とするものを選んで出力するものを作成する。

ハッシュや配列からの絞り込み

ハッシュあるいは配列に複数の要素が入っているときに, その中からある一定の条件を満たしたもののみに選別するには select メソッドを利用する。CSV ファイルから読み取った値にもこれが適用できる。 ハッシュに適用する select メソッドは 配列の持つ select メソッドとほぼ同様の記法だが,ブロックに対する引数を2つにし, 各々にキーと値を受け取る形式にする。

csv-read.rb で読み取った値は以下のようになっている。 3行分のデータがそれぞれ、ハッシュ形式に変換されていることに注意する。

./csv-read.rb
{"科目名"=>"基礎プログラミング",
 "科目コード"=>"154",
 "担当教員"=>"鳥海三輔",
 "開講時期"=>"春学期",
 "概要"=>"プログラミングを通じて問題解決能力を向上を図る。\n" + "本講ではRubyを用い,データ処理の構造と考え方の基礎を学ぶ"}
{"科目名"=>"応用プログラミング",
 "科目コード"=>"254",
 "担当教員"=>"飯森大和",
 "開講時期"=>"秋学期",
 "概要"=>
  "問題解決を通じてプログラミング能力向上を図る。\n" +
  "本講ではプログラミング言語Cを通じて,問題解決に最適なアルゴリズムを適用する方策を学ぶ。"}
{"科目名"=>"応用もっけ論",
 "科目コード"=>"39",
 "担当教員"=>"酒田育造",
 "開講時期"=>"春学期",
 "概要"=>
  "山形県庄内地方でよく使われる「もっけ」には様々な意味があり,他の地域にもこのような多様性を持つ言葉がある。\n" +
  "その成立ちをふまえて正しくその地域の「もっけ」を理解し,深い交流のきっかけとする。"}

この3つのレコードのうち,「開講時期」が "春学期" であるものを選択するには以下のようにする。

# csv-read.rbの続きから
db = csv                       # いったん別の変数にコピー
db = db.select {|row| row["開講時期"] == "春学期"}

ブロック変数 row は、3度代入され繰り返される。 row["開講時期"] の値は回ごとに、 "春学期", "秋学期", "春学期" が得られるため、== "春学期" で比較して true が返るのは1行目と3行目である。

なお、selectメソッドによる絞り込みでは,文字列比較だけでなく 正規表現マッチや数値比較も使える。 これらの絞り込みを繰り返すことでどのような検索も可能で、 複合条件の指定もできる。 以下の例は,開講時期と担当教員での絞り込みを正規表現マッチで行ない, 該当するもののみを出力するプログラムである。

csv-search.rb

#!/usr/bin/env ruby
# coding: utf-8
Encoding.default_external = 'utf-8'	# 入出力コードをutf-8に
require 'csv'		        # CSVライブラリ
csvfile = "syllabus.csv"	# シラバス定義CSVファイル

csv = CSV.read(csvfile, headers: true)

db = csv                       # いったん別の変数にコピー

STDERR.print("開講時期は(指定なしの場合は空で)?: ")
term = gets.chomp
STDERR.print("担当教員は(指定なしの場合は空で)?: ")
whom = gets.chomp

# selectメソッドで絞り込み:
# selectは1つずつ要素を取り出し、ブロック内の式が真(nilでもfalseでもない)
# を返す要素(CSVの1行に相当)のみで構成される集合を返す。
if term > ""			# 開講時期指定に何か文字列を入れたなら
  ptn = Regexp.new(term)	# 文字列を正規表現に変換
  db = db.select {|row| ptn =~ row["開講時期"]}	# 選択して再代入
end
if whom > ""			# 担当教員指定に何か文字列を入れたなら
  ptn = Regexp.new(whom)	# 文字列を正規表現に変換
  db = db.select {|row| ptn =~ row["担当教員"]}
end

puts("【該当科目一覧】")
db.each {|row|			# eachメソッドで1行ずつrowに取り出す
  printf("%s %sの情報 %s\n", "="*20, row["科目名"], "="*20)
  row.each {|key, value|
    printf("%s: %s\n", key, value)
  }
}

数値項目の処理

CSV.read によるCSVファイル一括読込の標準動作では、 各項目を文字列として代入する。したがって、CSV データの項目中に数値と見なすべきデータがあった場合は、 数値に変換する処理が必要となる。これには以下の2つの方法が考えられる。

要素を数値化して比較

表1の履修科目表を CSV.read で読み取ったものから、「科目コード」が100以上200未満のものを選別してみる。 このとき「科目コード」はいずれも必ず整数値が入るものと仮定できるとする。

上記 csv-search.rb の科目検索部分の select による選択を以下のようにすればよい。

db = db.select {|row|	# dbの集合から1行ずつ row 変数に取り出して選別
  row["科目コード"].to_i >= 100 && row["科目コード"].to_i < 200
  # 科目コード(数値) >= 100    かつ  科目コード(数値) < 200
}

CSV読み取り時に数値変換

以下のように CSV.read に与えるオプションで converters: :numeric を与えると数値と解釈できる値は数値化される。

x = CSV.read(csvfile, headers: true, converters: :numeric)

これにより、上記科目コード選択は以下のように書けることになる。

db = db.select {|row|
  row["科目コード"] >= 100 && row["科目コード"] < 200
}

もちろん、元のCSVデータに数値化できない値があった場合は数値化されず、 数値に対してのみ行なえる演算を施すとエラーになるので注意が必要である。 数値でない項目がある可能性を考える場合は、converters: オプションを用いず、その都度 to_i や to_f を使うのが安全といえる。


本日の目次