【问题标题】:Converting List[Option[A]] to an Option[List[A]] in Scala在 Scala 中将 List[Option[A]] 转换为 Option[List[A]]
【发布时间】:2015-02-26 22:13:24
【问题描述】:

我是 FP 和 Scala 的新手,正在阅读《Scala 中的函数式编程》一书。第 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. 我对@9​​87654329@ 的返回值的直觉是否正确?这真的是一个设计决策还是一种方式比另一种更好?
  2. 对于case h :: t,这是我理解的。我们首先将值h 传递给flatMap 中的匿名函数(如hh),该函数递归调用sequencesequence 的此递归调用返回一个 Option,将 Options 封装在 t 中。我们在这个返回值上调用map 并将h 传递给匿名函数(作为hh),然后它创建一个新的List[A],递归调用返回的列表作为尾部,h 作为头部.然后通过调用Some 将该值封装在Option 中并返回。

我对第二部分的理解正确吗?如果是,有没有更好的解释方式?

【问题讨论】:

    标签: scala


    【解决方案1】:

    如果列表中的任何元素是None,似乎sequence 旨在返回None,否则返回列表中的值的Some。所以你对Nil 的直觉是不正确的——Nil 是一个不包含Nones 的空列表,所以结果不应该是None

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

    假设我们有一些Option[List[A]] 类型的变量optionList 和一些A 类型的变量a。我们调用时会得到什么:

    optionList.map(a :: _)
    

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

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

    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 
    }
    

    【讨论】:

    • fold 版本的问题是生成的 List 不能保持顺序;它变得相反。
    • 到目前为止,您想在 case 语句中使用 sofar :+ value 代替 value :: sofar 来追加到列表的末尾。
    【解决方案2】:

    我想我试图从同一本书中解决相同的问题并想出了这个。它对我有用,看起来非常清晰和简洁

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

    【讨论】:

      【解决方案3】:

      尾递归解决方案

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

      【讨论】:

      • 如果顺序会颠倒原来的顺序
      【解决方案4】:

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

      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)以保留顺序。

      【讨论】:

        【解决方案5】:

        假设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
        }
        

        【讨论】:

          猜你喜欢
          • 2019-07-21
          • 1970-01-01
          • 1970-01-01
          • 2021-08-31
          • 2015-03-13
          • 2013-05-21
          • 2019-03-03
          • 2021-01-13
          • 2012-02-08
          相关资源
          最近更新 更多