強く型付けされた方法で scala の csv ファイルにアクセスしたいと思います。たとえば、csv の各行を読み取ると、自動的に解析され、適切な型のタプルとして表されます。パーサーに渡されるある種のスキーマで、事前にタイプを指定できます。これを行うためのライブラリはありますか? そうでない場合、どうすればこの機能を自分で実装できますか?
7 に答える
まさにその目的を念頭に置いて設計されたkantan.csvを使用できます。
次の入力があるとします。
1,Foo,2.0
2,Bar,false
kantan.csv を使用すると、次のコードを記述して解析できます。
import kantan.csv.ops._
new File("path/to/csv").asUnsafeCsvRows[(Int, String, Either[Float, Boolean])](',', false)
そして、各エントリが type であるイテレータを取得します(Int, String, Either[Float, Boolean])
。CSV の最後の列が複数のタイプである可能性があるビットに注意してください。ただし、これは で便利に処理されEither
ます。
これはすべて完全にタイプ セーフな方法で行われ、リフレクションは含まれず、コンパイル時に検証されます。
うさぎの穴のどこまで進んでいくかにもよりますが、自動化された case クラスと sum 型の派生のためのshapelessモジュール、およびscalazとcatの型と型クラスのサポートもあります。
完全な開示: 私は kantan.csv の作成者です。
object-csvと呼ばれる、Scala 用の厳密に型指定された CSV ヘルパーを作成しました。本格的なフレームワークではありませんが、簡単に調整できます。これを使用すると、次のことができます。
val peopleFromCSV = readCSV[Person](fileName)
Person はケース クラスで、次のように定義されます。
case class Person (name: String, age: Int, salary: Double, isNice:Boolean = false)
編集:コメントで指摘されているように、kantan.csv(他の回答を参照)は、この編集を行った時点(2020-09-03)でおそらく最高です。
これは、CSV の引用規則が自明ではないため、必要以上に複雑になります。おそらく、 OpenCSVや scala-csv というプロジェクトの 1つなど、既存の CSV パーサーから始める必要があります。(少なくとも 3 つあり ます。)
次に、ある種の文字列のコレクションのコレクションになります。大量の CSV ファイルをすばやく読み取る必要がない場合は、各行を各タイプに解析して、例外をスローしない最初の行を取得することができます。例えば、
import scala.util._
case class Person(first: String, last: String, age: Int) {}
object Person {
def fromCSV(xs: Seq[String]) = Try(xs match {
case s0 +: s1 +: s2 +: more => new Person(s0, s1, s2.toInt)
})
}
それらをかなり迅速に解析する必要があり、何がそこにあるのかわからない場合は、おそらく個々の項目に対してある種のマッチング (正規表現など) を使用する必要があります。Try
いずれにせよ、エラーが発生する可能性がある場合は、またはOption
エラーをパッケージ化するために使用したいでしょう。
私は自分のアイデアを構築し、最終製品を読み取り段階自体よりも強力に型キャストしました..指摘されたように、Apache CSV のようなものを使用して段階 1 として処理する方が適切であり、段階 2 は私が行ったことである可能性があります。これがあなたがそれに歓迎されているコードです。アイデアは、CSVReader[T] を型 T で型キャストすることです。構築時に、リーダーに Type[T] の Factor オブジェクトも提供する必要があります。ここでの考え方は、クラス自体 (または私の例ではヘルパー オブジェクト) が構築の詳細を決定し、これを実際の読み取りから切り離すというものです。Implicit オブジェクトを使用してヘルパーを渡すこともできますが、ここでは行っていません。唯一の欠点は、CSV の各行が同じクラス タイプでなければならないことですが、必要に応じてこの概念を拡張できます。
class CsvReader/**
* @param fname
* @param hasHeader : ignore header row
* @param delim : "\t" , etc
*/
[T] ( factory:CsvFactory[T], fname:String, delim:String) {
private val f = Source.fromFile(fname)
private var lines = f.getLines //iterator
private var fileClosed = false
if (lines.hasNext) lines = lines.dropWhile(_.trim.isEmpty) //skip white space
def hasNext = (if (fileClosed) false else lines.hasNext)
lines = lines.drop(1) //drop header , assumed to exist
/**
* also closes the file
* @return the line
*/
def nextRow ():String = { //public version
val ans = lines.next
if (ans.isEmpty) throw new Exception("Error in CSV, reading past end "+fname)
if (lines.hasNext) lines = lines.dropWhile(_.trim.isEmpty) else close()
ans
}
//def nextObj[T](factory:CsvFactory[T]): T = past version
def nextObj(): T = { //public version
val s = nextRow()
val a = s.split(delim)
factory makeObj a
}
def allObj() : Seq[T] = {
val ans = scala.collection.mutable.Buffer[T]()
while (hasNext) ans+=nextObj()
ans.toList
}
def close() = {
f.close;
fileClosed = true
}
} //class
次にヘルパー ファクトリの例と「メイン」の例
trait CsvFactory[T] { //handles all serial controls (in and out)
def makeObj(a:Seq[String]):T //for reading
def makeRow(obj:T):Seq[String]//the factory basically just passes this duty
def header:Seq[String] //must define headers for writing
}
/**
* Each class implements this as needed, so the object can be serialized by the writer
*/
case class TestRecord(val name:String, val addr:String, val zip:Int) {
def toRow():Seq[String] = List(name,addr,zip.toString) //handle conversion to CSV
}
object TestFactory extends CsvFactory[TestRecord] {
def makeObj (a:Seq[String]):TestRecord = new TestRecord(a(0),a(1),a(2).toDouble.toInt)
def header = List("name","addr","zip")
def makeRow(o:TestRecord):Seq[String] = {
o.toRow.map(_.toUpperCase())
}
}
object CsvSerial {
def main(args: Array[String]): Unit = {
val whereami = System.getProperty("user.dir")
println("Begin CSV test in "+whereami)
val reader = new CsvReader(TestFactory,"TestCsv.txt","\t")
val all = reader.allObj() //read the CSV info a file
sd.p(all)
reader.close
val writer = new CsvWriter(TestFactory,"TestOut.txt", "\t")
for (x<-all) writer.printObj(x)
writer.close
} //main
}
CSV の例 (タブ区切り。エディターからコピーする場合は修正が必要な場合があります)
Name Addr Zip "Sanders, Dante R." 4823 Nibh Av. 60797.00 "Decker, Caryn G." 994-2552 Ac Rd. 70755.00 "Wilkerson, Jolene Z." 3613 Ultrices. St. 62168.00 "Gonzales, Elizabeth W." "P.O. Box 409, 2319 Cursus. Rd." 72909.00 "Rodriguez, Abbot O." Ap #541-9695 Fusce Street 23495.00 "Larson, Martin L." 113-3963 Cras Av. 36008.00 "Cannon, Zia U." 549-2083 Libero Avenue 91524.00 "Cook, Amena B." Ap
#668-5982 Massa Ave 69205.00
そして最後にライター (ファクトリ メソッドでは "makerow" と同様にこれが必要であることに注意してください)
import java.io._
class CsvWriter[T] (factory:CsvFactory[T], fname:String, delim:String, append:Boolean = false) {
private val out = new PrintWriter(new BufferedWriter(new FileWriter(fname,append)));
if (!append) out.println(factory.header mkString delim )
def flush() = out.flush()
def println(s:String) = out.println(s)
def printObj(obj:T) = println( factory makeRow(obj) mkString(delim) )
def printAll(objects:Seq[T]) = objects.foreach(printObj(_))
def close() = out.close
}