在Scala中,是否可以通过遍历ListMap将部分函数的集合构建为一个函数?

问题描述 投票:0回答:1

昨天,@ KrzysztofAtłasik帮助我弄清楚了如何通过使用部分函数来减少匹配中的冗余,所以以前看起来像这样:

  i match {
      case x if x == 0 ⇒
        romanNumeral

      case x if x >= 1000 ⇒
        this.roman(i - 1000, s"${romanNumeral}M")

      case x if x >= 900 ⇒
        this.roman(i - 900, s"${romanNumeral}CM")

      // etc.

现在看起来像:

object RomanNumerals {
  def roman(i: Int)(implicit romanNumeral: String = ""): String =
    this.tryRoman(romanNumeral)
      .orElse(this.tryRoman(1000, "M", romanNumeral))
      .orElse(this.tryRoman(900, "CM", romanNumeral))
      .orElse(this.tryRoman(500, "D", romanNumeral))
      .orElse(this.tryRoman(400, "CD", romanNumeral))
      .orElse(this.tryRoman(100, "C", romanNumeral))
      .orElse(this.tryRoman(90, "XC", romanNumeral))
      .orElse(this.tryRoman(50, "L", romanNumeral))
      .orElse(this.tryRoman(40, "XL", romanNumeral))
      .orElse(this.tryRoman(10, "X", romanNumeral))
      .orElse(this.tryRoman(9, "IX", romanNumeral))
      .orElse(this.tryRoman(5, "V", romanNumeral))
      .orElse(this.tryRoman(4, "IV", romanNumeral))
      .orElse(this.tryRoman(1, "I", romanNumeral))
      .apply(i)

  private def tryRoman(romanNumeral: String = ""): PartialFunction[Int, String] = {
    case value if value == 0 => romanNumeral
  }

  private def tryRoman(
                        upperGuard: Int,
                        token: String,
                        romanNumeral: String
                      ): PartialFunction[Int, String] = {
    case value if value >= upperGuard =>
      this.roman(value - upperGuard)(s"$romanNumeral$token")
  }
}

好的,它更简洁,更干燥,但我想我想更进一步。

我已将所有值放入ListMap中,例如:

  val romanNumeralByValue: ListMap[Int, String] = ListMap(
    1000 → "M",
    900 → "CM",
    500 → "D",
    400 → "CD",
    100 → "C",
    90 → "XC",
    50 → "L",
    40 → "XL",
    10 → "X",
    9 → "IX",
    5 → "V",
    4 → "IV",
    1 → "I"
  )

现在,我试图弄清楚如何将该地图转换为一系列parial函数。

我以为是这样的:

 def roman(i: Int)(implicit romanNumeral: String = ""): String = {
    romanNumeralByValue.reduce(tryRoman){
      case (keyvalue, accumulator) ⇒
        accumulator
          .orElse(this.tryRoman(keyvalue._1, keyvalue._2, romanNumeral))
    }.apply(i)
  }

但是无法编译。

任何想法如何使这项工作?

谢谢!

scala recursion dry partialfunction ordered-map
1个回答
1
投票
这里是如何构建函数的基本思想。

(您仍然需要使它适应您的用例)

def checkLowerLimit(lowerLimit: Int, result: String): PartialFunction[Int, String] = { case value if (value >= lowerLimit) => result } val limits: ListMap[Int, String] = ListMap( 10 -> "ten", 0 -> "zero" ) val foo: PartialFunction[Int, String] = limits.map((checkLowerLimit _).tupled).reduce { (acc, f) => acc.orElse(f) }
您可以像这样进行测试:

foo(11) // res: String = "ten" foo(10) // res: String = "ten" foo(3) // res: String = "zero" foo(-1) // scala.MatchError: -1 (of class java.lang.Integer)


编辑

将技术应用于问题。

import scala.collection.immutable.ListMap import scala.collection.mutable.StringBuilder object RomanNumerals { private val romanNumeralByValue: ListMap[Int, String] = ListMap( 1000 → "M", 900 → "CM", 500 → "D", 400 → "CD", 100 → "C", 90 → "XC", 50 → "L", 40 → "XL", 10 → "X", 9 → "IX", 5 → "V", 4 → "IV", 1 → "I" ) private val tryRomanStep: (Int, String) => PartialFunction[Int, (Int, String)] = (upperLimit, result) => { case value if (value >= upperLimit) => upperLimit -> result } private val tryRoman: PartialFunction[Int, (Int, String)] = romanNumeralByValue.map(tryRomanStep.tupled).reduce { (acc, f) => acc.orElse(f) } def roman(i: Int): String = { @annotation.tailrec def loop(remainingValue: Int, acc: StringBuilder): String = if (remainingValue == 0) acc.result() else { val (removedValue, newToken) = tryRoman(remainingValue) loop(remainingValue - removedValue, acc.append(newToken)) } loop( remainingValue = i, acc = new StringBuilder() ) } }

© www.soinside.com 2019 - 2024. All rights reserved.