Vorrei accedere ai file CSV in scala in modo fortemente digitato. Ad esempio, mentre leggo ogni riga del csv, viene automaticamente analizzata e rappresentata come una tupla con i tipi appropriati. Potrei specificare i tipi in anticipo in una sorta di schema che viene passato al parser. Ci sono delle librerie che esistono per fare questo? In caso contrario, come potrei implementare questa funzionalità da solo?Accesso fortemente criptato a csv in scala?
risposta
product-collections sembra essere una buona misura per le vostre esigenze:
scala> val data = CsvParser[String,Int,Double].parseFile("sample.csv")
data: com.github.marklister.collections.immutable.CollSeq3[String,Int,Double] =
CollSeq((Jan,10,22.33),
(Feb,20,44.2),
(Mar,25,55.1))
product-collections utilizza opencsv sotto il cofano.
A CollSeq3
è un IndexedSeq[Product3[T1,T2,T3]]
e anche un Product3[Seq[T1],Seq[T2],Seq[T3]]
con un po 'di zucchero. Sono l'autore di product-collections.
Ecco a link to the io page of the scaladoc
Product3 è essenzialmente una tupla di arità 3.
Se si conosce il il # e tipi di campi, forse come questo ?:
case class Friend(id: Int, name: String) // 1, Fred
val friends = scala.io.Source.fromFile("friends.csv").getLines.map { line =>
val fields = line.split(',')
Friend(fields(0).toInt, fields(1))
}
Ciò è reso più complicato di quello che dovrebbe a causa delle regole di quoting non banali per CSV. Probabilmente dovresti iniziare con un parser CSV esistente, ad es. OpenCSV o uno dei progetti chiamato scala-csv. (Ci sono atleastthree.)
Quindi si finisce con una sorta di raccolta di raccolte di stringhe. Se non hai bisogno di leggere rapidamente file CSV di grandi dimensioni, puoi semplicemente provare ad analizzare ogni riga in ognuno dei tuoi tipi e prendere la prima che non genera un'eccezione. Ad esempio,
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)
})
}
Se si ha bisogno di analizzare loro abbastanza velocemente e non si sa quello che potrebbe essere lì, probabilmente si dovrebbe utilizzare una sorta di corrispondenza (ad esempio espressioni regolari) sulle singole voci. In entrambi i casi, se c'è qualche possibilità di errore, probabilmente si desidera utilizzare Try
o Option
o qualche errore di pacchetto.
Se il contenuto contiene virgolette doppie per racchiudere altre virgolette doppie, virgole e nuove righe, utilizzerei sicuramente una libreria come opencsv che gestisce correttamente i caratteri speciali. In genere si finisce con Iterator[Array[String]]
. Quindi usi Iterator.map
o collect
per trasformare ogni Array[String]
nelle tue tuple che si occupano degli errori di conversione dei tipi lì. Se è necessario elaborare l'input senza caricare tutto in memoria, si continua a lavorare con l'iteratore, altrimenti è possibile convertire in un Vector
o List
e chiudere il flusso di input.
Così può assomigliare a questo:
val reader = new CSVReader(new FileReader(filename))
val iter = reader.iterator()
val typed = iter collect {
case Array(double, int, string) => (double.toDouble, int.toInt, string)
}
// do more work with typed
// close reader in a finally block
A seconda di come si deve fare con gli errori, è possibile tornare Left
per gli errori e per Right
tuple di successo per separare gli errori dalle righe corrette. Inoltre, a volte avvolgo tutto questo usando scala-arm per chiudere le risorse. Quindi i miei dati potrebbero essere inclusi nella monade resource.ManagedResource
in modo da poter utilizzare input provenienti da più file.
Infine, anche se si desidera lavorare con le tuple, ho trovato che di solito è più chiaro avere una classe di case che è appropriata per il problema e quindi scrivere un metodo che crea quell'oggetto case class da un Array[String]
.
Ho costruito la mia idea per digitare fortemente il prodotto finale, più che la fase di lettura stessa .. che come indicato potrebbe essere meglio gestito come uno stadio con qualcosa come Apache CSV, e lo stage 2 potrebbe essere quello che ho fatto. Ecco il codice sei il benvenuto. L'idea è di digitare il CSVReader [T] con il tipo T .. dopo la costruzione, è necessario fornire al lettore anche un oggetto Fattore di Tipo [T]. L'idea qui è che la classe stessa (o nel mio esempio un oggetto helper) decide i dettagli della costruzione e quindi la disaccoppia dalla lettura vera e propria. Potresti usare oggetti impliciti per passare l'aiutante, ma non l'ho fatto qui. L'unico lato negativo è che ogni riga del CSV deve essere dello stesso tipo di classe, ma è possibile espandere questo concetto secondo necessità.
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
prossimo l'esempio Helper fabbrica e l'esempio "Main"
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
}
Esempio CSV (scheda separata .. potrebbe aver bisogno di riparare se si copia da un editor)
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
E infine lo scrittore (notare che i metodi di fabbrica richiedono anche questo con "creatore"
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
}
Ho creato un aiutante CSV fortemente tipizzato per Scala, chiamato object-csv. Non è un quadro completo, ma può essere regolato facilmente. Con esso si può fare questo:
val peopleFromCSV = readCSV[Person](fileName)
Dove persona è di classe caso, definito in questo modo:
case class Person (name: String, age: Int, salary: Double, isNice:Boolean = false)
Per saperne di più su di esso in GitHub, o nel mio blog post su di esso.
È possibile utilizzare kantan.csv, progettato appositamente per questo scopo.
Immaginate di avere il seguente testo:
1,Foo,2.0
2,Bar,false
Utilizzando kantan.csv, si potrebbe scrivere il seguente codice di analizzarlo:
import kantan.csv.ops._
new File("path/to/csv").asUnsafeCsvRows[(Int, String, Either[Float, Boolean])](',', false)
E si otterrebbe un iteratore in cui ogni voce è di tipo (Int, String, Either[Float, Boolean])
. Nota il bit in cui l'ultima colonna nel tuo CSV può essere di più di un tipo, ma questo è convenientemente gestito con Either
.
Questo è tutto fatto in un modo completamente sicuro, senza alcuna riflessione, convalidato in fase di compilazione.
A seconda di quanto in basso nella tana del coniglio che sei disposto ad andare, c'è anche un modulo per la classe shapeless automatizzata caso e il tipo di somma di derivazione, così come il supporto per scalaz e cats tipi e classi di tipo.
Full disclosure: Sono l'autore di kantan.csv.
- 1. Matrix al CSV in Scala
- 2. Accesso a strutture dati Scala in JRuby
- 3. accesso casuale CSV; C#
- 4. dati principali di accesso in un nidificato ripetitore fortemente tipizzato
- 5. Accesso a file FTP da Scala
- 6. Accesso pubblico predefinito in scala
- 7. locale Cache Biblioteca C# (persistente e criptato)
- 8. Accesso a classi nidificate di Scala da Java
- 9. convertire jpg in scala di grigi csv utilizzando R
- 10. C# Dizionario a .csv
- 11. Gioca! 2.0 Scala - Accesso all'oggetto globale
- 12. fortemente tipizzato azione url
- 13. csv a matrice in d3.js
- 14. csv a matrice sparsa in python
- 15. Accesso ai campi scala oggetto da java
- 16. Posso creare un metodo con accesso protetto Java in Scala?
- 17. Conversione da JSON a CSV e CSV a JSON in C#
- 18. In .Net/C#, è null fortemente tipizzato?
- 19. Convert csv grande a hdf5
- 20. Esportazione da maiale a CSV
- 21. Esporta HIVE a un CSV
- 22. Perché Scala si blocca durante la lettura del mio CSV?
- 23. Redis-cli --csv opzione (esportazione in csv)
- 24. Scrivi RDD a un csv
- 25. CSV a XLS script linux
- 26. Accesso alle risorse di configurazione in IDE Scala
- 27. export html table a csv
- 28. variabile in CDATA a Scala
- 29. Accesso a "questo" in Clojurescript
- 30. Conversione da CSV a array
Mi piace il modo in cui appare, ma sto cercando di capire come funziona. Non capisco davvero cosa sta succedendo in CsvParser.scala.template. Quali sono questi modelli nella sezione standard? – mushroom
Il modello viene elaborato da http://github.com/marklister/sbt-boilerplate che genera un file scala. Se si crea il progetto, è possibile vedere i risultati nella directory target/scala-2.10/src_managed. È esattamente lo stesso modo in cui Tuple1 ... Tuple22 di scala sembra funzionare. CsvParsers esiste per le entità da 1 a 22 e il compilatore seleziona quello corretto per la firma del tipo (schema) che fornisci. –
Esiste un metodo consigliato per gestire valori o valori nulli che non vengono analizzati? Sarebbe bello se potessi dare Option [T] come parametro di tipo; potrebbe tentare di analizzarlo come una T e dare una Nessuna se non è riuscita a analizzare o era vuota. – mushroom