LINQやコレクションAPIを使ってCSVファイルからデータ抽出 - C#, F#, Scala, Groovy, Ruby の場合
id:fits:20110702 や id:fits:20110709 にて、SQL を使ってデータ抽出した処理を LINQ やコレクション API を使って実施し直してみました。(ただし、今回は station_g_cd でのソートを実施していない等、以前使った SQL と完全に同じではありません)
今回は C#, F#, Scala, Groovy, Ruby(JRuby)で実装していますが、どの言語も似たような API を用意しており、同じように実装できる事が分かると思います。
以前使った SQL 例
SELECT * FROM ( SELECT pref_name, station_g_cd, station_name, count(*) as lines FROM CSVREAD('m_station.csv') S JOIN CSVREAD('m_pref.csv') P ON S.pref_cd = P.pref_cd GROUP BY station_g_cd, station_name ORDER BY lines DESC, station_g_cd ) WHERE ROWNUM <= 10
csv ファイルの内容は id:fits:20110702 を参照。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20110718/
C# の場合(LINQ)
C# 4.0 で LINQ を使って実装してみました。
実装内容は以下の通り。
- File.ReadAllLines(・・・) で行単位のコレクションを取得
- Skip でヘッダー行を無視
- join で都道府県を結合
- group by でグループ化
- 匿名型を使ってグルーピング
- 戻り値の型は IEnumerable
>
- order by でソート
- Take を使って 10件取得
LINQ を使うと SQL 風に実装できます。
listup_station.cs
using System; using System.Linq; using System.IO; using System.Text; class ListUpStation { public static void Main(string[] args) { //都道府県の取得 var plines = File.ReadAllLines("m_pref.csv", Encoding.Default); var prefs = from pline in plines.Skip(1) let p = pline.Split(',') select new { PrefCode = p[0], PrefName = p[1] }; //路線数の多い駅の抽出 var slines = File.ReadAllLines("m_station.csv", Encoding.Default); var list = ( from sline in slines.Skip(1) let s = sline.Split(',') join p in prefs on s[10] equals p.PrefCode group s by new { StationName = s[9], PrefName = p.PrefName, StationGroupCode = s[5] } into stGroup orderby stGroup.Count() descending select stGroup ).Take(10); //結果出力 foreach(var s in list) { Console.WriteLine("{0}駅 ({1}) : {2}", s.Key.StationName, s.Key.PrefName, s.Count()); } } }
実行結果
> csc listup_station.cs > listup_station.exe 新宿駅 (東京都) : 12 横浜駅 (神奈川県) : 11 東京駅 (東京都) : 10 渋谷駅 (東京都) : 10 池袋駅 (東京都) : 9 大宮駅 (埼玉県) : 9 新橋駅 (東京都) : 7 大船駅 (神奈川県) : 7 上野駅 (東京都) : 7 千葉駅 (千葉県) : 7
F# の場合
F# 2.0.0 では LINQ を使わずコレクションを使って実装してみました。
実装内容は以下の通り。
- File.ReadAllLines(・・・) で行単位のコレクションを取得
- Seq.skip でヘッダー行を無視
- 都道府県を Map.ofSeq で Map 化
- Seq.groupBy でグループ化
- レコード定義を使ってグルーピング
- List.sortWith でソート
- sortWith を使うために List.ofSeq を使って List 化
- Seq.take を使って 10件取得
なお、グルーピングの際にレコード定義を使ってますが、Tuple を使っても特に問題ありません。
listup_station.fsx
open System open System.IO open System.Text //レコード定義 type Station = { StationName: string PrefName: string StationGroupCode: string } //都道府県の取得 let prefMap = File.ReadAllLines("m_pref.csv", Encoding.Default) |> Seq.skip 1 |> Seq.map (fun l -> let items = l.Split(',') (items.[0], items.[1]) ) |> Map.ofSeq //路線数の多い駅の抽出 let lines = File.ReadAllLines("m_station.csv", Encoding.Default) let list = lines |> Seq.skip 1 |> Seq.map (fun l -> l.Split(',')) |> Seq.groupBy (fun s -> { StationName = s.[9] PrefName = Map.find s.[10] prefMap StationGroupCode = s.[5] } ) |> List.ofSeq |> List.sortWith (fun a b -> Seq.length(snd b) - Seq.length(snd a)) |> Seq.take 10 //結果出力 for s in list do let st = fst s stdout.WriteLine("{0}駅 ({1}) : {2}", st.StationName, st.PrefName, Seq.length((snd s)))
実行結果
> fsi listup_station.fsx 新宿駅 (東京都) : 12 横浜駅 (神奈川県) : 11 東京駅 (東京都) : 10 渋谷駅 (東京都) : 10 池袋駅 (東京都) : 9 大宮駅 (埼玉県) : 9 新橋駅 (東京都) : 7 大船駅 (神奈川県) : 7 上野駅 (東京都) : 7 千葉駅 (千葉県) : 7
Scala の場合
Scala 2.9.0.1 もコレクションで実装してみました。
実装内容は以下の通り。
- Source.fromFile(・・・).getLines() で行単位のコレクションを取得
- drop でヘッダー行を削除
- 都道府県を toMap で Map 化
- groupBy でグループ化
- ケースクラスを使ってグルーピング
- sortWith でソート
- take を使って 10件取得
なお、グルーピングでケースクラスを使ってますが、F# と同様に Tuple を使っても問題ありません。
listup_station.scala
import scala.io.Source case class Station(val stationName: String, val prefName: String, val stationGroupCode: String) //都道府県の取得 val prefMap = Source.fromFile("m_pref.csv").getLines().drop(1).map {l => val items = l.split(",") items(0) -> items(1) }.toMap //路線数の多い駅の抽出 val lines = Source.fromFile("m_station.csv").getLines() val list = lines.drop(1).toList.map(_.split(",")).groupBy {s => Station(s(9), prefMap.get(s(10)).get, s(5)) }.toList.sortWith {(a, b) => a._2.length > b._2.length } take 10 //結果出力 list.foreach {s => printf("%s駅 (%s) : %d\n", s._1.stationName, s._1.prefName, s._2.length) }
実行結果
> scala listup_station.scala 新宿駅 (東京都) : 12 横浜駅 (神奈川県) : 11 東京駅 (東京都) : 10 渋谷駅 (東京都) : 10 池袋駅 (東京都) : 9 大宮駅 (埼玉県) : 9 大船駅 (神奈川県) : 7 京都駅 (京都府) : 7 新橋駅 (東京都) : 7 千葉駅 (千葉県) : 7
Groovy の場合
Groovy 1.8.0 もコレクションで実装してみました。
実装内容は以下の通り。
- new File(・・・).readLines() で行単位のコレクションを取得
- tail でヘッダー行以外を取得
- 都道府県を collectEntries で Map 化
- groupBy でグループ化
- 配列を使ってグルーピング
- sort でソート
- List の getAt(Range) を使って 10件取得(asList()[0..9] の箇所)
- getAt(Range) を使うために entrySet() で取得した Set を asList() で List 化
なお、a <=> b は a.compareTo(b) と同じです。
listup_station.groovy
//都道府県の取得 def prefMap = new File("m_pref.csv").readLines() tail() collectEntries { def items = it.split(",") [items[0], items[1]] } //路線数の多い駅の抽出 def list = new File("m_station.csv").readLines() tail() collect { it.split(",") } groupBy { [it[9], prefMap[it[10]], it[5]] } sort {a, b -> b.value.size <=> a.value.size } entrySet() asList()[0..9] //結果出力 list.each { println "${it.key[0]}駅 (${it.key[1]}) : ${it.value.size}" }
実行結果
> groovy listup_station.groovy 新宿駅 (東京都) : 12 横浜駅 (神奈川県) : 11 東京駅 (東京都) : 10 渋谷駅 (東京都) : 10 池袋駅 (東京都) : 9 大宮駅 (埼玉県) : 9 新橋駅 (東京都) : 7 大船駅 (神奈川県) : 7 上野駅 (東京都) : 7 千葉駅 (千葉県) : 7
Ruby の場合
Ruby(JRuby 1.6.3)では csv モジュールを使わずに実装してみました。
実装内容は以下の通り。
- IO.readlines(・・・) で行単位のコレクションを取得
- drop でヘッダー行を削除
- chop で改行文字を削除
- 都道府県を Hash[] で Hash 化
- group_by でグループ化
- 配列を使ってグルーピング
- sort でソート
- take を使って 10件取得
listup_station.rb
#都道府県の取得 prefMap = Hash[IO.readlines("m_pref.csv").drop(1).map {|l| l.chop.split(',')}] #路線数の多い駅の抽出 list = IO.readlines("m_station.csv").drop(1).map {|l| l.chop.split(',') }.group_by {|s| [s[9], prefMap[s[10]], s[5]] }.sort {|a, b| b[1].length <=> a[1].length }.take 10 #結果出力 list.each do |s| puts "#{s[0][0]}駅 (#{s[0][1]}) : #{s[1].length}" end
実行結果
> jruby listup_station.rb 新宿駅 (東京都) : 12 横浜駅 (神奈川県) : 11 渋谷駅 (東京都) : 10 東京駅 (東京都) : 10 大宮駅 (埼玉県) : 9 池袋駅 (東京都) : 9 新橋駅 (東京都) : 7 大船駅 (神奈川県) : 7 京都駅 (京都府) : 7 岡山駅 (岡山県) : 7