0

我正在尝试读取 CSV 并将其存储在类似矩阵的数组对象数组中。我遇到的一个障碍是字符串是环绕的引号读入的——也就是说,字符串“price”不仅仅是单词价格,而是scala中的“”“”价格“”“”。因此,我想删除那些周围的引号。我还想确保任何数值都被强制转换为 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 的超级新手,所以如果答案没有假设很多知识,我将不胜感激。谢谢!

4

3 回答 3

1

您可能希望使用解析 CSV 文件的库,而不是尝试自己解决边缘情况。Scala /Java 有很多选择(一二 )。

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

在 Scala 中,函数的最后一条语句是返回值。因此,使您的if语句成为函数中的最后一个语句,并立即返回替换/解析的值将满足您的要求。

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

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

了解数据的特定格式(例如,给定字段始终为双精度字段还是始终为字符串),您可以将其重写为更精确并支持实际类型。

于 2013-09-29T20:21:10.187 回答
1

您的代码存在一些问题:

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

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

    def fixRawObs(x: String) = {
        if(x.startsWith(""""""")) x.replaceAll(""""""", "")
        否则 x.toDouble
    }

但是,此函数的返回类型是“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()
于 2013-09-29T20:32:48.593 回答
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))
于 2014-03-01T12:32:52.367 回答