格式化读入的CSV数据

时间:2013-09-29 19:13:12

标签: scala csv

我正在尝试读取CSV并将其存储在类似矩阵的数组对象数组中。我遇到的障碍是在中使用周围的引号读取字符串 - 也就是说,字符串“price”不仅仅是单词price,而是“”“”price“”“ “斯卡拉。因此,我想删除那些周围的引号。我还想确保将任何数值强制转换为Double / Int,因为它们以字符串形式读入。

我现在拥有的:

val rawObs = io.Source.fromFile(file).getLines() .map(_.split(",")).toArray

// An example element of the array is:
//scala> rawObs(2)
//res93: Array[String] = Array("3", 0, "2013-02-27", 1, 52, 52, 1, "1", "kg")

// Here, I make a function to remove surrounding strings and return a string, or if there are 
// not surrounding strings, return a Double.
def fixRawObs(x: String) = {
    // if it was actually meant to be a string:
    if(x.startsWith(""""""")){
               // delete any " quotes
               var y = x.replaceAll(""""""", "") 
            } else { // this means that x needs to be coerced to Int or Double
               var y = x.toDouble
            }
            y // return y
 }
// but this won't compile, it returns <console>:14: error: not found: value y

// If it did compile, I'd want to do something like this: 
rawObs.map(_.map(fixRawObs(_)))
// (although, is there a better way?)

所以,基本上,我的第一个问题是如何修复我的fixRawObs函数,其次,这甚至是一个好的方法来做到这一点还是有一些更好的方法来实现我想要的?我正在做的事情有点像黑客。

我是Scala的超级新手,所以如果答案没有多少知识,我将非常感激。谢谢!

3 个答案:

答案 0 :(得分:1)

您可能希望使用一个解析CSV文件的库,而不是试图自己完成边缘情况。 Scala / Java有很多选项(one two)。

如果您正在练习Scala,我将解释为什么它不会编译。问题是您正在尝试返回y,这是在循环范围内定义的,并且在其外部不可用。

在Scala中,函数的最后一个语句是返回值。因此,将if语句作为函数中的最后一个语句,并立即返回替换/解析后的值将完成您想要的操作。

def fixRawObs(x: String) = {
    x.startsWith("\"") match {
       case true =>
         x.replaceAll("\"", "")
       case false =>
        x.toDouble
    }
}

请注意,该函数将返回Any的实例 - Scala中所有类的超类。这是因为您在一个子句中返回一个String,在另一个子句中返回一个Double。

了解数据的具体格式(例如,给定字段总是双倍或者总是字符串),您可以将其重写为更精确并支持实际类型。

答案 1 :(得分:1)

您的代码存在一些问题:

  1. 您正在尝试将字符串和双打存储在数组中。由于最接近的常见超级类型的字符串和双打是Any,因此您将拥有一个数组[Any]。使用数组[任意],您需要在需要时使用它们将值转换为字符串或双打,这是不可取的。

  2. 你的函数fixRawObs()没有编译,因为它试图返回一个不可访问的变量。 “y”在花括号内声明,这使得花括号外无法访问。 “y”实际上甚至不是必需的,因为Scala中的if语句返回一个值,就像函数一样。你可以这样做:

    def fixRawObs(x: String) = {
        if(x.startsWith(""""""")) x.replaceAll(""""""", "") 
        else x.toDouble
    }
  3. 此函数的返回类型为“Any”,因此您仍然必须手动将返回值强制转换为正确的类型。同样,这不是一个好方法。

    我建议创建一个类,以便您可以使用适当的类型来引用您的值的自定义数据结构。

    case class Row(
        col1: String, col2: Double, col3: String, col4: Double, 
        col5: Double, col6: Double, col7: Double, col8: String, col9: String
    )
    

    最好使用适当的描述性名称重命名值。

    然后您可以像这样创建行对象:

    def stripQuotes(s: String): String = {
        if(s.startsWith("\"") && s.endsWith("\"")) s.dropRight(1).dropLeft(1)
        else s
    }
    
    val csv = io.Source.fromFile(file)
    val rows = (for {
        line <- file.getLines
        s = line.split(",")
        if(s.size == 9)
    } yield {
        new Row(
            stripQuotes(s(0)),
            s(1).toDouble,
            stripQuotes(s(2)),
            s(3).toDouble,
            s(4).toDouble,
            s(5).toDouble,
            s(6).toDouble,
            stripQuotes(s(7)),
            stripQuotes(s(8))
        )
    }).toArray
    csv.close()
    

答案 2 :(得分:0)

这个简单的库:product-collections的功能类似于您尝试使用数组阵列执行的操作。它还有一个直观的csv阅读器。

更新:库现在直接处理案例类:

val csv = Array("3", 0, "2013-02-27", 1, 52, 52, 1, "1", "kg").mkString(",")
csv: String = 3,0,2013-02-27,1,52,52,1,1,kg

scala> case class Row(
     |     col1: String, col2: Double, col3: String, col4: Double, 
     |     col5: Double, col6: Double, col7: Double, col8: String, col9: String
     | )
defined class Row

scala> CsvParser(Row).parse(new java.io.StringReader(csv))
res33: Seq[Row] = List(Row(3,0.0,2013-02-27,1.0,52.0,52.0,1.0,1,kg))