将列表[选项[A]]转换为Scala中的选项[列表[A]]

时间:2015-02-26 22:13:25

标签: scala

我是FP和Scala的新手,正在阅读Scala中的Functional Programming一书。第4章中的一个练习要求我们编写一个名为sequence的函数,它将List[Option[A]]转换为Option[List[A]]。这里Option是Scala库提供的Option的重新实现。这是必需的代码。

trait Option[+A] {
    /* Function to convert Option[A] to Option[B] using the function passed as an argument */
    def map[B](f: A => B): Option[B] = this match {
        case None => None
        case Some(v) => Some(f(v))
    }

    /* Function to get the value in `this` option object or return the default value provided. Here,
     * `B >: A` denotes that the data type `B` is either a super-type of `A` or is `A`
     */
    def getOrElse[B >: A](default: => B): B = this match {
        case None => default
        case Some(v) => v
    }

    /* Used to perform (nested) operations on `this` and aborts as soon as the first failure is
     * encountered by returning `None`
     */
    def flatMap[B](f: A => Option[B]): Option[B] = {
        map(f).getOrElse(None)
    }
}

case class Some[+A](get: A) extends Option[A]   // used when the return value is defined
case object None extends Option[Nothing]        // used when the return value is undefined

现在我尝试了很多,但我必须查找编写sequence的解决方案,即

def sequence[A](l: List[Option[A]]): Option[List[A]] = l match {
    case Nil => Some(Nil)                   // Or `None`. A design decision in my opinion
    case h :: t => h.flatMap(hh => sequence(t).map(hh :: _))
}

我只是想确保我正确理解解决方案。所以这是我的问题。

  1. 我对case Nil的返回值的直觉是否正确?它真的是一个设计决定还是比另一个更好?
  2. 对于case h :: t,这就是我所理解的。我们首先将值h传递给flatMap中的匿名函数(作为hh),它以递归方式调用sequencesequence的这种递归调用会返回Option封装Option中的t。我们对此返回值调用map并将h传递给匿名函数(作为hh),然后创建一个新的List[A],其中递归调用返回的列表为尾部和h作为头部。然后通过调用Option将此值封装在Some中并返回。
  3. 我对第二部分的理解是否正确?如果是,是否有更好的解释方法?

5 个答案:

答案 0 :(得分:3)

如果列表中的任何元素为sequenceNone似乎都会返回None,否则会返回列表中Some的值。因此,您对Nil案例的直觉不正确 - Nil是一个不包含None的空列表,因此结果不应为None

让我们从内到外一步一步。

假设我们有optionList类型的变量Option[List[A]]a类型的变量A。我们打电话时会得到什么:

optionList.map(a :: _)

如果optionListNone,那么这将是None。如果optionList包含一个列表,请说list,这将是Some(a :: list)

现在,如果对于option类型的某个变量Option[A],我们在调用时会得到什么:

option.flatMap(a => optionList.map(a :: _))

如果optionNone,那么这将是None。如果option包含一个值,例如a,那么这将是optionList.map(a :: _),我们在上面计算出来(根据flatMap的定义)。

现在如果我们把它绑在一起,我们会看到如果任何元素是None,那么就会避免递归调用,整个结果将是None。如果没有元素是None,那么递归调用将继续追加元素的值,结果将是列表元素内部值的Some

如果你重写内部部分可能会更清楚:

def sequence[A](l: List[Option[A]]): Option[List[A]] = l match {
    case Nil => Some(Nil)
    case h :: t => h match {
        case None => None
        case Some(head) => sequence(t) match {
            case None => None
            case Some(list) => Some(head :: list)
        }
    }
}

甚至更少惯用,但也许澄清:

def sequence[A](l: List[Option[A]]): Option[List[A]] = l match {
    case Nil => Some(Nil)
    case h :: t => 
        val restOfList = sequence(t)
        if (h == None || restOfList == None) None else Some(h.get :: restOfList.get)
}

你也可以在没有递归的情况下自然地重写这个fold,以防你感到困惑:

def sequence[A](l: List[Option[A]]) = (Option(List.empty[A]) /: l) {
    case(Some(sofar), Some(value)) => Some(value :: sofar); 
    case(_, _) => None 
}

答案 1 :(得分:0)

我想我试图从同一本书中解决同样的问题并提出这个问题。它适用于我,看起来很清楚和consice

{{1}}

答案 2 :(得分:0)

尾递归解决方案

def seqToOption[T](s: Seq[Option[T]]): Option[Seq[T]] = {
  @tailrec
  def seqToOptionHelper(s: Seq[Option[T]], accum: Seq[T] = Seq[T]()): Option[Seq[T]] = {
    s match {
      case Some(head) :: Nil => Option(head +: accum)
      case Some(head) :: tail => seqToOptionHelper(tail, head +: accum)
      case _ => None
    }
  }
  seqToOptionHelper(s)
}

原始答案:Need to convert Seq[Option[A]] to Option[Seq[A]]

答案 3 :(得分:0)

使用List[Option[A]]Option[List[A]]转换为foldLeft

def optionSequence[A](loa: List[Option[A]]): Option[List[A]] =
    loa
      .foldLeft(Option(List.empty[A]))((ol, v) =>
          (ol, v) match {
          case (None, _) | (_, None) => None
          case (Some(l), Some(av))   => Some(av :: l)
      })
      .map(_.reverse)

由于List是一个链表,所以前置比附加要快。这就是为什么我将值放在最终列表的前面,然后反转列表(使用map)以保留顺序的原因。

答案 4 :(得分:0)

假设lList[Option[A]],这是使用partitionisEmpty的另一种方法。

l.partition(_.isEmpty) match {
  case (Nil, x) => Some(x.map(_.get))
  case _        => None
}

请注意,当给定一个空列表时,它将返回Some(List())。如果需要None,则添加另一种情况很简单:

l.partition(_.isEmpty) match {
  case (Nil, Nil) => None
  case (Nil, x)   => Some(x.map(_.get))
  case _          => None
}