Scala - Flatmap a Seq of Options and Traversable - scala

Consider a flatMap written over some case matching. For example:
list.flatMap( v =>
v match {
case Cond1 => if(something) Some(Int) else None
//..Other conditions yielding Option[Int]
case CondN => if(somethingelse) Seq(Int) else Seq()
})
However this wont compile. If the seq is all of Option[Int] or all of Seq[Int] the flatMap works. But not if the Seq is a mix of Options and Seqs. Why is such a restriction in place? Does this solve a particular ambiguity that I cannot think of as of now.
EDIT1
Adding code snippet from REPL
scala> val a = Seq(Option(1), Seq(2,3))
a: Seq[Equals] = List(Some(1), List(2, 3))
scala> val b = Seq(Seq(1), Seq(2,3))
b: Seq[Seq[Int]] = List(List(1), List(2, 3))
scala> a.flatMap(x=>x)
<console>:9: error: type mismatch;
found : Equals
required: scala.collection.GenTraversableOnce[?]
a.flatMap(x=>x)
^
scala> b.flatMap(x=>x)
res24: Seq[Int] = List(1, 2, 3)
EDIT2
After Filippo's answer I tried the following piece of code in the REPL and it worked.
scala> val options = Seq("opt1", "opt2")
options: Seq[String] = List(opt1, opt2)
scala> options.flatMap( x =>
| x match {
| case "opt1" => Some(1)
| case "opt2" => Seq(2,3)
| case _ => None
| })
res27: Seq[Int] = List(1, 2, 3)
How is the resolution different in each of the scenarios.
More importantly when I map instead of flatMap the result is the same as the Seq a that I had created.
scala> options.map( x =>
| x match {
| case "opt1" => Some(1)
| case "opt2" => Seq(2,3)
| case _ => None
| })
res28: Seq[Equals] = List(Some(1), List(2, 3))

Option is a GenTraversableOnce but scala needs some help here:
val a: Seq[TraversableOnce[Int]] = Seq(Option(1), Seq(2,3))
a.flatMap(x=>x)
res0: Seq[Int] = List(1, 2, 3)
EDIT after additions to the question
I think that if the type of your sequence is the one you are expecting, everything boils down to the function passed to the flatMap. If scala can't figure out that the function is (A) => Traversable[A] when the starting sequence is Seq[A], I think we should make some types explicit.
Now, back to your first sample, I would refactor it as:
list.flatMap {
case Cond1 if something => Seq(Int)
case CondN if somethingelse => Seq(Int)
case _ => Seq()
}
No doubt scala is now able to infer the types correctly.

Related

map error when applying on list of tuples in scala

If applying map method to a list of tuple in Scala, it complains error as below:
scala> val s = List((1,2), (3,4))
s: List[(Int, Int)] = List((1,2), (3,4))
scala> s.map((a,b) => a+b)
<console>:13: error: missing parameter type
Note: The expected type requires a one-argument function accepting a 2-Tuple.
Consider a pattern matching anonymous function, `{ case (a, b) => ... }`
s.map((a,b) => a+b)
^
<console>:13: error: missing parameter type
s.map((a,b) => a+b)
But if I apply similar map method to list of Int, it works fine:
scala> val t = List(1,2,3)
t: List[Int] = List(1, 2, 3)
scala> t.map(a => a+1)
res14: List[Int] = List(2, 3, 4)
Anyone knows why it is? Thanks.
Scala dosen't deconstruct tuples automatically. You'll need to either use curly brackets:
val s = List((1,2), (3,4))
val result = s.map { case (a, b) => a + b }
Or use a single parameter of type tuple:
val s = List((1,2), (3,4))
val result = s.map(x => x._1 + x._2)
Dotty (the future Scala compiler) will bring automatic deconstruction of tuples.

How to make function for flatten pairs of arrays in Scala?

