使用提取器来解析文本文件

时间:2014-01-30 14:25:28

标签: scala

我正在尝试改进CSV解析例程,并认为提取器在这里很有用,但无法解决它们。假设有一个包含用户ID和电子邮件的文件:

1,alice@alice.com
2,bob@bob.com
3,carol@carol.com

如果User类被定义为case class User(id: Int, email: String),那么一切都非常简单

lines map { line =>
  line split "," match {
    case Array(id, email) => User(id.toInt, email)
  }
}

我不明白的是如何处理User类具有复杂属性的情况,例如

case class Email(username: String, host: string)
case class User(id: Int, email: Email)

3 个答案:

答案 0 :(得分:2)

您可能希望使用正则表达式来提取电子邮件地址的内容。也许是这样的:

val lines = Vector(
  "1,alice@alice.com",
  "2,bob@bob.com",
  "3,carol@carol.com")

case class Email(username: String, host: String)
case class User(id: Int, email: Email)

val EmailRe = """(.*)@(.*\.com)""".r  // substitute a real email address regex here

lines.map { line =>
  line.split(",") match {
    case Array(id, EmailRe(uname, host)) => User(id.toInt, Email(uname, host))
  }
}

答案 1 :(得分:1)

在这里,您可以使用自定义提取器。

// 1,Alice,21212,Baltimore,MD" -> User(1, Alice, Address(21212, Baltimore, MD))

定义一个自定义Extractor,用于创建给定String的对象:

object UserExtractor {
    def unapply(s: String) : Option[User] = try {
        Some( User(s) )
    }
    catch {
        // bettor handling of bad cases
        case e: Throwable => None
    }
}

用于在User上的Comapnion对象上使用自定义应用来保存数据的案例类:

case class Address(code: String, cit: String, county: String)

case class User(id: Int, name: String, address: Address)
object User {
    def apply(s: String) : User = s.split(",") match {
        case Array(id, name, code, city, county) => User(id.toInt, name, Address(code, city, county)  )
    }
}

取消应用有效字符串(在示例中,有效表示正确的字段数)。

"1,Alice,21212,Baltimore,MD" match { case UserExtractor(u) => u }
res0: User = User(1,Alice,Address(21212,Baltimore,MD))

可以使用更多自定义应用方法添加更多测试。

答案 2 :(得分:0)

我使用一个RegexExtractor:

val lines = List(
  "1,alice@alice.com",
  "2,bob@bob.com",
  "3,carol@carol.com"
)

case class Email(username: String, host: String)
case class User(id: Int, email: Email)


val idAndEMail = """^([^,]+),([^@]+)@(.+)$""".r  

并定义一个将行转换为用户的函数:

def lineToUserWithMail(line: String) : Option[User] = 
  idAndEMail.findFirstMatchIn(line) map { 
case userWithEmail(id,user,host) => User(id.toInt, Email(user,host) )
  }

将该功能应用于所有行

lines flatMap lineToUserWithMail
//res29: List[User] = List(User(1,Email(alice,alice.com)), User(2,Email(bob,bob.com)), User(3,Email(carol,carol.com)))

或者,您可以通过添加unnapply方法在case classe上实现自定义Extractor。但对于那种情况,这不值得痛苦。

以下是unapply

的示例
class Email(user:String, host:String)
object Email {
def unapply(s: String) : Option[(String,String)] = s.split("@") match {
    case Array(user, host) => Some( (user,host) )
    case _ => None 
}

}

"bob@bob.com" match {
case Email(u,h) => println( s"$u , $h" )
}
// prints bob , bob.com

使用正则表达式解析CSV数据的警告。这并不像你想象的那么容易,我建议使用CSV-Reader作为http://supercsv.sourceforge.net/来处理一些开箱即用的令人讨厌的边缘情况。