Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala conditional list construction

I'm using Scala 2.9.2, and would like to construct a list based on some conditions.

Consider the following, where cond is some function taking a predicate p and a value of type T (in this case t3):

t1 :: t2 :: cond( p, t3 ) :: t4

The behaviour I want is as follows. If p is true, this should give:

List[T]( t1, t2, t3, t4 )

If p evaluates to false, this should give:

List[T]( t1, t2, t4 )

I'm probably thinking about this completely the wrong way, but I'm struggling to come up with an elegant solution. I could involve Options everywhere and then filter, but that's makes the code rather harder to read:

def cond[T]( p : => Boolean, v : T ) : Option[T] =
{
    p match
    {
        case true => Some( v )
        case false => None
    }
}

This allows the following:

scala> ( Some( 1 ) :: Some( 2 ) :: cond( true, 3 ) :: Some( 4 ) :: Nil ).flatten
res4: List[Int] = List(1, 2, 3, 4)

scala> ( Some( 1 ) :: Some( 2 ) :: cond( false, 3 ) :: Some( 4 ) :: Nil ).flatten
res5: List[Int] = List(1, 2, 4)

However, it's not the most elegant solution, as it requires the user to wrap all of the their non-conditional elements in Some( ) and also to remember to do the flatten at the end. Can anyone think of a more elegant solution?

like image 726
paulmdavies Avatar asked Mar 22 '13 10:03

paulmdavies


4 Answers

As an option you could consider switching to use ListBuffer in Scala.collection.mutable package

val listBuffer = new ListBuffer[<item_type>]
if(<item1_cond>) listBuffer += <item1>
if(<item2_cond>) listBuffer += <item2>

Take note this is still a val (immutable reference) pointing to a mutable collection

like image 133
Ira Avatar answered Oct 14 '22 23:10

Ira


How about yielding a Lists?

@inline def cond[T]( p : => Boolean, v : T ) : List[T] = if(p) v::Nil else Nil

and then using them as this:

List(1,2,3) ++ cond(false, 3 ) ++ List(4)
like image 10
om-nom-nom Avatar answered Oct 21 '22 09:10

om-nom-nom


Try creating and filtering the new list with your condition:

List[T](t1, t2, t3, t4).filter(p)
like image 5
Leonardo Vidal Avatar answered Oct 21 '22 08:10

Leonardo Vidal


So, there's no way this will work with the standard list, because the types are wrong: :: expects an element of type [A >: T] where T is the list type, whereas you want to give it something that might, or might not, produce an element of that type.

However, there's no reason that one shouldn't be able to define a method that is quite happy to take something that only optionally produces a next element. List itself is sealed, so we can't extend it directly, but we can replicate the behaviour we need quite easily:

trait QList[+T] {

  def hd : T
  def tl : QList[T]

  def ?::[A >: T](hd : A) : QList[A] = new ?::[A](hd, this)
  def ?::[A >: T](hd : => Option[A]) : QList[A] = hd match {
    case Some(a) => ?::(a)
    case None => this
  }
}

case object QNil extends QList[Nothing] {
  def hd = throw new Exception("Head of empty list")
  def tl = throw new Exception("Tail of empty list")
}
case class ?::[+T](val hd: T, val tl : QList[T]) extends QList[T]

def cond[T]( p : => Boolean, v : T ) : Option[T] =
{
  p match
  {
    case true => Some( v )
    case false => None
  }
}

val truelist = 1 ?:: 2 ?:: 3 ?:: cond(true, 4) ?:: 5 ?:: QNil
val falselist = 1 ?:: 2 ?:: 3 ?:: cond(false, 4) ?:: 5 ?:: QNil

We basically recreate the list, but imbue it with an overloaded prepend operation that takes a condition.

It's possible to add the ?:: method to the standard list by using an implicit conversion to another class that has the correct method. You mention you're using 2.9.2, which is a shame because otherwise this is the sort of thing that implicit value classes are great for, but we don't need them, they just make things easier:

class ConditionallyAppend[T](tl : List[T]) {
  def ?::[A >: T](hd : A) : List[A] = hd :: tl
  def ?::[A >: T](x : => Option[A]) : List[A] = x match {
    case Some(a) => a :: tl
    case None => tl
  }
}

implicit def ListToConditionallyAppend[A](x : List[A]) = new ConditionallyAppend(x)

And now we can do this:

val truelist = 1 ?:: 2 ?:: 3 ?:: cond(true, 4) ?:: 5 ?:: Nil

truelist: List[Any] = List(1, 2, 3, 4, 5)

And this:

val falselist = 1 ?:: 2 ?:: 3 ?:: cond(false, 4) ?:: 5 ?:: Nil

falselist: List[Any] = List(1, 2, 3, 5)

like image 5
Impredicative Avatar answered Oct 21 '22 08:10

Impredicative