开发者

how to make this code functional?

i have written a simple script for transforming the c-style identifier names (for example, invoice_number) to java-style ones (like, 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 would like to know how i can make this code functional. i can't think of any higher order function to replace the "increment i by 2 if <some-condition> else increment by 1" logic. thanks.


Ok, here is my way to do it.

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)

Think it's in reasonable functional style. Interesting how scala gurus will solve this problem :)


I apologize for not using Scala, but the basic idea should translate, so here's how I'd write the essential logic in Haskell:

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

convertName :: String -> String
convertName = foldr capitalize ""

We have two pieces: A function that uppercases the first character of a string when given an underscore, or if given anything else prepends it to the string. Then we just use this in a right fold over the sequence of input characters, with the empty string as the base case.

Note that default strings in Haskell are lazy sequences of characters, which may not be the case in Scala, but I would expect something similar to be possible, since the functional side of Scala comes from the same general ML-inspired tradition that Haskell does.


EDIT: Incidentally, notice that contrary to what many functional programmers might expect, my implementation is not tail recursive, and this is both intentional and correct. Instead it does the recursive call in the tail of the list, and since data constructors in Haskell let things be lazy, each output character is generated on demand with the rest of the fold lazily deferred, and the whole thing runs in constant stack space. The end result is essentially an iterative loop consuming elements from an input stream, but written to look like a simple recursive function.

Even though you wouldn't do things this way in the general case except in Haskell, lazy lists/generators/etc. are common in many languages these days, and the idiom of "consume a finite amount, process it, produce output" for transforming such streams is language-agnostic.

Also, my appreciation to Antoras and Luigi Plinge for writing Scala implementations with similar algorithms--helps me get a better feel for Scala, which I'm only passingly familiar with currently.


Another solution; using sliding iterator window:

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


I translated the Haskell code of C. A. McCann to 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

Because String is a IndexedSeq in Scala, appending to it takes effectively constant time. The code of Luigi can be updated to something like that:

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


My tail recursive stab:

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

For some reason "string".toSeq doesn't match, but .toList does; maybe someone can explain why.

Edit: or this works too:

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


Of course, you could just use regex:

import util.matching.Regex.Match

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

edit: or if you use an anonymous function it's a bit shorter and you don't need the import (point-free for kicks):

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