データの格納と検索・絞り込み

テキストや数値を複合的に含むデータを保持し、 それらから利用者の希望するもののみ提示するような機能を考える。 ここでは単純化したシラバスを例に考え方を示す。

シラバスの表現例

たとえば以下のようなシラバスに基づく科目情報を考える。

表1: 履修科目データの例

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

以下同様の項目が多数続く…

このように,形式が同じものが多数あるようなデータを設計し, 検索できるデータベースを作成する過程を示す。

これ全体が「値」
キー11c
キー22c
キー33c
これ全体が「値」
キー11b
キー22b
キー33b

1科目分の情報は1つのハッシュで表せる。そのハッシュ全体がさらに「値」 となり,それを複数集めることで科目データベースができ上がる。 ハッシュで表現した1科目分の「値」をどのように集めるかについて考察する。

-->

CSVファイルとの連携

上記のシラバス例をCSV化する。Calc のような表計算ソフトを利用して入力する例を示す。

syllabus.csv

ABCDE
1科目名科目コード担当教員 開講時期概要
2基礎プログラミング154鳥海三輔春学期 プログラミングを通して問題…
3応用プログラミング254飯森大和秋学期 問題解決を通してプログラミ…
4応用もっけ論39酒田育造春学期 山形県庄内地方でよく使われ…

「概要」の列は長くて編集しにくいかもしれない。LibreOffice Calc であれば「概要」の列見出し右クリックし、セルの書式設定を選ぶ。 「配置」タブで「テキストを自動的に折り返す」をチェックしてから列幅を 画面に収まる程度にすると多少緩和される。

RubyのCSVライブラリを用いて CSV ファイルをハッシュとしてアクセスできる形式に変換する。syllabus.csv のように1行目が見出しとして構成された CSV ファイルであれば以下の流れで読み取りを行なう。短い手順なので irb で実験してみよう。

require 'csv'
=> true
csv = CSV.read("syllabus.csv", headers: true)
=> #<CSV::Table mode:col_or_row row_count:4>

通常 CSV.read で得られた値は、あたかも配列のように行ごとに取り出しで きる。見出し行を除く先頭行が添字0となる。

csv[0]
=> #<CSV::Row "科目名":"基礎プログラミング" "科目コード":"154" "担当教員":"鳥海
三輔" "開講時期":"春学期" "概要":"プログラミングを通じて問題解決能力を向上を図る。
\n本講ではRubyを用い,データ処理の構造と考え方の基礎を学ぶ">

このように取り出した行は、 あたかもハッシュのように特定の列の値を取り出せる。

csv[0]["科目コード"]
=> "154"

また、headers オプションを true にしたときはこれに加え、 1行目にある見出し項目をハッシュのキーのように指定することで 列全体を配列として取り出せる。

csv["科目名"]
=> ["基礎プログラミング", "応用プログラミング", "応用もっけ論"]

この様子を示すと以下の図のようになる。

CSV全体をCSV.readで読み、変数 c に代入したときのイメージ

c["列名1"]
c["列名2"]
c["列名3"]
列名1 列名2 列名3 ……
1-1 1-2 1-3 …… ⇐ c[0]
2-1 2-2 2-3 …… ⇐ c[1]
:
:
:
:
:
:
……
n-1 n-2 n-3 …… ⇐ c[n]

たとえば、列で縦方向に取り出す場合、c["列名2"] とすると2列目の値が配列となり、

[値1-2, 値2-2, 値3-2, ..., 値1-2]

が得られる。

以上のしくみをふまえて、ヘッダつきで書かれた CSV ファイルを読み取るプログラムを示す。

csv-read.rb

#!/usr/bin/env ruby
# coding: utf-8
require 'csv'		        # CSVライブラリ
csvfile = "syllabus.csv"	# シラバス定義CSVファイル

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

# 動作確認用に1行ずつ表示してみる
csv.each {|row|
  # ppメソッドはpメソッドよりさらに見やすく値表示する
  pp row.to_h                    # to_h メソッドはハッシュに変換してくれる
}

syllabus.csvを保存してから 上記プログラムを実行してみよ。

csv-read.rbの実行例:
./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


数値項目の処理

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 を使うのが安全といえる。

練習問題

  1. 複数の値が入った配列 a を受け取り、各要素を整数に直したものの平均値を求めるメソッド mean() を定義せよ。

  2. ja-math.csvCSV.read で読み取り、国語の得点を配列で得て puts で出力するプログラム ja-list.rb を作成し、実行例を示せ。

  3. 1,2を組み合わせ、ja-math.csv から国語の平均点を 求めるプログラム ja-mean.rb を作成し実行例を示せ。
  4. 上で求めた平均点を用い、平均点以上を取った生徒の一覧を 出力するプログラム ja-good.rb を作成し実行例を示せ。

本日の目次