if i have
scala> test
res3: Array[java.io.Serializable] = Array(Array((AA,BB), (CC,DD)), (EE,FF))
and i want to convert this to
Array[(Any, Any)] = Array((AA,BB), (CC,DD), (EE,FF))
I can convert using flatMap function like this
scala> val test2 = test.flatMap{
| case (a,b) => Array((a,b))
| case i:Array[Any] => i.flatMap{
| case (a,b)=> Array((a,b))}
| }
test2: Array[(Any, Any)] = Array((AA,BB), (CC,DD), (EE,FF))
but I want to make function for All depth of Arrays.
so I tried
scala> def flatArray(array: Array[Any]): Array[(Any,Any)] ={
| array.flatMap{
| case (a,b) => Array((a,b))
| case i:Array[Any] => flatArray(i)
| }
| }
scala> val test2 = flatArray(test)
<console>:9: error: type mismatch;
found : Array[java.io.Serializable]
required: Array[Any]
Note: java.io.Serializable <: Any, but class Array is invariant in type T.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
val test2 = flatArray(test)
^
what's the problem ???
You can do flattening the following way:
def flatten(arr:Array[Any]):Array[(Any,Any)] =
arr.flatMap {
case (a,b) => Array((a,b))
case v:Array[Any] => flatten(v)
}
Well, I have a solution, maybe not the best one, since it uses non-tail recursion that may lead to a problems when you have a lot of data. Also, it assumes, that you don't have mixed tuples and arrays on the same level (like Array(1 -> 2, Array(2 -> 3)). So, just for reference:
import scala.collection.mutable.ArrayBuffer
val a: Array[Any] =
Array(
Array(1 -> 2, 2 -> 3),
Array(
Array(7 -> 1, 8 -> 3),
Array(
Array(1 -> 4, 5 -> 6, 12 -> 5),
Array(3 -> 4)
)
)
)
def flattenImpl(arr: Array[Any], acc: ArrayBuffer[(Int, Int)]): Array[(Int, Int)] = {
arr.headOption match {
case None => acc.toArray
case Some((a: Int, b:Int)) => flattenImpl(arr.tail, acc :+ a -> b)
case Some(a: Array[Any]) => flattenImpl(a, acc ++ flattenImpl(arr.tail, acc))
}
}
def flatten(arr: Array[Any]): Array[(Int, Int)] = flattenImpl(arr, ArrayBuffer())
val res = flatten(a)
res: Array[(Int, Int)] = Array((3,4), (1,4), (5,6), (12,5), (7,1), (8,3), (1,2), (2,3))

How to convert a List[Any] in Option[List[String]]

