Pregunta

Quiero transformar una List[Option[T]] en un Option[List[T]].El tipo de firma de la función es

def lo2ol[T](lo: List[Option[T]]): Option[List[T]]

El comportamiento esperado es que el mapa muestra una lista que contiene sólo Somes en una Some contiene una lista de los elementos en el interior de los elementos Some's.Por otro lado, si la entrada de la lista tiene al menos un None, el comportamiento esperado es que acabo de volver de None.Por ejemplo:

scala> lo2ol(Some(1) :: Some(2) :: Nil)
res10: Option[List[Int]] = Some(List(1, 2))

scala> lo2ol(Some(1) :: None :: Some(2) :: Nil)
res11: Option[List[Int]] = None

scala> lo2ol(Nil : List[Option[Int]])
res12: Option[List[Int]] = Some(List())

Un ejemplo de implementación, sin scalaz, sería:

def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = {
  lo.foldRight[Option[List[T]]](Some(Nil)){(o, ol) => (o, ol) match {
    case (Some(x), Some(xs)) => Some(x :: xs);
    case _ => None : Option[List[T]]; 
}}}

Yo recuerdo haber visto en algún lugar un ejemplo similar, pero utilizando Scalaz para simplificar el código.¿Cómo sería?


Un poco más sucinta versión, con Scala2.8 PartialFunction.condOpt, pero aún sin Scalaz:

import PartialFunction._

def lo2ol[T](lo: List[Option[T]]): Option[List[T]] = {
  lo.foldRight[Option[List[T]]](Some(Nil)){(o, ol) => condOpt(o, ol) {
    case (Some(x), Some(xs)) => x :: xs
  }
}}
¿Fue útil?

Solución

Hay una función que convierte una List[Option[A]] en un Option[List[A]] en Scalaz. Es sequence. Para obtener None en caso de que alguno de los elementos son None y una Some[List[A]] en caso de que todos los elementos son Some, sólo puede hacer esto:

import scalaz.syntax.traverse._
import scalaz.std.list._     
import scalaz.std.option._

lo.sequence

Este método se convierte en realidad F[G[A] en G[F[A]] dado que existe una implementación de Traverse[F], y de Applicative[G] (Option y List sucede para satisfacer ambos y se proporcionan por esas importaciones).

La semántica de Applicative[Option] son tales que si cualquiera de los elementos de un List de Options son None, entonces el sequence será None también. Si desea obtener una lista de todos los valores Some independientemente de si otros valores son None, usted puede hacer esto:

lo flatMap (_.toList)

Se puede generalizar que para cualquier Monad que también forma una Monoid (List pasa a ser uno de ellos):

import scalaz.syntax.monad._

def somes[F[_],A](x: F[Option[A]])
                 (implicit m: Monad[F], z: Monoid[F[A]]) =
  x flatMap (o => o.fold(_.pure[F])(z.zero))

Otros consejos

Por alguna razón no te gusta

if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))

?Esa es probablemente la más corta en la Scala sin Scalaz.

Mientras que el Applicative[Option] en Scalaz tiene el comportamiento erróneo directamente el uso MA#sequence, también se puede derivar una Applicative de un Monoid. Esto se hizo práctica y MA#foldMapDefault o MA#collapse.

En este caso, se utiliza un Monoid[Option[List[Int]]. En primer lugar, realizamos un mapa interior (MA#∘∘) para envolver los Ints individuales en Lists de un elemento.

(List(some(1), none[Int], some(2)) ∘∘ {(i: Int) => List(i)}).collapse assert_≟ some(List(1, 2))
(List(none[Int]) ∘∘ {(i: Int) => List(i)}).collapse                   assert_≟ none[List[Int]]
(List[Option[Int]]() ∘∘ {(i: Int) => List(i)}).collapse               assert_≟ none[List[Int]]

abstracción de List a cualquier recipiente con instancias para Traverse, Pointed y Monoid:

def co2oc[C[_], A](cs: C[Option[A]])
                  (implicit ct: Traverse[C], cp: Pointed[C], cam: Monoid[C[A]]): Option[C[A]] =
  (cs ∘∘ {(_: A).pure[C]}).collapse


co2oc(List(some(1), none[Int], some(2)))   assert_≟ some(List(1, 2))
co2oc(Stream(some(1), none[Int], some(2))) assert_≟ some(Stream(1, 2))
co2oc(List(none[Int]))                     assert_≟ none[List[Int]]
co2oc(List[Option[Int]]())                 assert_≟ none[List[Int]]

Lamentablemente, tratando de compilar este código, ya sea actualmente desencadenantes # 2741 o envía el compilador en un bucle infinito.

Actualizar Para evitar que atraviesa la lista dos veces, que debería tener foldMapDefault usada:

(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))

Esta respuesta se basa en la solicitud original que una lista vacía, o una lista que contiene sólo Nones, deben devolver un None. Por cierto, este sería mejor modelada por la Option[scalaz.NonEmptyList] tipo -. Garantías NonEmptyList al menos un elemento

Si lo que desea es la de un List[Int], hay muchas formas más sencillas, dadas en otras respuestas. Dos formas directas que no se han mencionado:

list collect { case Some(x) => x }
list flatten

Esto funcionó para mí. Espero que esto sea una solución correcta.

Se devuelve None si una de las opciones de la lista en su defecto, de lo contrario devuelve la opción de Lista [A]

def sequence[A](a: List[Option[A]]): Option[List[A]] = {

  a.foldLeft(Option(List[A]())) {
    (prev, cur) => {

      for {
        p <- prev if prev != None
        x <- cur
      } yield x :: p

    }
  }

}

A partir Scala 2.13, y la adición de la Option::unless constructor para la biblioteca estándar , una variante a Rex de Kerr respuesta sería:

Option.unless(list contains None)(list.flatten)
// val list = List(Some(1), Some(2))          =>    Some(List(1, 2))
// val list = List(Some(1), None, Some(2))    =>    None

o, si el rendimiento está en juego (en orden a la conversión implícita evitar de flatten de Option a List):

Option.unless(list contains None)(list.map(_.get))
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top