You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:34:37