Yann Moisan
Yann Moisan

Reputation: 8281

loop until a condition stands in scala

I'd like to write a generic loop until a given condition stands, in a functional way.

I've came up with the following code :

def loop[A](a: A, f: A => A, cond: A => Boolean) : A =
  if (cond(a)) a else loop(f(a), f, cond)

What are other alternatives ? Is there anything in scalaz ?

[update] It may be possible to use cats and to convert A => A into Reader and afterwards use tailRecM. Any help would be appreciated.

Upvotes: 2

Views: 5971

Answers (2)

marios
marios

Reputation: 8996

From your structure, I think what you are describing is closer to dropWhile than takeWhile. What follows is 100% educational and I don't suggest that this is useful or the proper way to solve this problem. Nevertheless, you might find it useful.

If you want to be generic to any container (List, Array, Option, etc.) You will need a method to access the first element of this container (a.k.a. the head):

trait HasHead[I[_]]{
   def head[X](of: I[X]): X
}

object HasHead {
   implicit val listHasHead = new HasHead[List] {
       def head[X](of: List[X]) = of.head
   }

   implicit val arrayHasHead = new HasHead[Array] {
       def head[X](of: Array[X]) = of.head
   }

   //...
}

Here is the generic loop adapted to work with any container:

def loop[I[_], A](
   a: I[A], 
   f: I[A] => I[A], 
   cond: A => Boolean)(
   implicit 
      hh: HasHead[I]): I[A] = 
   if(cond(hh.head(a))) a else loop(f(a), f, cond)

Example:

loop(List(1,2,3,4,5), (_: List[Int]).tail, (_: Int) > 2)
> List(3, 4, 5)

Upvotes: 1

Dylan
Dylan

Reputation: 13859

I agree with @wheaties's comment, but since you asked for alternatives, here you go:

You could represent the loop's steps as an iterator, then navigate to the first step where cond is true using .find:

val result = Iterator.iterate(a)(f).find(cond).get

I had originally misread, and answered as if the cond was the "keep looping while true" condition, as with C-style loops. Here's my response as if that was what you asked.

val steps = Iterator.iterate(a)(f).takeWhile(cond)

If all you want is the last A value, you can use steps.toIterable.last (oddly, Iterator doesn't have .last defined). Or you could collect all of the values to a list using steps.toList.

Example:

val steps = Iterator.iterate(0)(_ + 1).takeWhile(_ < 10)
// remember that an iterator is read-once, so if you call .toList, you can't call .last
val result = steps.toIterable.last
// result == 9

Upvotes: 3

Related Questions