使用Scalaz将选项列表转换为List选项

时间:2010-04-02 20:28:53

标签: scala option scalaz

我想将List[Option[T]]转换为Option[List[T]]。函数的签名类型是

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

预期的行为是将仅包含Some的列表映射到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())

没有scalaz的示例实现将是:

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 个答案:

答案 0 :(得分:20)

在Scalaz中有一个函数可以将List[Option[A]]转换为Option[List[A]]。它是sequence。要获得None以防任何元素NoneSome[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]OptionList的实现碰巧满足这两者并由这些进口提供。)

Applicative[Option]的语义是这样的:如果List Option的任何元素都是None,则sequence将{ {1}}也是。如果您想获得所有None值的列表,无论其他值是否为Some,您都可以执行此操作:

None

您可以概括为lo flatMap (_.toList) 形成Monad的{​​{1}}(Monoid恰好是其中之一):

List

答案 1 :(得分:18)

出于某种原因,你不喜欢

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

?如果没有Scalaz,这可能是Scala中最短的。

答案 2 :(得分:2)

虽然Scalaz中的Applicative[Option]行为不正确直接使用MA#sequence,但您也可以从Applicative派生Monoid。使用MA#foldMapDefaultMA#collapse

可以方便地进行操作

在这种情况下,我们使用Monoid[Option[List[Int]]。我们首先执行内部地图(MA#∘∘)以将单个Int包裹在一个元素的List中。

(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抽象到包含TraversePointedMonoid实例的任何容器:

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,要么将编译器发送到无限循环。

<强>更新 为了避免遍历列表两次,我应该使用foldMapDefault

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

此答案基于原始请求,即空列表或仅包含None的列表应返回None。顺便提一下,最好使用类型Option[scalaz.NonEmptyList]建模 - NonEmptyList至少保证一个元素。

如果你只想要一个List[Int],那么在其他答案中有更简单的方法。没有提到的两种直接方式:

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

答案 3 :(得分:1)

这对我有用。我希望这是一个正确的解决方案。

如果List中的一个Options为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

    }
  }

}

答案 4 :(得分:0)

Scala 2.13开始,并将Option::unless构建器添加到标准库Rex Kerr's answer的变体为:

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

或者,如果性能受到威胁(为了避免flattenOptionList的隐式转换):

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