【问题标题】:How to extend a Scala list to enable slicing not by explicit position but by given predicate/condition如何扩展 Scala 列表以启用切片而不是通过显式位置而是通过给定的谓词/条件
【发布时间】:2014-03-15 00:50:17
【问题描述】:

对于

trait Item
case class TypeA(i: Int) extends Item
case class TypeB(i: Int) extends Item

考虑一个 Scala 项目列表,例如

val myList = List(TypeA(1), TypeB(11), TypeB(12), 
                  TypeA(2), TypeB(21), 
                  TypeA(3), TypeB(31))

目标是定义一个新的slice 方法,该方法可以应用于myList,并以谓词或条件作为参数;比如

myList.slice { x => x.isInstanceOf[TypeA] }

会交付

List(List(TypeA(1), TypeB(11), TypeB(12)), 
     List(TypeA(2), TypeB(21)), 
     List(TypeA(3), TypeB(31)))

在这个例子中,同样的结果将通过

myList.slice { case TypeA(x) => x < 10 }

非常感谢。

【问题讨论】:

  • 哦。您的意思是谓词定义了“开始新子列表的事物”?
  • @Rob Starling 没错,谓词定义了每个子列表的下限。
  • 如果输入不包含(或不以)满足谓词的元素,您在寻找什么?
  • @Rob Starling List span 证明了一个更简单、定义明确的案例,正如@Kevin Wright 所描述的那样;此处请求 multiSpan 方法,理想情况下可从列表实例调用,而不是作为以列表和谓词作为参数的单独方法。
  • @user3189923 - 好的,我在回答中也展示了如何做到这一点

标签: list scala collections slice


【解决方案1】:

List 已经有一个slice 方法 - 它采用开始和结束索引之间的元素子集。您正在寻找的是 span 方法的重复应用:

def span(p: (A) ⇒ Boolean): (List[A], List[A])

记录如下:

根据谓词将此列表拆分为前缀/后缀对。

注意:c span p 等价于(但可能比)(c takeWhile p, c dropWhile p),前提是谓词 p 的评估不会导致任何副作用。

返回:由该列表中所有元素都满足 p 的最长前缀和该列表的其余部分组成的对。

你可以通过反复使用这个方法和一个反向谓词来得到你需要的东西,以及额外的一点逻辑来确保返回的列表都不为空。

import annotation.tailrec

def multiSpan[A](xs: List[A])(splitOn: (A) => Boolean): List[List[A]] = {
  @tailrec
  def loop(xs: List[A], acc: List[List[A]]) : List[List[A]] = xs match {
    case Nil => acc

    case x :: Nil => List(x) :: acc

    case h :: t =>
      val (pre,post) = t.span(!splitOn(_))
      loop(post, (h :: pre) :: acc)
  }
  loop(xs, Nil).reverse
}

更新

根据原始帖子中 cmets 的要求,这是一个丰富列表而不是独立方法的版本:

implicit class AddMultispanToList[A](val list: List[A]) extends AnyVal {
  def multiSpan(splitOn: (A) => Boolean): List[List[A]] = {
    @tailrec
    def loop(xs: List[A], acc: List[List[A]]) : List[List[A]] = xs match {
      case Nil => acc

      case x :: Nil => List(x) :: acc

      case h :: t =>
        val (pre,post) = t.span(!splitOn(_))
        loop(post, (h :: pre) :: acc)
    }
    loop(list, Nil).reverse
  }
}

用作:

myList.multiSpan(_.isInstanceOf[TypeA])

【讨论】:

  • 这不是我第一次不得不解决这个问题 :)
【解决方案2】:

您不是在寻找filter,它(几乎)无需对您的示例进行任何调整即可工作?

$ sbt console
scala> trait Item
scala> case class TypeA(i: Int) extends Item
scala> case class TypeB(i: Int) extends Item
scala> val myList = List(TypeA(1), TypeB(11), TypeB(12), 
                         TypeA(2), TypeB(21), 
                         TypeA(3), TypeB(31))
myList: List[Product with Serializable with Item] = List(TypeA(1), TypeB(11), TypeB(12), TypeA(2), TypeB(21), TypeA(3), TypeB(31))

你的第一部作品没有改变:

scala> myList.filter { x => x.isInstanceOf[TypeA] }
res0: List[Product with Serializable with Item] = List(TypeA(1), TypeA(2), TypeA(3))

您的第二个需要默认情况:

scala> myList.filter { case TypeA(x) => x < 10; case _ => false }
res2: List[Product with Serializable with Item] = List(TypeA(1(3))

另见collect,它采用偏函数而不是布尔谓词:

scala> myList.collect { case z @ TypeA(x) if x < 10 => z }
res3: List[TypeA] = List(TypeA(1), TypeA(2), TypeA(3))

也可以变形:

scala> myList.collect { case TypeA(x) if x < 10 => x }
res4: List[Int] = List(1, 2, 3)

【讨论】:

    【解决方案3】:

    为什么不能使用标准 API 中的分区方法?

    示例:

    scala> val l = List(3,5,4,6)
    l: List[Int] = List(3, 5, 4, 6)
    
    scala> 
    
    scala> val (odd,even) = l.partition(_ %2 ==1)
    odd: List[Int] = List(3, 5)
    even: List[Int] = List(4, 6)
    

    你的例子:

    scala> val (typeA,typeB) = myList.partition(_.isInstanceOf[TypeA])
    typeA: List[Product with Serializable with Item] = List(TypeA(1), TypeA(2), TypeA(3))
    typeB: List[Product with Serializable with Item] = List(TypeB(11), TypeB(12), TypeB(21), TypeB(31))
    

    【讨论】:

    • 非常感谢您的想法; partition 会将初始列表一分为二;在这种情况下,我们需要多个子列表,每个子列表的下限由一个条件定义...
    • 您的意思是要获取两个以上的子列表吗?那么你可能不得不为此编写一个宏,或者使用 map + match + groupBy
    • 希望得到与此问题stackoverflow.com/q/21787505/3189923 类似的答案,其中List 类配备了用于一组专门数字的sum 方法。
    猜你喜欢
    • 2015-10-02
    • 1970-01-01
    • 2018-06-09
    • 1970-01-01
    • 2018-11-11
    • 2021-01-17
    • 2019-03-17
    • 1970-01-01
    • 2011-11-03
    相关资源
    最近更新 更多