Scala 优化csv文件的非规范化

Scala 优化csv文件的非规范化,scala,Scala,我正在读取一个csv文件,该文件将存储在一个不可变的数据结构中。每一排都是入口。每个入口有一个车站。每个车站可以有多个入口。有没有一种方法可以让我一次通过而不是你在下面看到的两次通过 object NYCSubwayEntrances { def main(args: Array[String]) = { import com.github.tototoshi.csv.CSVReader //http://www.mta.info/developers/data/nyct/s

我正在读取一个csv文件,该文件将存储在一个不可变的数据结构中。每一排都是入口。每个入口有一个车站。每个车站可以有多个入口。有没有一种方法可以让我一次通过而不是你在下面看到的两次通过

object NYCSubwayEntrances {
  def main(args: Array[String]) = {
    import com.github.tototoshi.csv.CSVReader
    //http://www.mta.info/developers/data/nyct/subway/StationEntrances.csv
    val file = new java.io.File("StationEntrances.csv")
    val reader = CSVReader.open(file)
    reader.readNext //consume headers
    val entranceMap = list2multimap(
      reader.all map {
        case fields: List[String] => 
          // println(fields)
          (
            fields(2), 
            Entrance(
              fields(14).toBoolean,
              Option(fields(15)),
              fields(16).toBoolean,
              fields(17),
              fields(18) match {case "YES" => true case _ => false},
              fields(19) match {case "YES" => true case _ => false},
              fields(20),
              fields(21),
              fields(22),
              fields(23),
              fields(24).toInt,
              fields(25).toInt
            )
          )
        }
      )
    reader.close
    val reader2 = CSVReader.open(file)
    reader2.readNext //consume headers
    val stations = reader2.all map { case fields: List[String] =>
      Station(
        fields(2),
        fields(0),
        fields(1),
        colate(scala.collection.immutable.ListSet[String](
          fields(3),
          fields(4),
          fields(5),
          fields(6),
          fields(7),
          fields(8),
          fields(9),
          fields(10),
          fields(11),
          fields(12),
          fields(13)
        )),
        entranceMap(fields(2)).toList
      )
    }
    reader2.close

    import net.liftweb.json._
    import net.liftweb.json.Serialization.write
    implicit val formats = Serialization.formats(NoTypeHints)
    println(pretty(render(parse(write(stations.toSet)))))
  }

  import scala.collection.mutable.{HashMap, Set, MultiMap}

  def list2multimap[A, B](list: List[(A, B)]) = 
    list.foldLeft(new HashMap[A, Set[B]] with MultiMap[A, B]){(acc, pair) => acc.addBinding(pair._1, pair._2)}

  def colate(set: scala.collection.immutable.ListSet[String]): List[String] =
    ((List[String]() ++ set) diff List("")).reverse
}

case class Station(name: String, division: String, line: String, routes: List[String], entrances: List[Entrance]) {}
case class Entrance(ada: Boolean, adaNotes: Option[String], freeCrossover: Boolean, entranceType: String, entry: Boolean, exitOnly: Boolean, entranceStaffing: String, northSouthStreet: String, eastWestStreet: String, corner: String, latitude: Integer, longitude: Integer) {}
具有所有正确相关性的sbt项目可在以下位置找到:


StationEntrances.csv是从

获得的。我有以下代码片段。第一种解决方案使用
groupBy
对与同一车站相关的入口进行分组。它不假定行已排序。虽然它只读取一次文件,但实际上它执行了3次传递(一次读取内存中的所有内容,一次用于
groupBy
,一次用于创建站点)。有关
提取器的代码,请参见末尾

val stations = {
  val file = new java.io.File("StationEntrances.csv")
  val reader = com.github.tototoshi.csv.CSVReader.open(file)
  val byStation = reader
    .all     // read all in memory
    .drop(1) // drop header
    .groupBy {
      case List(division, line, station, _*) => (division, line, station)
    }
  reader.close
  byStation.values.toList map { rows =>
    val entrances = rows map { case Row(_, _, _, _, entrance) => entrance }
    rows.head match {
      case Row(division, line, station, routes, _) =>
        Station(
          division, line, station,
          routes.toList.filter(_ != ""),
          entrances)
    }
  }
}
此解决方案假定行已排序并且应该更快,因为它只执行一次传递,并在读取文件时生成结果列表

val stations2  = {
  import collection.mutable.ListBuffer
  def processByChunk(iter: Iterator[Seq[String]], acc: ListBuffer[Station])
           : List[Station] = {
    if (!iter.hasNext) acc.toList
    else {
      val head = iter.next
      val marker = head.take(3)
      val (rows, rest) = iter.span(_ startsWith marker)
      val entrances = (head :: rows.toList) map {
        case Row(_, _, _, _, entrance) => entrance
      }
      val station = head match {
        case Row(division, line, station, routes, _) =>
          Station(
            division, line, station,
            routes.toList.filter(_ != ""),
            entrances)
      }
      processByChunk(rest, acc += station)
    }
  }
  val file = new java.io.File("StationEntrances.csv")
  val reader = com.github.tototoshi.csv.CSVReader.open(file)
  val stations = processByChunk(reader.iterator.drop(1), ListBuffer())
  reader.close
  stations
}           
我已经创建了一个专用提取器来获取给定线路的路线/入口。我认为这会使代码更具可读性,但如果您处理的是列表,则调用
字段(0)
字段(25)
并不是最佳选择,因为每次调用都必须遍历列表。提取器避免了这种情况。对于大多数JavaCSV解析器,通常会得到
Array[String]
,所以这通常不是问题。最后,csv解析通常不会返回空字符串,因此您可能希望使用
if(adaNotes==“”)None-other-Some(adaNotes)
而不是
选项(adaNotes)


我有以下片段。第一种解决方案使用
groupBy
对与同一车站相关的入口进行分组。它不假定行已排序。虽然它只读取一次文件,但实际上它执行了3次传递(一次读取内存中的所有内容,一次用于
groupBy
,一次用于创建站点)。有关
提取器的代码,请参见末尾

val stations = {
  val file = new java.io.File("StationEntrances.csv")
  val reader = com.github.tototoshi.csv.CSVReader.open(file)
  val byStation = reader
    .all     // read all in memory
    .drop(1) // drop header
    .groupBy {
      case List(division, line, station, _*) => (division, line, station)
    }
  reader.close
  byStation.values.toList map { rows =>
    val entrances = rows map { case Row(_, _, _, _, entrance) => entrance }
    rows.head match {
      case Row(division, line, station, routes, _) =>
        Station(
          division, line, station,
          routes.toList.filter(_ != ""),
          entrances)
    }
  }
}
此解决方案假定行已排序并且应该更快,因为它只执行一次传递,并在读取文件时生成结果列表

val stations2  = {
  import collection.mutable.ListBuffer
  def processByChunk(iter: Iterator[Seq[String]], acc: ListBuffer[Station])
           : List[Station] = {
    if (!iter.hasNext) acc.toList
    else {
      val head = iter.next
      val marker = head.take(3)
      val (rows, rest) = iter.span(_ startsWith marker)
      val entrances = (head :: rows.toList) map {
        case Row(_, _, _, _, entrance) => entrance
      }
      val station = head match {
        case Row(division, line, station, routes, _) =>
          Station(
            division, line, station,
            routes.toList.filter(_ != ""),
            entrances)
      }
      processByChunk(rest, acc += station)
    }
  }
  val file = new java.io.File("StationEntrances.csv")
  val reader = com.github.tototoshi.csv.CSVReader.open(file)
  val stations = processByChunk(reader.iterator.drop(1), ListBuffer())
  reader.close
  stations
}           
我已经创建了一个专用提取器来获取给定线路的路线/入口。我认为这会使代码更具可读性,但如果您处理的是列表,则调用
字段(0)
字段(25)
并不是最佳选择,因为每次调用都必须遍历列表。提取器避免了这种情况。对于大多数JavaCSV解析器,通常会得到
Array[String]
,所以这通常不是问题。最后,csv解析通常不会返回空字符串,因此您可能希望使用
if(adaNotes==“”)None-other-Some(adaNotes)
而不是
选项(adaNotes)


输入文件似乎按
分区、行、站名进行排序。这是你愿意依赖的假设吗?它可以加快处理速度并减少内存需求(尽管文件看起来不是很大)。是的,我愿意假设csv是按
分区、行、站名
排序的。输入文件似乎是按
分区、行、站名
排序的。这是你愿意依赖的假设吗?它可以加快处理速度,还可以减少内存需求(尽管文件看起来不是很大)。是的,我愿意假设csv是按
分区、行、站名进行排序的。