Frage

Ich möchte ein List[Option[T]] in eine Option[List[T]] verwandeln. Der Signaturtyp der Funktion

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

Das erwartete Verhalten ist eine Liste zuzuordnen, die nur Somes in eine Some enthält eine Liste der Elemente im Inneren der Elemente Some des enthält. Auf der anderen Seite, wenn die Eingabeliste mindestens eine None hat, ist das erwartete Verhalten None nur zurückzukehren. Zum Beispiel:

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

Eine beispielhafte Implementierung, ohne scalaz, wäre:

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

Ich erinnere mich, dass irgendwo ein ähnliches Beispiel, aber unter Verwendung von Scalaz den Code zu vereinfachen. Wie würde es aussehen?


Eine etwas knappere Version, mit Scala2.8 PartialFunction.condOpt, aber immer noch ohne 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
  }
}}
War es hilfreich?

Lösung

Es gibt eine Funktion, die eine List[Option[A]] in eine Option[List[A]] in Scalaz dreht. Es ist sequence. Um None im Falle irgendeines der Elemente sind None und ein Some[List[A]] zu bekommen, falls alle Elemente sind Some, können Sie dies nur tun:

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

lo.sequence

Diese Methode tatsächlich dreht F[G[A] in G[F[A]] gegeben, dass es eine Implementierung von Traverse[F] existiert, und von Applicative[G] (Option und List passieren beide erfüllen und werden durch diese Einfuhren zur Verfügung gestellt).

Die Semantik Applicative[Option] ist so, dass, wenn eine der Elemente eines List von Options None ist, dann ist die sequence None als auch sein wird. Wenn Sie eine Liste aller Some Werte unabhängig davon erhalten möchten, ob andere Werte sind None, Sie können dies tun:

lo flatMap (_.toList)

Sie können, dass für jede Monad verallgemeinern, dass auch eine Monoid bildet (List geschieht eine davon zu sein):

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

Andere Tipps

Aus irgendeinem Grund, den Sie nicht mögen

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

? Das ist wahrscheinlich die kürzeste in Scala ohne Scalaz.

Während die Applicative[Option] in Scalaz das falsche Verhalten muss direkt Verwendung MA#sequence, können Sie ableiten auch eine Applicative von einem Monoid. Dies wird bequem mit MA#foldMapDefault oder MA#collapse.

In diesem Fall verwenden wir eine Monoid[Option[List[Int]]. Wir führen eine innere erste Karte (MA#∘∘) die einzelnen Ints in Lists von einem Element zu wickeln.

(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]]

Abstrahieren von List einen Behälter mit Instanzen für Traverse, Pointed und 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]]

Leider versuchen, diesen Code zu kompilieren derzeit entweder Trigger # 2741 oder sendet der Compiler in eine Endlosschleife.

UPDATE Um zu vermeiden, zweimal durchlaufen die Liste, ich gebrauchte foldMapDefault haben:

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

Diese Antwort wurde auf der ursprünglichen Anforderung aus, dass eine leere Liste oder eine Liste, die nur Nones, sollte eine None zurück. Im übrigen würde dies am besten durch die Art Option[scalaz.NonEmptyList] modelliert -. NonEmptyList garantiert mindestens ein Element

Wenn Sie nur die eine List[Int] wollen, gibt es viele einfachere Möglichkeiten, in anderen Antworten gegeben. Zwei direkte Wege, die nicht erwähnt haben:

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

Das ist für mich gearbeitet. Ich hoffe, dies ist eine richtige Lösung.

Es gibt keine, wenn eine der Optionen in der Liste keine, sonst gibt es eine Option der 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

    }
  }

}

Starten Scala 2.13, und die Zugabe des Option::unless Builder die Standardbibliothek , eine Variante zu Rex Kerrs Antwort wäre:

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

oder, wenn die Leistung auf dem Spiel steht (um zu vermeiden, flatten die implizite Konvertierung von Option zu List):

Option.unless(list contains None)(list.map(_.get))
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top