【问题标题】:Scala conditional list constructionScala条件列表构造
【发布时间】:2013-03-22 10:58:34
【问题描述】:

我正在使用 Scala 2.9.2,并且想根据一些条件构建一个列表。

考虑以下情况,其中 cond 是某个函数,采用谓词 p 和类型 T 的值(在本例中为 t3):

t1 :: t2 :: cond( p, t3 ) :: t4

我想要的行为如下。如果 p 为真,这应该给出:

List[T]( t1, t2, t3, t4 )

如果 p 的计算结果为 false,则应该给出:

List[T]( t1, t2, t4 )

我可能以完全错误的方式思考这个问题,但我正在努力想出一个优雅的解决方案。我可以在任何地方使用 Options 然后进行过滤,但这会使代码更难阅读:

def cond[T]( p : => Boolean, v : T ) : Option[T] =
{
    p match
    {
        case true => Some( v )
        case false => None
    }
}

这允许以下操作:

scala> ( Some( 1 ) :: Some( 2 ) :: cond( true, 3 ) :: Some( 4 ) :: Nil ).flatten
res4: List[Int] = List(1, 2, 3, 4)

scala> ( Some( 1 ) :: Some( 2 ) :: cond( false, 3 ) :: Some( 4 ) :: Nil ).flatten
res5: List[Int] = List(1, 2, 4)

然而,这不是最优雅的解决方案,因为它要求用户将所有非条件元素包装在 Some() 中,并记住在最后进行展平。谁能想到更优雅的解决方案?

【问题讨论】:

    标签: scala collections


    【解决方案1】:

    作为一个选项,您可以考虑切换到使用 Scala.collection.mutable 包中的 ListBuffer

    val listBuffer = new ListBuffer[<item_type>]
    if(<item1_cond>) listBuffer += <item1>
    if(<item2_cond>) listBuffer += <item2>
    

    请注意,这仍然是一个指向可变集合的 val(不可变引用)

    【讨论】:

      【解决方案2】:

      生成一个列表怎么样?

      @inline def cond[T]( p : => Boolean, v : T ) : List[T] = if(p) v::Nil else Nil
      

      然后像这样使用它们:

      List(1,2,3) ++ cond(false, 3 ) ++ List(4)
      

      【讨论】:

      • 这可行,但并不理想,因为它不适合 1 :: 2 :: 3 构造方法:它需要您记住在某些情况下使用 ++ 而不是 ::
      【解决方案3】:

      因此,这不可能与标准列表一起使用,因为类型错误::: 需要 [A &gt;: T] 类型的元素,其中 T 是列表类型,而您想给它一些可能会或可能不会产生该类型的元素。

      但是,没有理由不能定义一种方法,该方法非常乐意采用仅可选地产生下一个元素的东西。 List 本身是密封的,所以我们不能直接扩展它,但是我们可以很容易地复制我们需要的行为:

      trait QList[+T] {
      
        def hd : T
        def tl : QList[T]
      
        def ?::[A >: T](hd : A) : QList[A] = new ?::[A](hd, this)
        def ?::[A >: T](hd : => Option[A]) : QList[A] = hd match {
          case Some(a) => ?::(a)
          case None => this
        }
      }
      
      case object QNil extends QList[Nothing] {
        def hd = throw new Exception("Head of empty list")
        def tl = throw new Exception("Tail of empty list")
      }
      case class ?::[+T](val hd: T, val tl : QList[T]) extends QList[T]
      
      def cond[T]( p : => Boolean, v : T ) : Option[T] =
      {
        p match
        {
          case true => Some( v )
          case false => None
        }
      }
      
      val truelist = 1 ?:: 2 ?:: 3 ?:: cond(true, 4) ?:: 5 ?:: QNil
      val falselist = 1 ?:: 2 ?:: 3 ?:: cond(false, 4) ?:: 5 ?:: QNil
      

      我们基本上是重新创建列表,但是用一个需要条件的重载前置操作来填充它。

      可以通过隐式转换到具有正确方法的另一个类,将?:: 方法添加到标准列表中。您提到您使用的是 2.9.2,这很遗憾,因为否则这是隐式值类非常适合的事情,但我们不需要它们,它们只是让事情变得更容易:

      class ConditionallyAppend[T](tl : List[T]) {
        def ?::[A >: T](hd : A) : List[A] = hd :: tl
        def ?::[A >: T](x : => Option[A]) : List[A] = x match {
          case Some(a) => a :: tl
          case None => tl
        }
      }
      
      implicit def ListToConditionallyAppend[A](x : List[A]) = new ConditionallyAppend(x)
      

      现在我们可以这样做了:

      val truelist = 1 ?:: 2 ?:: 3 ?:: cond(true, 4) ?:: 5 ?:: Nil

      truelist: List[Any] = List(1, 2, 3, 4, 5)

      还有这个:

      val falselist = 1 ?:: 2 ?:: 3 ?:: cond(false, 4) ?:: 5 ?:: Nil

      假名单:List[Any] = List(1, 2, 3, 5)

      【讨论】:

      • 我对此进行了简短的编辑,建议我们可以替代默认的:: 运算符,而不是添加?::。我们可以,但它并不完全有效。因为List 是协变的,所以标准前置将接受任何它可以推断为超类型的东西,因此只会给你一个List[Any] 和一个None
      【解决方案4】:

      尝试根据您的条件创建和过滤新列表:

      List[T](t1, t2, t3, t4).filter(p)
      

      【讨论】:

      • 这在一般情况下不起作用 - 条件 p_i 特定于元素 t_i - 评估 p_i 的结果应该只影响 t_i 是否在列表中,而不是 t_j,其中 j !=我
      【解决方案5】:

      如果您需要知道索引以选择正确的谓词,您可以zipWithIndex 将索引与值配对,然后使用collect 而不是filter 以允许您从结果中删除索引并编码谓词选择和在警卫中的应用。例如:

      List(1, 4, 9, 16, 25).zipWithIndex.collect { case (n, i) if (n + i) % 3 <= 1 => n }
      res0: List[Int] = List(1, 16)
      

      【讨论】:

        猜你喜欢
        • 2019-02-19
        • 1970-01-01
        • 2022-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-30
        • 2015-11-30
        • 1970-01-01
        相关资源
        最近更新 更多