#scala
#scala
Вопрос:
В коллекциях scala существует метод collect, который представляет собой комбинацию map и filter.
Существует ли другой метод, представляющий собой комбинацию flatMap и filter?
Вот что я пытаюсь сделать
val myList: List[Int] = ....
val x = myList.flatMap { id =>
val r : Option[List[Int]] = obj.foo(id)
r
}
Прямо сейчас компилятор scala сообщает мне, что тип x равен List[List[Int]]
Но я хочу List[Int]
Если я изменю свой код на
val myList: List[Int] = ....
val x = myList.flatMap { id =>
val r : Option[List[Int]] = obj.foo(id).get
r
}
Тогда я получаю то, что хочу. но я не хочу делать get. Итак, я хочу чистый и лаконичный способ создания flatMap при отфильтровывании элементов, которых нет.
Я также могу сделать
val myList: List[Int] = ....
val result = myList.flatMap { id =>
val r : Option[List[Int]] = obj.foo(id).getOrElse(List[Int]())
r
}
но это все еще очень многословно.
Комментарии:
1. Что вы пытаетесь сделать?
2. отредактировал мой вопрос
Ответ №1:
Ну, если вы пытаетесь фильтровать во время использования flatMap
, вы можете легко сделать что-то вроде
List(1, 2, 3).flatMap {
case n if n > 1 => List.fill(n)(n.toString)
case _ => Nil
}
// result: List("2", "2", "3", "3", "3")
В вашем конкретном случае:
myList.flatMap { id => obj.foo(id) match {
case Some(list) => list
case None => Nil
}}
Или даже короче
myList.flatMap(obj.foo(_).getOrElse(Nil))
Комментарии:
1. Почему не просто
myList.flatMap(obj.foo).flatten
?2.
flatten
это правильный способ для этой задачи. Она лаконична, понятна и существует именно для этой задачи.
Ответ №2:
Я думаю, что выражение for может сделать свое дело:
val myList: List[Int] = ....
val x = for {
id <- myList
r <- obj.foo(id)
} yield r
На самом деле выражение for переводится в flatMap
, map
и filter
(см. http://docs.scala-lang.org/tutorials/FAQ/yield.html )
Ответ №3:
Ваш исходный код работал бы идеально, если бы вы добавили .flatten
в конец.
Вот как я бы это написал:
val x1: List[Int] = myList.flatMap(obj.foo).flatten
или, если вам по какой-то причине действительно нужно collect
, то следующим образом:
val x2: List[Int] = myList.map(obj.foo).collect{
case Some(data) => data
}.flatten
Полный исходный код здесь:
object Test3 extends App {
object obj {
def foo(i: Int) = (0 to i).toList match {
case Nil => None
case nonEmpty => Some(nonEmpty)
}
}
val myList: List[Int] = (0 to 3).toList
val x = myList.flatMap { id =>
val r : Option[List[Int]] = obj.foo(id)
r
}.flatten
val x1: List[Int] = myList.flatMap(obj.foo).flatten
val x2: List[Int] = myList.map(obj.foo).collect{
case Some(data) => data
}.flatten
}
Ответ №4:
filter
на самом деле может быть выражено через, flatMap
если известно пустое состояние ( None
/ List.empty
), поэтому flatMap
является более общим, чем filter
(по крайней мере, для List
s и Option
s). Ваша проблема в том, что вам нужно применить это дважды:
myList.flatMap(obj.foo).flatMap(x => x)
или просто:
myList.flatMap(obj.foo).flatten
Простое замечание: если у вас есть тип List[List[T]]
или, в более общем M[M[T]]
плане, и вы хотите получить, M[T]
это обычно включает flatMap
/ flatten
Пример:
val obj = Map(1 -> List(1), 2 -> List(2, 3))
val l = List(1,2,3,4,5,6,7)
scala> obj.get(1)
res1: Option[List[Int]] = Some(List(1))
scala> l.flatMap(obj.get)
res2: List[List[Int]] = List(List(1), List(2, 3))
scala> l.flatMap(obj.get).flatten
res3: List[Int] = List(1, 2, 3)