2010-06-13 8 views
4

J'utilise scala 2.7.7, et je voulais analyser le fichier CSV et stocker les données dans la base de données SQLite. J'ai fini par utiliser OpenCSV Java Library pour analyser le fichier CSV et utiliser la bibliothèque sqlitejdbc.processus csv dans scala

L'utilisation de ces bibliothèques java fait mon code scala est presque identique à celui de code Java (sans points-virgules et val/var)

Comme je traite avec des objets java, je ne peux pas utiliser la liste scala, carte , etc, sauf si je fais la conversion scala2java ou mise à niveau vers scala 2.8

Y a-t-il un moyen de simplifier mon code en utilisant des bits scala que je ne connais pas?

val filename = "file.csv"; 
val reader = new CSVReader(new FileReader(filename)) 
var aLine = new Array[String](10) 
var lastSymbol = "" 
while((aLine = reader.readNext()) != null) { 
    if(aLine != null) { 
     val symbol = aLine(0) 
     if(!symbol.equals(lastSymbol)) { 
      try { 
       val rs = stat.executeQuery("select name from sqlite_master where name='" + symbol + "';") 
       if(!rs.next()) { 
        stat.executeUpdate("drop table if exists '" + symbol + "';") 
        stat.executeUpdate("create table '" + symbol + "' (symbol,data,open,high,low,close,vol);") 
       } 
      } 
      catch { 
       case sqle : java.sql.SQLException => 
       println(sqle) 

      } 
      lastSymbol = symbol 
     } 
     val prep = conn.prepareStatement("insert into '" + symbol + "' values (?,?,?,?,?,?,?);") 
     prep.setString(1, aLine(0)) //symbol 
     prep.setString(2, aLine(1)) //date 
     prep.setString(3, aLine(2)) //open 
     prep.setString(4, aLine(3)) //high 
     prep.setString(5, aLine(4)) //low 
     prep.setString(6, aLine(5)) //close 
     prep.setString(7, aLine(6)) //vol 
     prep.addBatch() 
     prep.executeBatch() 
    } 
} 
conn.close() 

Répondre

9

Si vous avez un simple fichier CSV, une alternative serait de ne pas utiliser une bibliothèque CSV du tout, mais juste analyser simplement à Scala, par exemple:


case class Stock(line: String) { 
    val data = line.split(",") 
    val date = data(0) 
    val open = data(1).toDouble 
    val high = data(2).toDouble 
    val low = data(3).toDouble 
    val close = data(4).toDouble 
    val volume = data(5).toDouble 
    val adjClose = data(6).toDouble 

    def price: Double = low 
} 

scala> import scala.io._ 

scala> Source.fromFile("stock.csv") getLines() map (l => Stock(l)) 
res0: Iterator[Stock] = non-empty iterator 


