让玩具类Counter
如:
class Counter private( val next: Int, val str2int: Map[String,Int] ) {
def apply( str: String ): (Int,Counter) = str2int get str match {
case Some(i) => ( i, this )
case None => ( next, new Counter( next+1, str2int + (str -> next) ) )
}
}
object Counter {
def apply() = new Counter( 0, Map() )
}
该类提供String和自然数之间的映射,每次查询一个新String时,映射被惰性扩展。
然后,我可以编写一个方法,将Seq of string转换为Seq of int,在遍历过程中更新映射。我得到的第一个实现是foldLeft
:
def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
strs.foldLeft( (Seq[Int](), counter) ) { (result, str) =>
val (i, nextCounter) = result._2( str )
( result._1 :+ i, nextCounter )
}
按预期工作:
val ss = Seq( "foo", "bar", "baz", "foo", "baz" )
val is = toInt( ss, Counter() )._1
//is == List(0, 1, 2, 0, 2)
但是我对toInt
的实现不是很满意。问题是我在两个不同的值上折叠。是否有函数式编程模式来简化实现?
您正在寻找的模式是State
单子:
import scalaz._
import Scalaz._
case class Counter(next: Int = 0, str2int: Map[String,Int] = Map()) {
def apply( str: String ): (Counter, Int) = (str2int get str) fold (
(this, _),
(new Counter(next+1, str2int + (str -> next)), next)
)}
type CounterState[A] = State[Counter, A]
def count(s: String): CounterState[Int] = state(_(s))
def toInt(strs: Seq[String]): CounterState[Seq[Int]] =
strs.traverse[CounterState, Int](count)
这里的类型注释是不幸的,也许可以以某种方式消除它。无论如何,这里是它的运行:
scala> val ss = Seq( "foo", "bar", "baz", "foo", "baz" )
ss: Seq[java.lang.String] = List(foo, bar, baz, foo, baz)
scala> val is = toInt(ss) ! Counter()
is: Seq[Int] = List(0, 1, 2, 0, 2)
你可以通过更多的模式匹配来让你得到的折叠看起来更漂亮:
strs.foldLeft((Seq[Int](), counter)) { case ((xs,counter), str) =>
val (i, nextCounter) = counter(str)
(xs :+ i, nextCounter)
}
如果您在某处定义了管道操作符|>
,并且您对foldLeft
的/:
别名感到满意,则可以将其设置为
((Seq[Int](), counter) /: strs) { case ((xs,counter), str) =>
counter(str) |> { case (i,nextCounter) => (xs +: i, nextCounter) }
}
,一旦你熟悉了语法,它是紧凑的和可读的。
我认为状态单子是你正在寻找的。
在两个值上折叠没有问题。可以稍微改进一下:
import scalaz._
import Scalaz._
def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
strs.foldLeft( (Seq[Int](), counter) ) { case ((xs, counter), str) =>
counter(str).mapElements(xs :+ _, identity)
}
或者,如果你愿意,
def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
strs.foldLeft( (Seq[Int](), counter) ) { case ((xs, counter), str) =>
(xs :+ (_: Int)) <-: counter(str)
}
,
def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
strs.foldLeft( (Seq[Int](), counter) ) { case (result, str) =>
result.fold((xs, counter) =>
counter(str).mapElements(xs :+ _, identity))
}