В Scala, возможно ли собрать коллекцию частичных функций в функцию, перебирая ListMap? - PullRequest
0 голосов
/ 30 октября 2019

Вчера @Krzysztof Atł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"
  )

Теперь я пытаюсь понять, как преобразовать эту карту в серию парциальных функций.

Я думал, что это будет что-то вроде:

 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)
  }

Но этоне компилируется.

Есть идеи, как сделать эту работу?

Спасибо!

1 Ответ

1 голос
/ 30 октября 2019

Вот основная идея о том, как построить функцию.
(Вам все еще нужно адаптировать ее к своему случаю использования)

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()
    )
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...