scala> res0.toSeq 
res1: Seq[Stock] = List(Stock(2010-03-15,37.90,38.04,37.42,37.64,941500,37.64), Stock(2010-03-12,38.00,38.08,37.66,37.89,834800,37.89) //etc... 

qui aurait avantage que vous pouvez utiliser l'API complète de la collection Scala.

Si vous préférez utiliser des combinateurs d'analyseurs, vous trouverez également un exemple de csv parser combinator sur github.

+0

http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/base/Splitter.html dans la section http://code.google.com com/p/guava-libraries/pour une meilleure fonction de partage. string.split a quelques gotchas. – BenjaminJackman

+24

Le fractionnement sur des virgules ne fonctionnera pas si l'un des champs contient des virgules, par ex. "San Jose, Californie", "SomeOtherData" –

+1

Source.fromFile ("stock.csv") – jcalfee314

3

L'instruction if après le while est inutile - vous avez déjà vérifié que aLine n'est pas null.

Aussi, je ne sais pas exactement ce que le contenu de aLine est, mais vous voulez sans doute faire quelque chose comme

aLine.zipWithIndex.foreach(i => prep.setString(i._2+1 , i._1)) 

au lieu de compter à la main de 1 à 7. Ou bien, vous pouvez

for (i <- 1 to 7) { prep.setString(i, aLine(i)) } 

Si vous vous êtes senti en adoptant un style plus fonctionnel, vous pouvez remplacer probablement le tout avec

Iterator.continually(reader.readNext()).takeWhile(_!=null).foreach(aLine => { 
    // Body of while goes here 
} 

(et également retirer le var aLine). Mais en utilisant le moment est bien. On pourrait aussi refactoriser pour éviter le lastSymbol (par exemple en utilisant un def récursif), mais je ne suis pas vraiment sûr que cela en vaille la peine.

+0

aurait dû être 'for (i <- 0 à 6) {prep.setString (i + 1, aLine (i))}' – Jesper

3

Si vous voulez l'analyser dans Scala, les analyseurs intégrés sont assez puissants, et une fois que vous avez compris, c'est assez facile. Je ne suis pas expert, mais avec quelques tests de spécification, ce avéré être fonctionnel:

object CSVParser extends RegexParsers { 
    def apply(f: java.io.File): Iterator[List[String]] = io.Source.fromFile(f).getLines().map(apply(_)) 
    def apply(s: String): List[String] = parseAll(fromCsv, s) match { 
    case Success(result, _) => result 
    case failure: NoSuccess => {throw new Exception("Parse Failed")} 
    } 

    def fromCsv:Parser[List[String]] = rep1(mainToken) ^^ {case x => x} 
    def mainToken = (doubleQuotedTerm | singleQuotedTerm | unquotedTerm) <~ ",?".r ^^ {case a => a} 
    def doubleQuotedTerm: Parser[String] = "\"" ~> "[^\"]+".r <~ "\"" ^^ {case a => (""/:a)(_+_)} 
    def singleQuotedTerm = "'" ~> "[^']+".r <~ "'" ^^ {case a => (""/:a)(_+_)} 
    def unquotedTerm = "[^,]+".r ^^ {case a => (""/:a)(_+_)} 

    override def skipWhitespace = false 
} 

Ce n'est pas ce que je considère une solution de fonctionnalités complète peut-être, je ne suis pas la façon dont il traiterait UTF-8 etc, mais il semble fonctionner pour les CSV ASCII qui ont des citations au moins.

1

Si vous voulez quelque chose d'un peu plus idiomatique et un peu plus de type de sécurité, puis-je suggérer kantan.csv?

Il vous permet de transformer n'importe quelle source de données CSV en une collection de valeurs bien typées.Pour réécrire la partie de votre analyse syntaxique CSV par exemple (et de traiter avec des dates en tant que chaînes parce que je ne sais pas quel format vous les recevez dans), vous écririez:

import kantan.csv.ops._ 

type Row = (String, String, Double, Double, Double, Double, Double) 

// Do whatever it is you need to do with each row 
def sqliteMagic(row: Row): Unit = ??? 

new File(filename).asUnsafeCsvRows[Row](',', false).foreach(sqliteMagic) 

Notez que je ne suis pas particulièrement friand d'utiliser des tuples lorsque vous pouvez utiliser des types plus spécifiques. En utilisant le module shapeless de kantan.csv, vous pouvez écrire un peu plus bien:

import kantan.csv.ops._ 
import kantan.csv.generic.codecs._ 

case class Symbol(name: String, date: String, open: Double, high: Double, low: Double, close: Double, vol: Double) 

def sqliteMagic(symbol: Symbol): Unit = ??? 

new File(filename).asUnsafeCsvRows[Symbol](',', false).foreach(sqliteMagic) 

Notez que vous n'avez pas eu à faire un travail pour soutenir la Symbol classe de cas, ce qui est assez agréable et toujours taper en sécurité grâce à informe.

Divulgation complète: Je suis l'auteur de kantan.csv.