Scala:能否在unapplySeq内嵌套unapply处理数字转英文表述?
在Scala的unapplySeq中嵌套unapply的实现方案
当然可以在Scala的unapplySeq里嵌套使用unapply啦!这刚好能解决你处理11-19这类特殊十几位数的痛点,咱们结合你的数字转英文场景一步步实现。
核心思路
unapply是用于提取固定数量元素的提取器,unapplySeq则处理可变数量的元素。嵌套的本质就是在unapplySeq的模式匹配流程中,调用其他提取器(即unapply方法)来复用逻辑、处理特殊分支——比如你遇到的11-19这类无法用普通十位+个位逻辑转换的数字。
步骤1:先实现十几位数的专属提取器
首先针对11-19的特殊情况,写一个单独的unapply提取器,专门处理这类数字的转换:
import scala.util.matching.Regex object TeenNumber { private val teenRegex: Regex = """^1(\d)$""".r def unapply(numStr: String): Option[String] = numStr match { case teenRegex(digit) => Some(digit match { case "1" => "eleven" case "2" => "twelve" case "3" => "thirteen" case "4" => "fourteen" case "5" => "fifteen" case "6" => "sixteen" case "7" => "seventeen" case "8" => "eighteen" case "9" => "nineteen" }) case _ => None } }
步骤2:在unapplySeq中嵌套调用这个提取器
接下来我们写一个主提取器NumberParser,用unapplySeq处理一位、两位(含十几)、三位数字的转换,过程中嵌套调用TeenNumber.unapply来优先处理特殊情况:
object NumberParser { private val singleDigit: Regex = """^(\d)$""".r private val doubleDigit: Regex = """^(\d)(\d)$""".r private val tripleDigit: Regex = """^(\d)(\d)(\d)$""".r // 定义单数字到英文的映射 private val singleMap = Map( "0" -> "zero", "1" -> "one", "2" -> "two", "3" -> "three", "4" -> "four", "5" -> "five", "6" -> "six", "7" -> "seven", "8" -> "eight", "9" -> "nine" ) // 定义十位数字到英文的映射 private val tensMap = Map( "2" -> "twenty", "3" -> "thirty", "4" -> "forty", "5" -> "fifty", "6" -> "sixty", "7" -> "seventy", "8" -> "eighty", "9" -> "ninety" ) def unapplySeq(numStr: String): Option[Seq[String]] = numStr match { // 优先匹配十几位数(嵌套调用TeenNumber的unapply) case TeenNumber(teenStr) => Some(Seq(teenStr)) // 处理一位数 case singleDigit(d) => singleMap.get(d).map(Seq(_)) // 处理普通两位数(非十几) case doubleDigit(tens, ones) => val tensPart = tensMap.getOrElse(tens, "") val onesPart = if (ones == "0") "" else singleMap(ones) val parts = Seq(tensPart, onesPart).filter(_.nonEmpty) Some(parts) // 处理三位数,这里嵌套处理后两位的逻辑 case tripleDigit(hundreds, tens, ones) => val hundredPart = s"${singleMap(hundreds)} hundred" // 对后两位数字,再次尝试匹配十几位数的提取器 val tensOnesPart = s"$tens$ones" match { case TeenNumber(teen) => teen case doubleDigit(t, o) => val tPart = tensMap.getOrElse(t, "") val oPart = if (o == "0") "" else singleMap(o) Seq(tPart, oPart).filter(_.nonEmpty).mkString(" ") case _ => "" } // 拼接百位和后两位,注意"and"的逻辑 val finalParts = if (tensOnesPart.nonEmpty) { Seq(hundredPart, s"and $tensOnesPart") } else { Seq(hundredPart) } Some(finalParts) // 处理其他无法匹配的情况 case _ => None } }
步骤3:使用提取器完成数字转换
最后用这个提取器来实现你的numberToString方法:
def numberToString(num: Int): String = num.toString match { case NumberParser(parts@_*) => parts.mkString(" ") case _ => "unknown number" } // 测试一下效果 println(numberToString(101)) // 输出:one hundred and one println(numberToString(15)) // 输出:fifteen println(numberToString(23)) // 输出:twenty three println(numberToString(319)) // 输出:three hundred and nineteen println(numberToString(500)) // 输出:five hundred
为什么这样可行?
Scala的模式匹配会按顺序尝试每个case,我们把TeenNumber的匹配放在最前面,就能优先处理11-19的特殊情况;在处理三位数的后两位时,又嵌套调用了TeenNumber的提取器,确保特殊情况不会被普通两位数逻辑覆盖。这种嵌套方式不仅解决了你的问题,还让代码逻辑更模块化,方便后续扩展更多数字规则。
内容的提问来源于stack exchange,提问作者Westwater
相关产品推荐
相关产品推荐

