Какова комбинация flatMap и filter?

#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)