Преобразовать список опций в опцию списка с помощью Scalaz - PullRequest
28 голосов
/ 03 апреля 2010

Я хочу преобразовать List[Option[T]] в Option[List[T]]. Тип подписи функции

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

Ожидаемое поведение - отобразить список, содержащий только Some s, в Some, содержащий список элементов внутри элементов Some. С другой стороны, если во входном списке есть хотя бы один None, ожидаемое поведение - просто вернуть None. Например:

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

Примером реализации без скаляза будет:

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

Я помню, что где-то видел подобный пример, но использовал Scalaz для упрощения кода. Как бы это выглядело?

<Ч />

Немного более краткая версия, использующая Scala2.8 PartialFunction.condOpt, но без 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
  }
}}

Ответы [ 5 ]

20 голосов
/ 03 апреля 2010

В Scalaz есть функция, которая превращает List[Option[A]] в Option[List[A]]. Это sequence. Чтобы получить None в случае, если любой из элементов None и Some[List[A]] в случае, если все элементы Some, вы можете просто сделать это:

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

lo.sequence

Этот метод фактически превращает F[G[A] в G[F[A]], учитывая, что существует реализация Traverse[F] и Applicative[G] (Option и List оказываются удовлетворяют обоим и обеспечиваются этим импортом).

Семантика Applicative[Option] такова, что если любой из элементов List из Option s равен None, то sequence также будет None. Если вы хотите получить список всех значений Some независимо от того, являются ли другие значения None, вы можете сделать это:

lo flatMap (_.toList)

Вы можете обобщить это для любого Monad, который также формирует Monoid (List, случается, один из них):

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))
18 голосов
/ 03 апреля 2010

Почему-то вам не нравится

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

? Это, вероятно, самый короткий путь в Скале без Скалаза.

2 голосов
/ 03 апреля 2010

Хотя Applicative[Option] в Scalaz имеет неправильное поведение для непосредственного использования MA#sequence, вы также можете получить Applicative из Monoid. Это удобно с MA#foldMapDefault или MA#collapse.

В этом случае мы используем Monoid[Option[List[Int]]. Сначала мы выполняем внутреннюю карту (MA#∘∘), чтобы обернуть отдельные Int s в List s одного элемента.

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

Абстрагирование от List до любого контейнера с экземплярами для Traverse, Pointed и 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]]

К сожалению, попытка скомпилировать этот код в настоящее время либо вызывает # 2741 , либо отправляет компилятор в бесконечный цикл.

UPDATE Чтобы избежать обхода списка дважды, я должен был использовать foldMapDefault:

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

Этот ответ был основан на исходном запросе о том, что пустой список или список, содержащий только None s, должен возвращать None. Между прочим, это лучше всего смоделировать с помощью типа Option[scalaz.NonEmptyList] - NonEmptyList, который гарантирует хотя бы один элемент.

Если вы просто хотите List[Int], есть много более простых способов, приведенных в других ответах. Два прямых способа, которые не были упомянуты:

list collect { case Some(x) => x }
list flatten
1 голос
/ 06 марта 2018

Это сработало для меня.Я надеюсь, что это правильное решение.

Возвращает None, если один из параметров в списке равен None, в противном случае возвращается параметр 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

    }
  }

}
0 голосов
/ 09 марта 2019

Начиная с Scala 2.13, и добавив компоновщик Option::unless к стандартной библиотеке , вариант Ответ Рекса Керра будет: 1009 *

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

или, если речь идет о производительности (во избежание неявного преобразования flatten из Option в List):

Option.unless(list contains None)(list.map(_.get))
...