Scala模式匹配提取器(extracter)+正则表达式+正则表达式提取器

1. 提取器

提取器会从输入中提取出匹配的部分。假定我们在写一个服务,处理股票相关的输入。对我们来说,手头的第一个工作就是接收股票代码,返回这个股票的价格(为了演示,这里打印出结果)。

process()方法需要校验给定的代码是否有效,如果有效,则返回其股价。代码如下:

object Symbol {
    def unapply(symbol: String): Boolean = symbol == "GOOD" || symbol == "IBM"
}

object StockService {
    def process(input: String): Unit = {
        input match {
            case Symbol() => println("Look up price for valid symbol " + input)
            case _ => println("Invalid input " + input)
        }
    }
}

process()方法使用了提取器执行匹配。如果提取器确定股票代码有效,就会返回true,否则,返回false。如果返回true,会执行同case关联的表达式,否则,模式匹配继续下一个case。

提取器有个方法叫作unapply(),接收要匹配的值。执行caseSymbol()=> …时,match表达式会自动把input作为参数传入unapply()。
执行结果为:

Look up price for valid symbol GOOD
Look up price for valid symbol IBM
Invalid input ERR

现在,我们可以请求股票报价了,对于服务而言,下一个任务时设置股票价格。假设这个消息的格式是“SYMBOL:PRICE"。我们需要对这种格式进行模式匹配,然后采取行动。下面是修改过的proces()方法,处理了这个附加的任务:

object Symbol {
    def unapply(symbol: String): Boolean = symbol == "GOOD" || symbol == "IBM"
}

object ReceiveStockPrice {
    def unapply(input: String): Option[(String, Double)] = {
        try {
            if (input contains ":") {
                val splitQuote = input split ":"
                Some(splitQuote(0), splitQuote(1).toDouble)
            } else {
                None
            }
        } catch {
            case _: NumberFormatException => None
        }

    }
}

object StockService {
    def process(input: String): Unit = {
        input match {
            case Symbol() => println("Look up price for valid symbol " + input)
            case ReceiveStockPrice(symbol, price) =>
                printf("Received price %f for symbol %s\n", price, symbol)
            case _ => println("Invalid input " + input)
        }
    }
}

这里添加了一个新的case,用到ReceiveStockPrice提取器。这个提取器不同于之前写的Symbol提取器。后者只返回了一个boolean结果,而ReceiveStockPrice则需要解析输入,返回两个值,symbol和price。在case语句里,它们被指定成ReceiveStockPrice的实参;不过,它们并不是传入的实参,而是从提取器中传出的实参。所以,symbol和price并不是用来传递值,而是用来接收值得。

ReceiveStockPrice提取器有一个unapply(),根据“:”分割输入,返回一个元组,包含股票代码和价格。然而,这里还有个catch,因为输入可能不遵循“SYMBOL:PRICE"得格式。为了处理这种可能性,这个方法得返回值应该是Option[(String, Double)]。在运行时,我们得到得要么时Some(String, Double),要么时None。

执行下面服务:

        StockService process "GOOD"
        StockService process "GOOD:310.84"
        StockService process "GOOD:BUY"
        StockService process "IBM"
        StockService process "ERR:12.21"

执行结果:

Look up price for valid symbol GOOD
Received price 310.840000 for symbol GOOD
Invalid input GOOD:BUY
Look up price for valid symbol IBM
Received price 12.210000 for symbol ERR

对于前三个请求,这段代码都做了很好得处理。接收了有效得参数,拒绝了无效得参数。不过,最后一个请求处理得并不好。它应该拒绝掉无效得股票代码ERR,即使输入得格式是有效得。有两种方式处理这种情况。一是在ReceiveStockPrice里检查股票代码是否有效,不过这会导致重复得工作。另外,还可以在一个case语句里应用多个模式。只需修改process()方法来做到这一点:

object Symbol {
    def unapply(symbol: String): Boolean = symbol == "GOOD" || symbol == "IBM"
}

object ReceiveStockPrice {
    def unapply(input: String): Option[(String, Double)] = {
        try {
            if (input contains ":") {
                val splitQuote = input split ":"
                Some(splitQuote(0), splitQuote(1).toDouble)
            } else {
                None
            }
        } catch {
            case _: NumberFormatException => None
        }

    }
}

