Scala - 使用更高级别的方法缩短代码段

Scala - shortening piece of code with higher level methods

如何使用更高级别的方法(如 maptakeWhilefilter 等)缩短以下代码片段(toText 方法):

/** Returns the text that is produced by this keypad from the given multi-tap input. 
  * For instance, the input `"33 3338881330000"` produces `"HIYAH!"`. The given string
  * is assumed to consist of digits and spaces only.
  */
def toText(keysPressed: String): String = {
 val pieces = keysPressed.split(" ")
 var result = ""                        // gatherer
 for (currentPiece <- pieces) {         // most-recent holder
   var remaining = currentPiece         // gatherer 
   while (remaining.nonEmpty) {                    
     val copyCount = countInitialCopies(remaining) // temporary
     result += charFor(remaining(0), copyCount)
     remaining = remaining.drop(copyCount)
   }
 }
 result
} 

其中:

/** Returns the character produced by pressing the given number key (from '0' to '9')
  * the given number of times on this keypad. If a key is pressed more times than there
  * are characters assigned to the key, the result "wraps around".
  */
def charFor(keyPressed: Char, timesPressed: Int): Char = {
  val charactersForKeys = Vector(" .,!?", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ", "ÅÄÖ")
  val key = charactersForKeys(keyPressed.asDigit)
  key((timesPressed-1) % key.length)
}

和:

/** Determines the first letter of the given string, and returns the number of times 
  * the letter occurs consecutively at the beginning of the string. The given
  * string must have at least one character.
  */
def countInitialCopies(str: String): Int = str.takeWhile(_ == str(0)).length

我尝试执行以下操作,但没有成功:

def toText(keysPressed: String): String = keysPressed.split(" ").foldLeft("")(charFor(_(0), countInitialCopies(_)))

不确定它是否真的更短,但这是一个:

def toText(keysPressed: String): String = {
  def split(seq: Seq[Char]): Stream[Seq[Char]] = {
    if (seq.isEmpty)
      Stream.empty
    else {
      val lr = seq.span(ch => ch == seq.head)
      Stream.cons(lr._1, split(lr._2))
    }
  }

  split(keysPressed)
    .filter(s => s.head.isDigit) // filter out spaces between different series of the same digits
    .map(s => charFor(s.head, s.length))
    .mkString("")
}

这段代码背后的想法是:

  1. 首先以类似于 countInitialCopies 的方式将 keysPressed(由于隐式 scala.collection.immutable.StringOps 而被视为 Seq[Char])拆分为 Stream[Seq[Char]](注意cons 第二个参数是对 Seq 其余参数的 "recursive"(实际上是延迟的)调用!)
  2. 然后过滤掉 Seq[Char] 来自空间的显式组分离,
  3. 然后 map 使用您的 charFor
  4. 过滤 Stream[Seq[Char]]
  5. 最终将Stream的结果累加成String

这里有一个稍微不同的方法来解决这个问题。使用 takeWhile(),但没用太多。

val charsForKeys = Vector(" .,!?", "ABC", "DEF", "GHI", "JKL", "MNO", "PQRS", "TUV", "WXYZ")

def toText(keysPressed: String): String = {
  if (keysPressed.isEmpty) ""
  else {
    val kpHead = keysPressed.head
    val kpStr  = keysPressed.takeWhile(_ == kpHead)
    val kpLen  = kpStr.length
    if (kpHead.isDigit)
      charsForKeys(kpHead.asDigit)(kpLen-1) + toText(keysPressed.drop(kpLen))
    else
      toText(keysPressed.drop(kpLen))  //not a digit, skip these chars
  }
}

toText("33 3338881330000")  //res0: String = HIYAH!

第二次,更短的尝试。使用 foldRight()collect().

def toText(keysPressed: String): String = {
  keysPressed.foldRight(List[String]()){
    case (c, s::ss) => if (c == s.head) c+s :: ss
                       else c.toString :: s :: ss
    case (c, Nil)   => List(c.toString)
  }.collect{
    case s if s.head.isDigit => charsForKeys(s.head.asDigit)(s.length-1)
  }.mkString
}