Scala:如何将每隔一个字母大写

Scala: How to uppercase every other letter

我在面试中被问到这个问题。她想要 scala 中的答案,而不是 java。 我的回答是以 java 为中心的。请注意,您不能使用字符索引来决定是大写还是小写。你能找到一个更好的方法让它以 scala 为中心吗?我想不出有什么 StringOps 内置函数可以帮助解决这个问题。专注于消除对 var 的需求。我使用尾递归(之后)重写了它,但答案效率低下,所以我坚持这个 java 中心答案。

object Cap extends App {
  def _capitalizeEveryOtherLetter(str: String): String = {
    val s2: Array[Char] = str.toCharArray
    var capitalize: Boolean = true
    val arr: Array[Char] = (for (c <- s2)  yield  {
      if (!Character.isLetter(c)) {
        c
      } else if (capitalize) {
        capitalize = false
        val newChar = Character.toUpperCase(c)
        newChar
      } else {
        capitalize = true
        val newChar = Character.toLowerCase(c)
        newChar
      }
    })
    new String(arr)
  }
  val s1: String = "*7yTuu(i&^giuisKJSU&Y"
  println(s"${_capitalizeEveryOtherLetter(s1)}")
}

预期输出是

*7YtUu(I&^gIuIsKjSu&Y

相似但不相同,因为它依赖于索引,而索引对此问题无效。

由于字符串操作很昂贵,我只使用 StringBuilderfoldLeft

def capitalizeEveryOtherLetter(str: String): String = {
  val sb = new StringBuilder(str.size)
  
  str.foldLeft(false) {
    case (flag, char) =>
      val isLetter = char.isLetter
      if (isLetter && flag) {
        sb += char.toUpper
        false
      } else {
        sb += char.toLower
        isLetter || flag
      }
  }
  
  sb.result()
}

可以看到代码运行 here.

如果你只有字母,scala 的方式就像

input.grouped(2).map(_.capitalize).mkString("")

跳过 non-letter 个字符会使它变得更复杂:首先弄清楚每个字母应该如何转换,然后替换它们:

val letters = input.filter(_.isLetter).grouped(2).flatMap(_.capitalize)
input.map { c => if (c.isLetter) letters.next else c }

但这对于面试来说可能太“聪明”了:) (好吧,取决于你在哪里面试)。

“traverse/transform with state”的“官方”scala 方法是(尾)递归或(对于更简单的情况)foldLeft 使用累加器。避免可变性和将事物附加到另一个答案中提到的字符串或集合的低效率的技巧是向后构建列表,并在最后反转它。

input.foldLeft[(Boolean, List[Char])](true -> Nil) { 
  case ((x, ac), c) if !c.isLetter => (x, c::ac)
  case ((true, ac), c) => (false, c.toUpper :: ac)
  case ((_, ac), c) => (true, c :: ac)
}._2.reverse.mkString("")

由于你的问题被标记为 tail-recursion,这里有一个 tail-recursive 的方法(它与上面的 foldLeft 并没有什么不同):

   @tailrec
   def upSome(
       chars: List[Char], 
       state: Boolean = true, 
       result: List[Char]
   ): List[Char] = (state, chars) match { 
     case (_, Nil) => result.reverse
     case (_, c::tail) if !c.isLetter => upSome(tail, state, c::result)
     case (true, c::tail) => upSome(tail, false, c::result)
     case (_, c::tail) => upSome(tail, true, c::result)
   }

您可以将其用作 upSome(input.toList).mkString("")

另一个(功能更强大?),它避免了保持“我们是小写还是大写”状态的需要:

val xs  = "the quick brown fox jumps over the lazy dog";

val fs = 
 LazyList.continually(LazyList((c:Char) =>c.toUpper, (c:Char)=>c.toLower)).flatten;


(xs zip fs).map{case (c, f) => f(c)}.mkString

虽然我 mis-interpreted 可能是问题的意图 - 这会交替大写和小写,而不考虑它是否是字母。我离开它是因为它是一种有趣的替代方法