object StockService {
    def process(input: String): Unit = {
        input match {
            case Symbol() => println("Look up price for valid symbol " + input)
            case ReceiveStockPrice(symbol@Symbol(), price) =>
                printf("Received price %f for symbol %s\n", price, symbol)
            case _ => println("Invalid input " + input)
        }
    }
}

这里会先应用ReceiveStockPrice提取器,成功得话,会返回一对结果。对第一个结果(symbol)进一步应用Symbol提取器校验这个股票得代码。我们可以使用后面跟着@符号得模式变量,在symbol从两个提取器之间传递得过程中把它拦截住。
重新运行代码,输出如下结果:

Look up price for valid symbol GOOD
Received price 310.840000 for symbol GOOD
Invalid input GOOD:BUY
Look up price for valid symbol IBM
Invalid input ERR:12.21

2. 正则表达式

Scala 通过scala.util.matching包里的类支持正则表达式。创建正则表达式,就是在利用这个包里的Regex类的实例在工作。假定我们要检查给定的字符串是否包含Scala或scala:

        val pattern = "(S|s)cala".r

        val str = "Scala is scalable and cool"
        println(pattern findFirstIn str) // Some(Scala)

这里创建了一个String,然后,调用了它的r()方法。Scala会隐式的将String转换成RichString,调用这个方法得到一个Regex实例。当然,如果正则表达式需要转义字符,用原始字符串会好些。"""\d2:\d2:\d4""“写起来和读起来都比”\d2:\d2:\d4"容易的多。

为了找到正则表达式的第一个匹配,调用findFirstIn()方法即可。
如果要找的不单是匹配单词第一次出现的地方,而是要找所有出现的地方,可以使用findAllIn():

        println((pattern findAllIn str).mkString(",")) // Scala,scala

如果想替换字符串,可以用replaceFirstIn()替换第一个匹配,或者用replaceAllIn()替换所有的地方:

        println("cool".r replaceFirstIn(str, "awesome")) // Scala is scalable and awesome

3. 把正则表达式当作提取器

Scala正则表达式提供了一个买一送一的选择。创建一个正则表达式,就附送一个提取器。Scala的正则表达式就是提取器,所以,很容易就可以在case表达式里使用。Scala会把每个放在括号里的匹配都展开到一个模式变量里。比如说,"(S|s)cala".r有一个unapply()方法,它会返回Option[String]。另一方面,"(S|s)(cala)".r的unapply()会返回Option[String,String]。用个例子来说明这一点,假定我们想对"GOOG"这个股票进行模式匹配,获取其价格。下面就是用正则表达式实现的方式:

    def process(input: String): Unit = {
        val GoogStock = """^GOOG:(\d*\.?\d+)""".r
        input match {
            case GoogStock(price) => println("Price of GOOD is " + price)
            case _ => println("not processing " + input)
        }
    }

    def main(args: Array[String]): Unit = {
        process("GOOG:310.84") // Price of GOOD is 310.84
        process("GOOG:310") // Price of GOOD is 310
        process("IBM:84.01") // not processing IBM:84.01
    }

这里创建了一个正则表达式匹配字符串,这个字符串以"GOOG:"开头,后面跟着一个正的带小数的十进制数。将其存到一个叫GoogStock的val里。幕后,Scala为这个提取器创建了一个unapply()方法,返回匹配到的括号里模式的值——price。

上面创建的提取器并不是可重用的。它会寻找股票代码"GOOG",但如果要找其他股票代码,它就没用了。重用它并不困难。

    def process(input: String): Unit = {
        val MatchStock = """^(.+):(\d*\.?\d+)""".r
        input match {
            case MatchStock("GOOG", price) => println("Price of GOOG is " + price)
            case MatchStock("IBM", price) => println("IBM's trading at " + price)
            case MatchStock(symbol, price) => printf("Price of %s is %s\n", symbol, price)
            case _ => println("not processing " + input)
        }
    }

    def main(args: Array[String]): Unit = {
        process("GOOG:310.84") // Price of GOOD is 310.84
        process("GOOG:310") // Price of GOOD is 310
        process("IBM:84.01") // IBM's trading at 84.01
        process("GE:15.96") // Price of GE is 15.96
    }

上面的例子里,这个正则表达式匹配一个字符串,这个字符串以任意字符串或数字开头,跟着一个冒号,然后是一个正的带小数的十进制数。生成unapply()方法会把":"前面的部分和后面的部分作为两个单独模式变量返回。这样一来,既可以匹配特定的股票,比如GOOG和IBM,也可以接收传进来的任意股票代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值