Which operations should be included in the new collections?

I would like something like Option.when[A](condition: Boolean)(value: => T): Option[A] = if (condition) Some(value) else None. I often find myself doing if (condition) None else Some {something complicated}, which would be nicer as Option.when(condition) {something complicated}.

1 Like

That’s not sufficient though. The map method itself needs to be different for the different cases.

Isn’t it what CC[_] suppose to do?

I would like something like Option.when

there is an open PR on that at Add Option.{when,unless} by japgolly · Pull Request #5995 · scala/scala · GitHub

(but note that Option isn’t a collection, so it’s not really on-topic for this thread…)

@SethTisue Thanks for the pointer.

Not sure if this belongs here but scala.collection.mutable.ArrayOps maybe misses some method like
def getOpt(idx: Int): Option[T]
that would return Some(T) if element at idx exists, None otherwise.

Is there some thing along the lines of

/** foreach loop with counter */
def iForeach(f: (A, Int) => Unit, count: Int = 0): Unit

/** maps over a traversable (collection / sequence) with a counter */
def iMap[B](f: (A, Int) => B, count: Int = 0): Seq[B]

/** flatMaps over a traversable (collection / sequence) with a counter */
   def iFlatMap[B](f: (A, Int) => Seq[B], count: Int = 0): Seq[B]

I find these useful to have in my utilities.

1 Like

There’s zipWithIndex, which gives you List[A] => List[(A, Int)] and so on. The downside is that it makes an intermediate collection, but you can use an iterator if that’s a concern. I think that adding 3 more methods to each collection for that rather specialized use case is a bit too much.

4 Likes

I’m not sure if I’ve used iFlatmap, but I’d argue the other 2 are useful. zipwithIndex could be removed. Maybe they could just be (util) methods on Arrays.

I have tried to read through all the answers and couldn’t see anything like the following suggestion.

implicit class IteratorOps[A](it: Iterator[A]) {

/**
  * Constructs an iterator where consecutive elements are accumulated as
  * long as the output of f for each element doesn't change.
  * <pre>
  * Vector(1,2,2,3,3,3,2,2)
  *     .iterator
  *     .groupUntilChanged(identity)
  *     .toList
  * </pre>
  * produces
  * <pre>
  * List(Seq(1),
  *      Seq(2,2),
  *      Seq(3,3,3),
  *      Seq(2,2))
  * </pre>
  * @param f the function to compute a key for each element
  * @tparam K the type of the computed key
  * @return an iterator of sequences of the consecutive elements with the
  *         same key in the original iterator
  */
def groupUntilChanged[K](f: A => K): Iterator[Seq[A]] =
  new AbstractIterator[Seq[A]] {
    private var hd: Option[A] = readHead()
    override def hasNext: Boolean = hd.isDefined

    override def next(): Seq[A] = {
      hd match {
        case None => Iterator.empty.next()
        case Some(head) =>
          val key = f(head)
          hd = readHead()
          var seq = mutable.Buffer(head)
          while (hd.exists(el => f(el) == key)) {
            seq = seq ++ hd
            hd = readHead()
          }
          seq.toVector
      }
    }

    private def readHead() = if (it.hasNext) Some(it.next) else None
  }
}

This implementation and naming can certainly be improved but it suited my immediate need.I couldn’t find an equivalent on Stream or Iterator yet this problem is common enough when manipulating streams that it was added to akka-stream-contrib.

2 Likes

I want to support this addition. I have definitely had situations where I would have used this if it existed.

It seems to me that zipWithIndex is much more general than these other specific methods. Yes, imap could be made to produce the output of zipWithIndex, but the result would be longer, harder to read, and I have a feeling it might be less optimizable.

1 Like

I think it makes more sense in the context of streaming where you often have to deal with framing issues, but I’m not sure it applies much to synchronous collections. You can still add it to the scala-collection-contrib module, if you want and we can continue the discussion there…

I think it makes sense on all collections which promise not to load all the values in memory at the same time (so scala.collection.Iterator and possibly on scala.collection.Stream)
I am not familiar enough with the full structure of the collection api to know if there would a place where this can be added so it is available on “streams” but not made available on the full in memory collections…

zipWithIndex works perfectly with for-comprehensions. Removing it would be a real loss since for-comprehensions are the preferred way of doing mapping and iteration.

OK I have an array of 1000 doubles. I want to iterate / map over it with an index. zipWithIndex creates 3000 intermediate objects. I wouldn’t call that working perfectly. My utility method could in theory create a 1000 intermediate objects. Saving 2000 objects is not to be sniffed at, but in practice its even better as the JVM will almost certainly erase them at run time, where as I very much doubt its smart enough to erase the List.

I meant zipWithIndex can be conveniently used in for-comprehensions. If you want to optimize performance critical code then probably you should consider while loops instead of higher level constructs.

1 Like

notContains

notContains>

lacks> ?

I don’t want to write !(list.contains(x))