在Scala中解析具有许多属性的数据

时间:2013-05-22 02:13:31

标签: scala record etl

我一直在使用Scala中的天气数据进行一些自主学习,因为数据是免费的,很大的,并且可以提供给我想要做的很多其他事情。我立即遇到了如何用超过22列的文本文件表示数据的问题。

处理超过22列数据的惯用方法是什么?我一直试图在NOAA的NWS-USAF-NAVY station list阅读,每行有32条信息。

我最初的倾向是使用案例类,但最直接的方法是定义它们:

  /*
  first goal is to be able to read the inventory of WBAN stations from NOAA at
  ftp.ncdc.noaa.gov/pub/data/inventories/WBAN.TXT
  formats are listed in:
  ftp://ftp.ncdc.noaa.gov/pub/data/inventories/WBAN-FMT.TXT
  although I don't think that file is completely right
  */

case class WBAN(
  CoopStationID:                  Option[String],  // 01 - 06     Coop Station Id
  ClimateDivision:                Option[String],  // 08 - 09     Climate Division
  WBANStationID:                  Option[String],  // 11 - 15     WBAN Station Id
  WMOStationID:                   Option[String],  // 17 - 21     WMO Station Id
  FAALOCID:                       Option[String],  // 23 - 26     FAA LOC ID
  // and so on, for 32 elements!

不允许,因为scala不允许超过22个项目的案例类,因为它使用元组来表示数据。

嵌套元组似乎是一种可能的解决方案,因此不是为NOAA列出的每个项目都有一个字段,而是可以嵌套纬度,经度,高度等等:

  // class representing a latitude or longitude's information
case class DMS(
  Degrees:   Int,
  Minutes:   Int,
  Seconds:   Int
  )

// class combining a lat lon with elevation data
case class LatLonElevation(
  Latitude:          DMS,
  Longitude:         DMS,
  LatLonPrecision:   String,
  ElevationGround:   Option[Int],
  Elevation:         Option[Int],
  ElevationTypeCode: Option[Int]
  )

或者你把它放在一个地图中,每个值都有一个向量?

似乎应该有一种简洁的方法来实现这一点,但在实现之后,我最终重复了不同格式的相当多的含义,这非常难看。有没有办法使用SLICK或其他库导入这种数据,还是会有与案例类相同的限制?顺便说一句,使用lazy val,Future或其他库来处理连接是否更好?

2 个答案:

答案 0 :(得分:2)

如果你有这么多字段,你应该以某种方式对它们进行分组。

如果不进行分组,则无法使您的类不可变,您无法使用case class的优势,例如生成的equalscopy

很难使用包含这么多字段的类。

在您的情况下,位置180-219是单个字段“位置”,应将其分组到单个字段中。没有其他Latitude Seconds字段,Latitude *完全没用。

location只有18个字段,您可以减少此数字。

分组数据字段的自然方式是嵌套的case类。

要从字符串生成嵌套类,可以使用parsing combinator。这似乎有点矫枉过正,但你会得到一个干净的代码结构:

class WbanParsers extends RegexParsers {
  def wban: Parser[Wban] = stationId ~ .... ~ latLong ~ ... ^^
    { case sid ~ ... ~ latLong ~ ... => Wban(sid, ..., latLong, ...) }
  ...
  def latLong: Parser[LatLonElevation] = dms ~ dms ~ ... ^^
    { case lat ~ long ~ ... => LatLonElevation(lat, long, ...) }
  def dms: Parser[Dms] = (" " ^^^ {Positive} | "-" ^^^ {Negative}) ~ degrees ~ minutes ~ seconds ^^
    { case sign ~ d ~ m ~ s => Dms(sign, d, m, s) }
  ...
}

您可以使用一些解析器创建trait,以便以不同的格式重复使用它们。

例如:

class WbanParsers extends RegexParsers with LatLongParser {
  def wban: ... ~ latLonf ~ ...
  ...
}

trait LatLongParser {
  this: RegexParsers =>

  def latLong: ...
  def dms: ...
  ...
}

答案 1 :(得分:2)

您所描述的问题比Scala更为普遍。表示某些数据有两种方法:denormalizednormalized form非规范化数据是平坦的,更适合存储和传输,同时更难以推理和管理人类。 规范化数据正好相反。

您以非规范化形式获取数据。由于您计划在充满抽象和分类的高级语言中使用它,因此在解析时将这些数据规范化是很自然的。

通用实践表明,22个案例类的字段足以表示规范化形式的任何类型的数据。您的情况也不例外,您已经在自己的“LatLonElevation"示例

规范化数据

查看your data structure,您可以轻松地提取跨越多个字段的一些子实体:纬度经度海拔。你可以看到,事实上这三个人本身就是可分组的,因为它们都代表 Location 信息。您可以再次查看您的结构并查看 FAA LOC ID NWS位置标识符国家/地区州/省缩写字段,进入 Location 也是有意义的。这样做最终会得到一个标准化的数据结构,该结构由一堆相互关联的案例类组成,每个类只有几个字段。

case class Station 
  ( coopId : String,
    wbanId : String,
    wmoId : String,
    icaoId : String,
    location : Location )

case class Location
  ( faaId : String,
    nwsId : String,
    country : String,
    stateOrProvince : Option[ String ],
    county : Option[ String ],
    latitude : Latitude,
    longitude : Longitude,
    elevation : Elevation )

case class Latitude
  ( direction : LatitudeDirection.Value,
    degrees : Int,
    minutes : Int,
    seconds : Int )

object LatitudeDirection extends Enumeration {
  val North, South = Value
}

//  and so on

存储此数据

幸运的是,有一个SORM Scala数据库框架可以很好地处理规范化数据。