Better version of "iterate over Seq or if empty" in scala?
Asked Answered
B

5

13

Is there a shorter/better way to do the following :

mySeq.map { elmt => 
    // do stuff
}   

if (mySeq.isEmpty) {
    // some other stuff
}

Ps : I'm using PlayFramework and this is meant to be used in templates, so if there are any "helpers" there I missed, I would be glad to discover those ;)

Barrett answered 21/8, 2012 at 8:31 Comment(2)
what is wrong with if (seq.isEmpty) ... else seq.map(...)?Staurolite
Nothing actually, I was simply wondering if there were some other ways of doing it.Barrett
H
16

How about this?

mySeq.headOption.map { _ =>
  mySeq.map { elmt => 
    // do stuff
  }
}.getOrElse {
  // some other stuff
}
Hello answered 21/8, 2012 at 8:56 Comment(1)
Not entirely compact is it :-)Consubstantial
L
7

You can use match:

l match {
  case l if !l.isEmpty => l.map{ // do stuff }
  case _ => // some other stuff
}

For List:

l match {
  case h :: t => l.map{ // do stuff }
  case _ => // some other stuff
}

Alternatively you can define your own method:

import scala.collection.generic.CanBuildFrom
import scala.collection.TraversableLike

class FoldEmpty[T, S[T] <: TraversableLike[T, S[T]]](l: S[T]){
  def foldEmpty[B, That](notEmpty: T => B, empty: => That)(implicit cbf: CanBuildFrom[S[T], B, That]): That = 
    l match {
      case t if !t.isEmpty => l map notEmpty
      case _ => empty
    }
}

implicit def seqToFoldEmpty[T, S[T] <: TraversableLike[T, S[T]]](l: S[T]) = new FoldEmpty(l)

Usage:

scala> IndexedSeq(1, 2, 3).foldEmpty( _ + 1 , IndexedSeq(-1))
res0: IndexedSeq[Int] = Vector(2, 3, 4)

scala> IndexedSeq[Int]().foldEmpty( _ + 1 , Seq(-1))
res1: Seq[Int] = List(-1)
Leckie answered 21/8, 2012 at 8:43 Comment(2)
case h :: l will match only if l is a List.Hello
If you match the empty case first, you won’t need the if guard…Mayramays
B
4

I recently gisted a helper that generates some HTML only if the given sequence is non-empty. Put this slight variation in a file, e.g. Helpers.scala:

package views.html.helper

import play.api.templates.Html

object nonEmptyOrElse {
  def apply[T <: Seq[_]](t: T)(nonEmptyBlock: (T) => Html)(emptyBlock: => Html) = {
    if (t.nonEmpty) nonEmptyBlock(t) else emptyBlock
  }
}

And use it like this in your template:

@nonEmptyOrElse(mySeq) { seq =>
  //doSomething with entire seq
} {
  // do something else
}

Edit: And here is the version the maps each element:

object mapOrElse {
  def apply[T](t: Seq[T])(nonEmptyBlock: (T) => Html)(emptyBlock: => Html) = {
    if (t.nonEmpty) t.map(nonEmptyBlock(_)) else emptyBlock
  }
}
Bairn answered 21/8, 2012 at 10:13 Comment(2)
+1, indirect generics in Play non-generics-supported templates, niceDictatorial
Nice stuff. Might end up doing something like that indeed.Barrett
L
2

Having the following simple extension in scope:

(for Scala 2.10):

implicit class AnyExtensions[A] ( val x : A ) extends AnyVal {
  def asSatisfying(p: A => Boolean): Option[A] =
    if (p(x)) Some(x) else None
}

(for Scala 2.9):

implicit def anyExtensions[A] (x : A) = new {
  def asSatisfying(p: A => Boolean): Option[A] =
    if (p(x)) Some(x) else None 
}

you'll be able to rewrite your code as follows:

mySeq
  .asSatisfying{_.nonEmpty}
  .map{
    _.map{elmt => 
      // do stuff
    }
  }
  .getOrElse{
    // some other stuff
  }

In my practice this extension turned out to be applicable in a lot of cases and very useful. It excels in situations when you realize you need an if statement in a middle of an expression, which without this extension would have required you to introduce a temporary variable. Here's an example:

List(1, 2, 3).mkString(", ").asSatisfying{_.nonEmpty}.getOrElse("Empty list")

It will result in a String 1, 2, 3 and would have resulted in a String Empty list if the list was empty.

Lowercase answered 21/8, 2012 at 13:8 Comment(2)
AFAIK, will work only in 2.10 (because of implicit classes)Yamada
@Yamada Oh yeah, forgot to mention that. Thanks. UpdatedLowercase
P
0

Here's a useful value class converts Seq[A] to None if the Seq.isEmpty, otherwise wraps it in a Some[Seq[A]].

for Scala 2.10:

  /**
   * A value type class to add some useful utility methods to Seq
   *
   * @param underlying The Seq to augment.
   */
 implicit class SeqExt[+A](private val underlying: Seq[A]) extends AnyVal {

   /** If this Seq is empty, returns None, otherwise builds a new collection by
     * applying a function to all elements of this immutable sequence, and wraps
     * the Seq in an Option.
     *
     * @tparam B   The element type of the returned collection.
     * @param f    The function to apply to each element.
     * @return     If this Seq is empty, returns None, otherwise builds a new collection by
     *             applying a function to all elements of this immutable sequence, and wraps 
     *             the Seq in an Option.
     */
   def mapOption[B](f: (A) ⇒ B): Option[Seq[B]] = {
     if(underlying.isEmpty) None else Some(underlying.map(f))
   }
 }

Usage (as in your example):

mySeq
  .mapOption { elmt => 
    // do stuff to each element if mySeq.nonEmpty
  } 
  .getOrElse {
      // some other stuff if mySeq.isEmpty
  }
Peculiarize answered 1/8, 2019 at 15:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.