はじめに
どうも、torihaziです
Rubyで csvを扱う時があり、かつ "ある列の値を取り出したい"ということがありました。
どうやるんだとなったので、色々調べてやってみました。
version
ruby -v ruby 3.2.0 (2022-12-25 revision a528908271) [aarch64-linux]
rails cで
困ったらrails cで色々実験です。
なのでこいつをrequireします
の前にヒアドキュメントを使って、文字列を作ります。
app(dev)" users = <<CSV app(dev)" id,first name,last name,age app(dev)" 1,taro,tanaka,20 app(dev)" 2,jiro,suzuki,18 app(dev)" 3,ami,sato,19 app(dev)" 4,yumi,adachi,21 app(dev)> CSV
次にrequireします。
この文字列をCSV.parseを使ってパースすることで加工することができます
c = CSV.parse(users) => [["id", "first name", "last name", "age"], ["1", "taro", "tanaka", "20"], ["2", "jiro", "suzuki", "18"], ["3", "ami", "sato", "19"], ["4", "yumi", "adachi", "21"]]
となって、このように配列の配列で返されます。
今回やりたいことは "取得したい列番号を指定して値を取り出す"ことです。
列番号を指定して値を扱うためには CSV::Tableクラスにすると便利です。
名前がheaderの列の値や2列目の値を取り出すことができます。
parseを使ってCSV::Tableを作る
c = CSV.parse(users, headers: true) => #<CSV::Table mode:col_or_row row_count:5> ...
headers optionをtrueにすると CSV::Tableクラスになります。
もう少し。
厄介なことにCSV::Tableクラスは モードという概念があり、defaultでは
:col_or_row というモードです。これは ヘッダーの名前でしか指定できません。
:col_or_row デフォルトはこのモードです。このマニュアル内ではミックスモードと呼んでいます。行単位でアクセスするか列単位でアクセスするか自動的に判断します。
:row ロウモード。テーブルに行単位でアクセスします。
:column カラムモード。テーブルに列単位でアクセスします。
現状のモードは CSV::Tableクラスのインスタンスメソッド modeを使って確認できます。
app(dev)> c = CSV.parse(users, headers: true) => #<CSV::Table mode:col_or_row row_count:5> ... app(dev)> c.mode => :col_or_row
これで2通りの方法で列の値を見てみると
app(dev)> c["id"] => ["1", "2", "3", "4"] app(dev)> c[0] => #<CSV::Row "id":"1" "first name":"taro" "last name":"tanaka" "age":"20">
今は ミックスモードなので、1列目を [0]とindex指定でアクセスする事はできません。
indexで指定するとrowのデータになってしまいます。
そこでmodeを変換します。
app(dev)> c["id"] => ["1", "2", "3", "4"] app(dev)> c[0] => #<CSV::Row "id":"1" "first name":"taro" "last name":"tanaka" "age":"20"> app(dev)> app(dev)> app(dev)> app(dev)> c.by_col! => #<CSV::Table mode:col row_count:5> id,first name,last name,age 1,taro,tanaka,20 2,jiro,suzuki,18 3,ami,sato,19 4,yumi,adachi,21 app(dev)> c[0] => ["1", "2", "3", "4"]
こうすると先ほど 0 でアクセスしたら rowになっていたものが rowとならずに取得することができました。
これであれば、穴あきのheaderであってもアクセスできそうです。
例えば
app(dev)" users = <<CSV app(dev)" ,first name,last name,age app(dev)" 1,taro,tanaka,20 app(dev)" 2,jiro,suzuki,18 app(dev)" 3,ami,sato,19 app(dev)" 4,yumi,adachi,21 app(dev)> CSV
先ほどのidを消したものです
これで1列目を指定したいですが、ヘッダーの名前では指定できそうにありません。
そこでmode変えてできるかを試してみます。
app(dev)> c.by_col! => #<CSV::Table mode:col row_count:5> ,first name,last name,age 1,taro,tanaka,20 2,jiro,suzuki,18 3,ami,sato,19 4,yumi,adachi,21 app(dev)> app(dev)> app(dev)> c[0] => ["1", "2", "3", "4"]
変わらず行けましたね、納得です。
終わりに
実務で穴あきのheaderを持つcsvを扱うことになりそうだったので予習です。
なんとかなりそう