Domanda

Voglio trasformare un List[Option[T]] in un Option[List[T]]. Il tipo di firma della funzione è

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

Il comportamento previsto è di mappare una lista che contiene solo Somes in un Some contenente un elenco degli elementi all'interno del elementi Some del. D'altra parte, se la lista di ingresso ha almeno un None, il comportamento previsto è quello di tornare solo None. Ad esempio:

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 esempio di implementazione, senza scalaz, potrebbe essere:

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]]; 
}}}

Ricordo di aver visto da qualche parte un esempio simile, ma utilizzando Scalaz per semplificare il codice. Come apparirebbe come?


Una versione leggermente più succinto, utilizzando Scala2.8 PartialFunction.condOpt, ma ancora senza 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
  }
}}
È stato utile?

Soluzione

C'è una funzione che trasforma un List[Option[A]] in un Option[List[A]] in Scalaz. E 'sequence. Per ottenere None in caso di qualsiasi degli elementi sono None e Some[List[A]] nel caso in cui tutti gli elementi sono Some, si può solo fare questo:

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

lo.sequence

Questo metodo trasforma effettivamente F[G[A] in G[F[A]] dato che esiste un'implementazione di Traverse[F], e di Applicative[G] (Option e List capita di soddisfare entrambi e sono forniti da tali importazioni).

La semantica Applicative[Option] sono tali che se uno degli elementi di una List di Options sono None, allora il sequence sarà None pure. Se si desidera ottenere un elenco di tutti i valori Some indipendentemente dal fatto che tutti gli altri valori sono None, si può fare questo:

lo flatMap (_.toList)

È possibile generalizzare che per ogni Monad che costituisce anche una Monoid (List sembra essere uno di questi):

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

Altri suggerimenti

Per qualche motivo non ti piace

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

? Questa è probabilmente la più breve a Scala, senza Scalaz.

Mentre il Applicative[Option] nel Scalaz ha il comportamento sbagliato direttamente uso MA#sequence, si può anche derivare una Applicative da un Monoid. Ciò è reso conveniente con MA#foldMapDefault o MA#collapse.

In questo caso, si usa una Monoid[Option[List[Int]]. Per prima cosa effettuare una mappa interna (MA#∘∘) per avvolgere i singoli Ints in Lists di 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]]

Astraendo da List a qualsiasi contenitore con istanze per Traverse, Pointed e 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]]

Purtroppo, cercando di compilare il codice attualmente sia trigger # 2741 o invia il compilatore in un ciclo infinito.

Aggiorna Per evitare l'attraversamento della lista due volte, dovrei avere foldMapDefault usato:

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

Questa risposta è stata basata sulla richiesta originale che una lista vuota, o un elenco contenente solo Nones, dovrebbero restituire un None. Per inciso, questo sarebbe meglio modellata dal tipo Option[scalaz.NonEmptyList] -. Garanzie NonEmptyList almeno un elemento

Se si desidera solo l'un List[Int], ci sono molti modi più facili, dato in altre risposte. Due modi diretti che non sono stati menzionati:

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

Questo ha funzionato per me. Spero che questa è una soluzione corretta.

Si restituisce None se una delle opzioni nell'elenco vale None, altrimenti restituisce Opzione di List [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

    }
  }

}

Scala 2.13, e l'aggiunta di Option::unless costruttore al libreria standard , una variante di Rex Kerr risposta sarebbe:

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, se le prestazioni sono in gioco (al fine di evitare di conversione implicita di flatten da Option a List):

Option.unless(list contains None)(list.map(_.get))
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top