将文件转换为CSV

时间:2014-11-12 12:14:13

标签: scala csv

我有一个平面文本文件,其格式如下。

98430John        Smith       Y
98431Mary        Jones       N
98432Michael     Johnson     Y
  • 前5个字符是ID。
  • 接下来12个字符是第一个名字。
  • 接下来12个字符是姓氏。
  • 最后1个字符是Y / N值。

我一直在使用Java打开文件,并使用substring和FileInputStream拆分文件。

FileInputStream fis = new FileInputStream(fin);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));

String line = null;
while ((line = br.readLine()) != null) {
   String csvString = str.substring(5)+","str.substring(6, 18);
}

br.close();

我现在想做同样的事情,但是在Scala中。我知道我可以通过导入Java库来实现这一点,但我只是想知道,是否有更优雅,纯粹的Scala方法来实现这一目标?

2 个答案:

答案 0 :(得分:1)

您可以使用scala.io.Source来阅读文件。

示例代码(无异常处理!):

import scala.io.Source

// field lengths
val fieldLengths = List(5, 12, 12, 1)

// from field lengths to start pos: List(0, 5, 17, 29, 30)
val startPos = fieldLengths.foldLeft(List(0)){(acc, l) =>
  l + acc.head :: acc
}.reverse


// fields: List[List[String]] = List(List(98430, John, Smith, Y), List(98431, Mary, Jones, N),
val fields = Source.fromFile("/tmp/test.txt").getLines map { line =>
  startPos.zip(fieldLengths).map{ case (start, length) =>
      line.substring(start, start + length).trim
  }
}

// csv: String = 
//   98430,John,Smith,Y
//   98431,Mary,Jones,N
//  98432,Michael,Johnson,Y
val csv = fields.map(_.mkString(",")).mkString("\n")

答案 1 :(得分:1)

处理i / o可能永远不会优雅,因为您始终必须考虑i / o异常的发生。一旦你必须处理异常,你就不再具有好的功能(在数学意义上,即没有状态和确定性结果)。有一种情况可以优雅地处理异常行为:如果异常是结果的一部分(例如,如果您正在编写测试引擎),则可以将结果指定为类型Try[Something]所以java.io或java.nio是可以选择的

考虑到可测试性,将i / o访问与转换分开。首先读取输入文件(如果它至少适合内存),然后将其转换为csv-string。

您的输入格式可以很容易地表达为正则表达式,您可以根据该表达式匹配输入行。假设ID始终是整数,则正则表达式可能如下所示:(\d{5})(.{12})(.{12})([Y,N])

给定输入行上的迭代器,您可以使用fold和regexp-matcher将输入转换为csv-string:

object ToCSV {

  val InputFormat = "(\\d{5})(.{12})(.{12})([Y,N])".r

  def main(args: Array[String]): Unit = {
    // Assume the input to be read from file using a BufferedReader
    val input =
      """98430John        Smith       Y
        |98431Mary        Jones       N
        |98432Michael     Johnson     Y""".stripMargin
    val inputLines = input.lines 

    val csvString =
      (inputLines foldLeft "") {
        case (accumulator, InputFormat(id, firstName, lastName, yesOrNo)) =>
          s"$accumulator$id,${firstName.trim},${lastName.trim},$yesOrNo\n"
      }

    print(csvString)
  }
}

与正则表达式匹配的好处是你的字符串看起来像一个元组。在代码中,"#####firstName lastName X"case class InputFormat(id, firstName, lastName, yesOrNo)的实例无法区分。

编辑:实际上,如果将正则表达式更改为.trim,则可以删除对(\d{5})(\S{1,12})\s*(\S{1,12})\s*([Y,N])的调用,但是第一个名称和姓氏不能包含空格。