Scala 优化csv文件的非规范化
我正在读取一个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
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是按分区、行、站名进行排序的。