Scala - 使用更高级别的方法缩短代码段
Scala - shortening piece of code with higher level methods
如何使用更高级别的方法(如 map
、takeWhile
、filter
等)缩短以下代码片段(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("")
}
这段代码背后的想法是:
- 首先以类似于
countInitialCopies
的方式将 keysPressed
(由于隐式 scala.collection.immutable.StringOps
而被视为 Seq[Char]
)拆分为 Stream[Seq[Char]]
(注意cons
第二个参数是对 Seq
其余参数的 "recursive"(实际上是延迟的)调用!)
- 然后过滤掉
Seq[Char]
来自空间的显式组分离,
- 然后
map
使用您的 charFor
过滤 Stream[Seq[Char]]
- 最终将
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
}
如何使用更高级别的方法(如 map
、takeWhile
、filter
等)缩短以下代码片段(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("")
}
这段代码背后的想法是:
- 首先以类似于
countInitialCopies
的方式将keysPressed
(由于隐式scala.collection.immutable.StringOps
而被视为Seq[Char]
)拆分为Stream[Seq[Char]]
(注意cons
第二个参数是对Seq
其余参数的 "recursive"(实际上是延迟的)调用!) - 然后过滤掉
Seq[Char]
来自空间的显式组分离, - 然后
map
使用您的charFor
过滤 - 最终将
Stream
的结果累加成String
Stream[Seq[Char]]
这里有一个稍微不同的方法来解决这个问题。使用 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
}