Convertir une liste d'options à une option de la liste à l'aide Scalaz
Question
Je veux transformer un List[Option[T]]
en Option[List[T]]
. Le type de signature de la fonction est
def lo2ol[T](lo: List[Option[T]]): Option[List[T]]
Le comportement attendu est de cartographier une liste qui contient seulement Some
s dans un Some
contenant une liste des éléments à l'intérieur des éléments Some
pour. D'autre part, si la liste d'entrée a au moins un None
, le comportement attendu est de retour juste None
. Par exemple:
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 exemple d'implémentation, sans scalaz, serait:
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]];
}}}
Je me souviens avoir vu quelque part un exemple similaire, mais en utilisant Scalaz pour simplifier le code. Comment serait-il ressembler?
Une version légèrement plus succincte, à l'aide de PartialFunction.condOpt
Scala2.8, mais toujours sans 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
}
}}
La solution
Il y a une fonction qui transforme un List[Option[A]]
en un Option[List[A]]
en Scalaz. Il est sequence
. Pour obtenir None
au cas où des éléments sont None
et un Some[List[A]]
dans le cas où tous les éléments sont Some
, vous pouvez simplement faire ceci:
import scalaz.syntax.traverse._
import scalaz.std.list._
import scalaz.std.option._
lo.sequence
Cette méthode se fait F[G[A]
en G[F[A]]
étant donné qu'il existe une mise en œuvre de Traverse[F]
et de Applicative[G]
(Option
et List
arriver à satisfaire à la fois et sont fournis par les importations).
La sémantique de Applicative[Option]
sont telles que si l'un des éléments d'un List
de Option
s sont None
, le sequence
sera None
aussi. Si vous souhaitez obtenir une liste de toutes les valeurs de Some
indépendamment du fait que toutes les autres valeurs sont None
, vous pouvez faire ceci:
lo flatMap (_.toList)
Vous pouvez généraliser que pour tout Monad
qui forme aussi un Monoid
(List
se trouve être l'un d'entre eux):
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))
Autres conseils
Pour une raison quelconque vous n'aimez pas
if (lo.exists(_ isEmpty)) None else Some(lo.map(_.get))
? C'est probablement le plus court à Scala sans Scalaz.
Alors que le Applicative[Option]
dans Scalaz a le mauvais comportement à utiliser directement MA#sequence
, vous pouvez également tirer un Applicative
d'un Monoid
. Ceci est rendu pratique avec MA#foldMapDefault
ou MA#collapse
.
Dans ce cas, nous utilisons un Monoid[Option[List[Int]]
. Nous effectuons une première carte interne (MA#∘∘
) pour envelopper les Int
s individuels dans List
s d'un élément.
(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]]
Abstraction faite de List
à un conteneur avec les instances de Traverse
, Pointed
et 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]]
Malheureusement, en essayant de compiler ce code actuellement, soit déclenche # 2741 ou envoie le compilateur dans une boucle infinie.
UPDATE
Pour éviter de traverser la liste deux fois, je devrais avoir utilisé foldMapDefault
:
(List(some(1), none[Int], some(2)) foldMapDefault (_ ∘ ((_: Int).pure[List])))
Cette réponse a été basée sur la demande initiale qu'une liste vide ou une liste ne contenant que None
s, devraient retourner un None
. Soit dit en passant, ce serait mieux modélisé par le type Option[scalaz.NonEmptyList]
-. Garantit NonEmptyList
au moins un élément
Si vous voulez juste l'un List[Int]
, il y a beaucoup de façons plus faciles, données dans d'autres réponses. Deux moyens directs qui ne sont pas mentionnés:
list collect { case Some(x) => x }
list flatten
Cela a fonctionné pour moi. J'espère que cela est une bonne solution.
Il retourne Aucun si l'une des options de la liste est Aucun, sinon il retourne l'option de la liste [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
}
}
}
À partir Scala 2.13
, et l'ajout de l'élément constructeur de Option::unless
à la bibliothèque standard , une variante à Rex Kerr serait:
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
ou, si la performance est en jeu (afin d'éviter la conversion implicite de flatten
de Option
à List
):
Option.unless(list contains None)(list.map(_.get))