Let the toy-class Counter
such as:
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() )
}
This class provides a mapping between a String and a natural number, the mapping is extended lazily each time a new String is queried.
I can then write a method which can convert a Seq of Strings in a Seq of Ints, updating the mapping during traversal. The first implementation I got is with 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 )
}
This works as intended:
val ss = Seq( "foo", "bar", "baz", "foo", "baz" )
val is = toInt( ss, Counter() )._1
//is == List(0, 1, 2, 0, 2)
But I am not very satisfied about toInt
implementation. The problem is that I am folding on two different values. Is there a functional programming pattern to simplify the implementation ?
The pattern you're looking for is the State
monad:
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)
The type annotation there is unfortunate, and maybe it can be eliminated somehow. Anyway, here's a run of it:
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)
You can make the fold you've got look quite a bit nicer by doing more pattern matching:
strs.foldLeft((Seq[Int](), counter)) { case ((xs,counter), str) =>
val (i, nextCounter) = counter(str)
(xs :+ i, nextCounter)
}
And then if you have the pipe operator |>
defined somewhere, and you're comfortable with the /:
alias for foldLeft
, you can make that
((Seq[Int](), counter) /: strs) { case ((xs,counter), str) =>
counter(str) |> { case (i,nextCounter) => (xs +: i, nextCounter) }
}
which is, once you're familiar with the syntax, compact and readable.
I think the state monad is what you're looking for.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With