I have a recursive function that I am trying to make @tailrec
by having the inner, recursive part (countR3
) add elements to a queue (agenda
is a scala.collections.mutable.Queue
). My idea is to then have the outer part of the function fold over the agenda and sum up the results.
NOTE: This was a homework problem, thus I don't want to post the whole code; however, making the implementation tail-recursive was not part of the homework.
Here is the portion of the code relevant to my question:
import scala.collection.mutable.Queue
val agenda: Queue[Tuple2[Int, List[Int]]] = Queue()
@tailrec
def countR3(y: Int, x: List[Int]): Int = {
if (y == 0) 1
else if (x.isEmpty) 0
else if …
else {
agenda.enqueue((y - x.head, x))
countR3(y, x.tail)
}
}
⋮
agenda.enqueue((4, List(1, 2)))
val count = agenda.foldLeft(0) {
(count, pair) => {
val mohr = countR3(pair._1, pair._2)
println("count=" + count + " countR3=" + mohr)
count + mohr
}
}
println(agenda.mkString(" + "))
count
This almost seems to work… The problem is that it doesn't iterate over all of the items added to the agenda, yet it does process some of them. You can see this in the output below:
count=0 countR3=0
count=0 countR3=0
count=0 countR3=0
(4,List(1, 2)) + (3,List(1, 2)) + (2,List(2)) + (2,List(1, 2)) + (1,List(2)) + (0,List(2))
[Of the six items on the final agenda, only the first three were processed.]
I'm generally well-aware of the hazards of mutating a collection while you're iterating over it in, say, Java. But a Queue is kind of a horse of a different color. Of course, I understand I could simply write an imperative loop, like so:
var count = 0
while (!agenda.isEmpty) {
val pair = agenda.dequeue()
count += countR3(pair._1, pair._2)
}
This works perfectly well, but this being Scala, I am exploring to see if there is a more functionally elegant way.
Any suggestions?
foldLeft() method is a member of TraversableOnce trait, it is used to collapse elements of collections. It navigates elements from Left to Right order. It is primarily used in recursive functions and prevents stack overflow exceptions.
In Scala, we can use foldLeft and foldRight methods for collection types like List . Both methods recursively combine items into another item. foldLeft combines items from left one to right one, on the other hand foldRight does this from right one to left one.
Scala has a rich set of collection library. Collections are containers of things. Those containers can be sequenced, linear sets of items like List, Tuple, Option, Map, etc. The collections may have an arbitrary number of elements or be bounded to zero or one element (e.g., Option).
Although probably not entirely idiomatic, you could try this:
Stream.continually({ if (agenda.isEmpty) None else Some(agenda.dequeue()) }).
takeWhile(_.isDefined).flatten.
map({ case (x, y) => countR3(x, y) }).
toList.sum
Stream.continually({ if (agenda.isEmpty) None else Some(agenda.dequeue()) })
gives you an infinite stream of queue items wrapped in Option[Tuple2[Int, List[Int]]]
. takeWhile(_.isDefined)
cuts off the sequence as soon as the first None
item is encountered, i.e. when the queue is exhausted.Option
s, we need to unwrap them with flatten
.map({ case (x, y) => countR3(x, y) })
basically applies your function to each item.toList
forces the evaluation of a stream (that's what we were working with) and then sum
calculates the sum of list's items.I think the problem with agenda.foldLeft
(that it processes only 'some' enqueued items) is I'd guess that it takes a (probably immutable) list of currently enqueued items, and therefore isn't affected by items that were added during the calculation.
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