【问题标题】:how to make this code functional?如何使这段代码起作用?
【发布时间】:2011-10-29 00:49:52
【问题描述】:

我编写了一个简单的脚本,用于将 c 样式的标识符名称(例如,invoice_number)转换为 java 样式的名称(例如,invoiceNumber)。

val files = Vector("file1", "file2")
for (file <- files) {
  val in = io.Source.fromFile(file).mkString
  var out = ""
  var i = 0
  while (i < in.length) {
    val c = in(i)
    if (c == '_') {
      out += in(i + 1).toUpper
      i += 2
    } else {
      out += c
      i += 1
    }
  }
  val writer = new PrintWriter(file + "1")
  writer.write(out)
  writer.flush()
  writer.close()
}

我想知道如何使这段代码发挥作用。我想不出任何高阶函数来替换“如果 将 i 增加 2,否则增加 1”逻辑。谢谢。

【问题讨论】:

    标签: scala functional-programming


    【解决方案1】:

    好的,这是我的方法。

    val in = "identifier" :: "invoice_number" :: "some_other_stuff" :: Nil
    val out = in map(identifier => {
      val words = identifier.split("_")
      val tail = words.tail.map(_.capitalize)
      (words.head /: tail)(_ + _)
    })
    
    println(in)
    println(out)
    

    认为它具有合理的功能风格。有趣的是 scala 大师将如何解决这个问题:)

    【讨论】:

    • +1 对我来说看起来很合理,虽然我会 words.head + words.tail.map(_.capitalize).mkString 而不是最后两行。
    【解决方案2】:

    我很抱歉没有使用 Scala,但基本思想应该翻译,所以这是我在 Haskell 中编写基本逻辑的方式:

    capitalize :: Char -> String -> String
    capitalize '_' (x:xs) = toUpper x:xs
    capitalize x xs = x:xs
    
    convertName :: String -> String
    convertName = foldr capitalize ""
    

    我们有两个部分:一个函数,它在给定下划线时将字符串的第一个字符大写,或者如果给定任何其他字符,则将其添加到字符串前面。然后我们只是在输入字符序列的右折叠中使用它,以空字符串作为基本情况。

    请注意,Haskell 中的默认字符串是惰性字符序列,这在 Scala 中可能不是这种情况,但我希望类似的事情是可能的,因为 Scala 的功能方面来自与受 ML 启发的相同的一般传统Haskell 可以。


    编辑:顺便提一下,与许多函数式程序员可能期望的相反,我的实现不是尾递归,这是有意的和正确的。相反,它在列表的尾部进行递归调用,并且由于 Haskell 中的数据构造函数让事情变得懒惰,每个输出字符都是按需生成的,其余的折叠延迟延迟,整个事情在恒定的堆栈空间中运行。最终结果本质上是一个从输入流中消耗元素的迭代循环,但写成看起来像一个简单的递归函数。

    即使在一般情况下您不会这样做,除了在 Haskell、惰性列表/生成器/等中。如今在许多语言中都很常见,并且用于转换此类流的“消耗有限数量,处理它,产生输出”的习语与语言无关。

    另外,我感谢 Antoras 和 Luigi Plinge 用类似的算法编写 Scala 实现,这让我对 Scala 有了更好的了解,我目前只是稍微熟悉了一些。

    【讨论】:

      【解决方案3】:

      我的尾递归刺:

      def camel(s: String) = {
        def rec(s: Seq[Char], res: Seq[Char]): String = s match {
          case Nil           => res.reverse.mkString
          case '_' :: x :: t => rec(t, x.toUpper +: res)
          case x :: t        => rec(t, x +: res)
        }
        rec(s.toList, "")
      }
      
      println(camel("hay_guise k_thx_bai")) // hayGuise kThxBai
      

      由于某种原因,"string".toSeq 不匹配,但 .toList 匹配;也许有人可以解释原因。

      编辑:或者这也可以:

      def camel(s:String) = {
        val it = s.iterator
        it.map {
          case '_' => if(it.hasNext) it.next.toUpper else ""
          case x => x
        }.mkString
      }
      

      【讨论】:

      • 这和我的对比很有趣——逻辑本质上是一样的,但是在你使用直接尾递归的地方,我使用了右折叠,这是 not尾递归,但 Haskell 中的惰性意味着它仍然在恒定的堆栈空间中运行。
      • @C. Scala 也有名为 Streams 的惰性列表,但我不确定如何在这里使用它们。但是,我们可以使用Iterator 来创建一个新序列,就像我上面的编辑一样,这大大简化了事情。
      【解决方案4】:

      我将C. A. McCann的Haskell代码翻译成Scala:

      def capitalize: (Char, Seq[Char]) => Seq[Char] = {
        case ('_', Seq(x, xs @ _*)) => x.toUpper +: xs
        case (c, xs) => c +: xs
      }
      
      def convertNumber: String => String =
        _.foldRight(Seq.empty[Char]) { capitalize } mkString
      
      Seq("invoice_number", "ident", "some_other_stuff") map convertNumber foreach println
      

      因为 String 是 Scala 中的 IndexedSeq,所以附加到它实际上需要恒定的时间。 Luigi 的代码可以更新成这样的:

      def capitalize(xs: Seq[Char], ys: Seq[Char]): Seq[Char] = xs match {
        case Seq('_', x, xs @ _*) => capitalize(xs, ys :+ x.toUpper)
        case Seq(x, xs @ _*) => capitalize(xs, ys :+ x)
        case _ => ys
      }
      
      def convertNumber(s: String): String =
        capitalize(s, Seq.empty).mkString
      

      【讨论】:

      • 感谢您的翻译! :] 我个人认为分离出简单的单步函数并将其传递给折叠是比直接递归更好的函数样式,但这是一个口味问题,我的任何一种方式都可以在 Haskell 中轻松完成(啊,我明白了你在我写评论时编辑了你的翻译以使用折叠)。字符串类型的对比也很有趣。
      • 很有帮助 - Seq 的模式匹配中的变量绑定 (@) 是我做错的一件事;这样做我就不必将序列转换为列表来使模式匹配工作。不利的一面是,它使代码看起来像 Perl!
      • 这里有一个错误:String 在 Scala 中不是 IndexedSeq,事实上,附加到它上面是线性超过字符串的大小。跨度>
      • 当使用高阶方法时,String 会隐式转换为 IndexedSeq。它不是真正的 IndexedSeq,这是真的。在方法 capitalize 中,我只使用 Seq 而不是 String 的方法,因此追加需要恒定的时间。附加到 JVM 字符串需要线性时间,你是这个意思吗?
      【解决方案5】:

      当然,你可以只使用正则表达式:

      import util.matching.Regex.Match
      
      def camel(s: String) = "_(.)".r.replaceAllIn(s, (m: Match) => m.group(1).toUpperCase)
      

      编辑: 或者如果您使用匿名函数,它会更短一些,并且您不需要导入(无点踢):

      def camel(s: String) = "_(.)".r replaceAllIn (s, _ group 1 toUpperCase)
      

      【讨论】:

        【解决方案6】:

        另一种解决方案;使用滑动迭代器窗口:

          println(
            (" ".iterator ++ io.Source.fromFile(file)).sliding(2).map {
              s => if (s(0) == '_') s(1).toUpper else s(1)
            }.filter(_ != '_').mkString)
        

        【讨论】:

          猜你喜欢
          • 2022-01-23
          • 2010-09-18
          相关资源
          最近更新 更多