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

发布于 2024-08-27 04:28:32 字数 1243 浏览 7 评论 0原文

我想将 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
  }
}}

I want to transform a List[Option[T]] into a Option[List[T]]. The signature type of the function is

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

The expected behavior is to map a list that contains only Somes into a Some containing a list of the elements inside the elements Some's. On the other hand, if the input list has at least one None, the expected behavior is to just return None. For example:

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

An example implementation, without scalaz, would be:

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

I remember seeing somewhere a similar example, but using Scalaz to simplify the code. How would it look like?


A slightly more succinct version, using Scala2.8 PartialFunction.condOpt, but still without 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
  }
}}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

停顿的约定 2024-09-03 04:28:32

Scalaz 中有一个函数可以将 List[Option[A]] 转换为 Option[List[A]]。这是序列。如果任何元素为 None,则获取 None;如果所有元素为 ,则获取 Some[List[A]]有些,你可以这样做:

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] 的语义是这样的:如果 OptionList 中的任何元素为 None,那么sequence也将是None。如果您想获取所有 Some 值的列表,无论其他值是否为 None,您可以这样做:

lo flatMap (_.toList)

您可以将其推广到任何 Monad 也形成了 MonoidList 恰好是其中之一):

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

There's a function that turns a List[Option[A]] into an Option[List[A]] in Scalaz. It's sequence. To get None in case any of the elements are None and a Some[List[A]] in case all the elements are Some, you can just do this:

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

lo.sequence

This method actually turns F[G[A] into G[F[A]] given that there exists an implementation of Traverse[F], and of Applicative[G] (Option and List happen to satisfy both and are provided by those imports).

The semantics of Applicative[Option] are such that if any of the elements of a List of Options are None, then the sequence will be None as well. If you want to get a list of all the Some values regardless of whether any other values are None, you can do this:

lo flatMap (_.toList)

You can generalize that for any Monad that also forms a Monoid (List happens to be one of these):

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))
季末如歌 2024-09-03 04:28:32

由于某种原因你不喜欢

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

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

For some reason you dislike

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

? That's probably the shortest in Scala without Scalaz.

不回头走下去 2024-09-03 04:28:32

开始 Scala 2.13,并添加 Option::unless 构建器到标准库,Rex Kerr 的答案的一个变体是:

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

Starting Scala 2.13, and the addition of the Option::unless builder to the standard library, a variant to Rex Kerr's answer would be:

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

or, if performance is at stake (in order to avoid flatten's implicit conversion from Option to List):

Option.unless(list contains None)(list.map(_.get))
鹿! 2024-09-03 04:28:32

虽然 Scalaz 中的 Applicative[Option] 直接使用 MA#sequence 的行为是错误的,但您也可以从 Applicative 派生一个 Applicative >幺半群。使用 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 的列表。顺便说一句,这最好通过 Option[scalaz.NonEmptyList] 类型进行建模 - NonEmptyList 保证至少有一个元素。

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

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

While the Applicative[Option] in Scalaz has the wrong behaviour to directly use MA#sequence, you can also derive an Applicative from a Monoid. This is made convenient with MA#foldMapDefault or MA#collapse.

In this case, we use a Monoid[Option[List[Int]]. We first perform an inner map (MA#∘∘) to wrap the individual Ints in Lists of one element.

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

Abstracting from List to any container with instances for Traverse, Pointed and 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]]

Sadly, trying to compile this code currently either triggers #2741 or sends the compiler into an infinite loop.

UPDATE
To avoid traversing the list twice, I should have used foldMapDefault:

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

This answer was based on the original request that an empty list, or a list containing only Nones, should return a None. Incidentally, this would be best modeled by the type Option[scalaz.NonEmptyList] -- NonEmptyList guarantees at least one element.

If you just want the a List[Int], there are many easier ways, given in other answers. Two direct ways that haven't been mentioned:

list collect { case Some(x) => x }
list flatten
差↓一点笑了 2024-09-03 04:28:32

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

如果列表中的选项之一为 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

    }
  }

}

This worked for me. I hope this is a correct solution.

It returns None if one of the Options in the List is None, otherwise it returns Option of 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

    }
  }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文