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?
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
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)
Try creating and filtering the new list with your condition:
List[T](t1, t2, t3, t4).filter(p)
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)
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