2010-04-02 70 views
24

我想将List[Option[T]]转换为Option[List[T]]。函数的签名类型是使用Scalaz将选项列表转换为列表选项

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

示例实现,没有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 
    } 
}} 

回答

20

有一个函数可以将斯卡拉兹的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]OptionList碰巧满足这两个并且由那些进口提供)。

Applicative[Option]的语义是使得如果任何OptionList的一个S的元素是None,那么sequenceNone为好。如果你想获得的所有Some值的列表,无论任何其他值是否None,你可以这样做:

lo flatMap (_.toList) 

可以概括,对于还形成一个MonoidList恰好是任何Monad其中之一):

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

但是,如果您只需要'List [Option [A]]'的'Some'值,您就不需要'Option'了。您将拥有一个空列表或非空列表“A”。 – Apocalisp 2010-04-03 15:10:38

+0

实际上,如果任何元素都是'None',我确实想要'None',所以'sequence'正是我所要求的。 我会尝试编辑问题以澄清要求。 – 2010-04-03 16:20:57

+0

我无法使'lo.sequence'工作。使用scala-2.8.0.Beta1和scalaz-core_2.8.0.Beta1-5.0.1-SNAPSHOT.jar,如果我输入'def lo2ol [T](lo:List [Option [T]]):Option [List [ T]] = lo.sequence',我得到 ':10:error:diverging implicit expansion for type scalaz.Applicative [N]' – 2010-04-04 20:54:16

15

出于某种原因,你不喜欢

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

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

+2

不坏的选择,但它越过列表的两倍。 – Apocalisp 2010-04-03 15:19:58

+3

@Apocalisp:确实如此,但是在丢弃所有内容并丢弃'None'之前,这通常比制作新列表的一半要便宜。 – 2010-04-03 16:14:35

+1

我倾向于不喜欢'Option.get',但它在这里似乎没问题。 – 2010-04-03 16:27:57

2

虽然Scalaz的Applicative[Option]有错误的行为直接使用MA#sequence,你也可以从一个Monoid导出Applicative。这通过MA#foldMapDefaultMA#collapse来实现。我们使用Monoid[Option[List[Int]]。我们首先执行内部映射(MA#∘∘)以将单个Int s包含在一个元素的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或编译器送入一个无限循环。

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 
+0

这个问题背后真正的用例完全可以通过Apocalisp答案中的'sequence'方法实现,包括给定一个空列表的结果(可能发生在所述用例)。但是感谢提到NEL类型,它看起来很方便。 – 2010-04-04 19:41:07

+0

@retronym:Rafael写道:“如果输入列表中至少有一个无,预期的行为就是返回无”。当你写下“只包含Nones的列表时,应该返回一个无”。这是非常不同的。 “拉平”不符合拉菲尔实际要求的。 – 2010-04-04 20:48:08

+0

我的不好,我错误地解释了这个问题。 'MA#foldMapDefault'和'MA#sequence'都使用'Traverse [List]',但使用不同的Applicative Functors。 – retronym 2010-04-05 10:22:09

0

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

如果在列表中的选项之一是无返回None,否则返回列表[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 

    } 
    } 

}