Given a List[Any], I want to convert it to a Option[List[String]]
def convert(ls: List[Any]) : Option[List[String]] = {
if (ls.forall(_.getClass == classOf[String]))
Some(ls.map(_.asInstanceOf[String]))
else
None
}
Is there a better way ?
Like:
scala> val bag = List("a", 1, 2.0, "b")
bag: List[Any] = List(a, 1, 2.0, b)
scala> def unbagged(vs: List[Any]): Option[List[String]] = Option(vs collect { case s: String => s}) filter (_.nonEmpty)
unbagged: (vs: List[Any])Option[List[String]]
scala> unbagged(bag)
res0: Option[List[String]] = Some(List(a, b))
scala> unbagged(List(1, 3.14))
res1: Option[List[String]] = None
or, addressing the use case:
scala> def strung(vs: List[Any]): Option[List[String]] = (Option(vs) filter (_ forall { case _: String => true case _ => false })).asInstanceOf[Option[List[String]]]
strung: (vs: List[Any])Option[List[String]]
scala> strung(bag)
res3: Option[List[String]] = None
scala> strung(List("a","b","c"))
res4: Option[List[String]] = Some(List(a, b, c))
There are already quite a few answers, but I think they're all cleverer than needed. The initial proposal in the question is not that bad, except I would replace the getClass test by isInstanceOf:
def convert(ls: List[Any]): Option[List[String]] = {
if (ls.forall(_.isInstanceOf[String]))
Some(ls.map(_.asInstanceOf[String]))
else
None
}
It's functional, copies the list only once. Yes, the list is traversed twice, but typically that it still going to be faster than throwing an exception (which is usually slow -- if you really want to go that route, at least use a ControlThrowable, which does not record the stack trace when constructed).
Besides, as #som-snytt pointed out quietly in a comment, due to erasure, you don't even need to cast all the elements inside the list. You may just as well cast the list, which, after having checked that all elements are Strings, is just as safe as any other casts:
def convert(ls: List[Any]): Option[List[String]] = {
if (ls.forall(_.isInstanceOf[String]))
Some(ls.asInstanceOf[List[String]])
else
None
}
This is simply the most efficient version, because there is no list copying at all.
There is a toString method, which can make a String from any object. So if it's not a requirement that all elements of your original List should actually be String elements, you can do this:
import scala.util.Try
def convert(l: List[Any]) : Option[List[String]] = {
Try(l.map(_.toString)).toOption
}
Try will return Some(x) if it succeeds and obtains a value x, or None otherwise.
If the conversion should succeed only if all elements are Strings, then we can do the conversion inside Try (at the first failure, Try will fail and hence we'll get None)
import scala.util.Try
def convert(l: List[Any]) : Option[List[String]] = {
Try(l.map(_.asInstanceOf[String])).toOption
}
I would recommend to use pattern matching:
def convert(l: List[Any]) : Option[List[String]] = {
Try(list.collect{
case s : String => s
case x : Any => throw new Exception()
}).toOption
}
Code is a bit ugly but it works.
It doesnt use classOf but it uses pattern matching:
scala> val l1 = List("a", 1, 12.0)
l1: List[Any] = List(a, 1, 12.0)
scala> val l2 = List[Any]("a", "b", "c")
l2: List[Any] = List(a, b, c)
scala> def convert(list: List[Any]) = {
| list.foldLeft(Some(List()): Option[List[String]]) { (x, y) =>
| x match {
| case Some(l) =>
| y match {
| case elem: String => Some(l ::: List(elem))
| case _ => None
| }
| case None => None
| }
| }
| }
convert: (list: List[Any])Option[List[String]]
scala> convert(l1)
res12: Option[List[String]] = None
scala> convert(l2)
res13: Option[List[String]] = Some(List(a, b, c))
scala>
There is a straightforward solution using scalaz :
def convert(ls: List[Any]) : Option[List[String]] =
ls.map { a => if (a.isInstanceOf[String]) Some(a.asInstanceOf[String]) else None}.sequence

Using a Type with collect

I'm trying to dynamically filter (or collect) a list based on type:
If I do this specifying the type explicitly, it works fine
scala> var aList = List("one", 2, 3.3)
aList: List[Any] = List(one, 2, 3.3)
scala> aList.collect{case x:Int => x}
res10: List[Int] = List(2)
If I want to write a method to do this generically, then it doesn't:
scala> def collectType[T](l:List[Any]):List[T] = l.collect{case x:T => x}
warning: there were unchecked warnings; re-run with -unchecked for details
collectType: [T](l: List[Any])List[T]
scala> collectType[Int](aList)
res11: List[Int] = List(one, 2, 3.3)
scala> collectType[Double](aList)
res16: List[Double] = List(one, 2, 3.3)
scala> collectType[String](aList)
res14: List[String] = List(one, 2, 3.3)
I thought at first that it was naming the type 'Integer' rather than using Integer as the type, but that doesn't seem to be the case as:
collectType[Int](aList).foreach(x => println(x))
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
It's as though it's deferring checking the type until it's forced to
What am I missing about Types?
Is there a way to achieve what I want to achieve?
After reading through the linked questions, this is what I've come up with. Pretty simple now that it's been pointed out. Taggable is a trait which knows how to hold a map of tags for a class
def matches[F <: Taggable](thing:Taggable)(implicit m:Manifest[F]):Boolean = {
thing match {
case e if (m >:> singleType(e)) => true
case x => false
}
}
def findByType[G <: Taggable](list:List[Taggable])(implicit m:Manifest[G]) = {
list.collect{case x if (matches[G](x)) => x}
}
You are missing type erasure. At runtime your method is actually
def collectType(l:List):List = l.collect {case x:Object => x}

Scala collection type for filter

Assume you have a List(1,"1") it is typed List[Any], which is of course correct and expected. Now if I map the list like this
scala> List(1, "1") map {
| case x: Int => x
| case y: String => y.toInt
| }
the resulting type is List[Int] which is expected as well. My question is if there is an equivalent to map for filter because the following example will result in a List[Any]. Is this possible? I assume this could be solved at compile time and possibly not runtime?
scala> List(1, "1") filter {
| case x: Int => true
| case _ => false
| }
Scala 2.9:
scala> List(1, "1") collect {
| case x: Int => x
| }
res0: List[Int] = List(1)
For anyone stumbling across this question wondering why the most-voted answer doesn't work for them, be aware that the partialMap method was renamed collect before Scala 2.8's final release. Try this instead:
scala> List(1, "1") collect {
| case x: Int => x
| }
res0: List[Int] = List(1)
(This should really be a comment on Daniel C. Sobral's otherwise-wonderful answer, but as a new user, I'm not allowed to comment yet.)
With regard to your modified question, if you simply use a guard in the case comprising your partialFunction, you get filtering:
scala> val l1 = List(1, 2, "three", 4, 5, true, 6)
l1: List[Any] = List(1, 2, three, 4, 5, true, 6)
scala> l1.partialMap { case i: Int if i % 2 == 0 => i }
res0: List[Int] = List(2, 4, 6)