05-05 学习记录


scala extractor and regex

最近遇到一个场景:输入三个数字,用逗号分隔,同时要求三个数字两两不相同。
常规解析需要分割出三个数字,转成整型,判断边界条件,最后判断两两是否相同。
重新温习了下scala的extractor,发现如下代码就可以清晰明了地解决这个场景:

object Digit {

  def unapply(s: String): Option[Int] = try {
    Some(s.toInt)
  } catch {
    case _: NumberFormatException => None
  }

  // simple one, pre-condition: s match \d
  // def unapply(s: String): Option[Int] = Some(s.toInt)
}

val PATTERN = "^(\\d),(\\d),(\\d)$".r

def parse(ds: String): Option[(Int, Int, Int)] = ds match {
  case PATTERN(_ @ Digit(a), _ @ Digit(b), _ @ Digit(c)) if a != b && a != c && b != c => Some((a, b, c))
  case _ => None
}

println(parse("1,2,3"))
println(parse("1,2,2"))
println(parse("1,2,33"))
println(parse("a,2,3"))

这里主要有以下几点:

  • 正则表达式的模式匹配,注意PATTERN
  • 变量绑定的模式匹配,注意_ @ Digit(a)
  • 最后一个两两互不相等的使用guard实现

这样的解析相比low-level的处理要清晰很多。

JSON解析器

复习了一下Scala的JavaTokenParsers,重新写了次JSON解析器。
自己也尝试过非JavaTokenParsers的,记得当时自己用的还是Specs(不是电视剧名字),现在觉得scalatest更上手点。
其实下面的代码和《Scala编程》上基本是一样的,因为我就是参照那本书的语法定义来的。

import scala.util.parsing.combinator._

class JsonParser extends JavaTokenParsers {

  def value: Parser[Any] = obj | arr | stringLiteral |
    floatingPointNumber ^^ ( _.toDouble ) |
    "null" ^^ ( x => null ) |
    "true" ^^ ( x => true ) |
    "false" ^^ ( x => false )
 
  def obj: Parser[Any] = "{" ~> repsep(member, ",") <~ "}" ^^ (Map() ++ _)

  def arr: Parser[List[Any]] = "[" ~> repsep(value, ",") <~ "]"

  def member: Parser[(String, Any)] = stringLiteral ~ ":" ~ value ^^ {
    case name ~ ":" ~ value => (name, value)
  }

  def parse(cs: CharSequence) = parseAll(value, cs)

}

val parser = new JsonParser
println(parser.parse("""
{
  "name": "XnnYygn",
  "hobbies": ["A", "B", "C"]
}
"""
))
,