【问题标题】:Parse Data with Many Attributes in Scala在 Scala 中解析具有许多属性的数据
【发布时间】:2013-05-22 02:13:31
【问题描述】:

我一直在使用 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 或其他库来处理连接更好吗?

【问题讨论】:

  • 为什么不让它成为一个普通的类呢?有这么多字段,您可能不想将其用于模式匹配,因此您不会损失太多。

标签: scala record etl


【解决方案1】:

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

没有分组就不能让你的类不可变,你不能利用case class的优势,比如生成equalscopy

很难使用具有这么多字段的类。

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

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: ...
  ...
}

【讨论】:

  • 我想你可能想再读一遍这个问题。尽管有标题,但问题不在于解析。
  • @RégisJean-Gilles:我可能不明白这个问题。你能告诉我有什么问题吗?很明显,位置180-219 是一个字段“位置”,它应该被分组在一个字段中。所以嵌套案例类是要走的路。作者自己也意识到了。 “有没有办法使用......导入这种数据?”。我认为将这种数据导入嵌套类的唯一方法是解析它。
  • 我也没有看到您的帖子是对 OP 问题的可能答案,因为它目前只是关于解析。但是,您的评论使联系更加清晰。请通过关注“嵌套案例类”部分来改进您的答案。解析部分可能也有帮助,但似乎不是 OP 的核心关注点(“什么可能是解决方案”-> 嵌套案例类;“如何到达那里”-> 例如,通过解析组合器解析)。
  • mhs 从我嘴里说出来。在更新后的形式中,您的答案现在很有意义,值得 +1。
  • @RégisJean-Gilles:我对解析器没有太多经验,但维基百科说“[在]计算中,解析器是解释器或编译器中的组件之一,它接受输入文本并构建一个数据结构......”虽然这个数据结构不是很有趣并且语法只是位置,但我认为它有资格作为一个常规语法(然后我使用正则表达式进行 lex)。但是,就像我说的那样,我对他们没有太多经验,而且我主要是自学成才,所以这不是我第一次犯错。
【解决方案2】:

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

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

一般实践表明,案例类的 22 个字段足以以规范化的形式表示任何类型的数据。您的案例也不例外,您已经在“LatLonElevation”示例中发现了这一点。

规范化您的数据

查看your data structure 很容易立即提取一些跨越多个字段的子实体:纬度经度海拔。你可以看到实际上这三个本身是可分组的,因为它们都代表一个 Location 信息。然后您可以再次查看您的结构并看到有 FAA LOC IDNWS 位置标识符国家州/省AbbreviationCounty 字段,将它们移入 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 DB 框架可以很好地处理标准化数据。

【讨论】:

  • 这种区别很有帮助,我会检查上面提到的 SORM 和 SLICK。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-16
  • 2012-06-11
  • 2011-11-27
  • 2021-09-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多