Scala 关于给定谓词多次拆分的集合方法的思考

Scala 关于给定谓词多次拆分的集合方法的思考,scala,collections,Scala,Collections,我正在寻找一种集合方法,它在给定的成对条件下分裂,例如 val x = List("a" -> 1, "a" -> 2, "b" -> 3, "c" -> 4, "c" -> 5) implicit class RichIterableLike[A, CC[~] <: Iterable[~]](it: CC[A]) { def groupWith(fun: (A, A) => Boolean): Iterator[CC[A]] = new Iter

我正在寻找一种集合方法,它在给定的成对条件下分裂,例如

val x = List("a" -> 1, "a" -> 2, "b" -> 3, "c" -> 4, "c" -> 5)

implicit class RichIterableLike[A, CC[~] <: Iterable[~]](it: CC[A]) {
  def groupWith(fun: (A, A) => Boolean): Iterator[CC[A]] = new Iterator[CC[A]] {
    def hasNext: Boolean = ???

    def next(): CC[A] = ???
  }
}

assert(x.groupWith(_._1 != _._1).toList == 
  List(List("a" -> 1, "a" -> 2), List("b" -> 3), List("c" -> 4, "c" -> 5))
)
valx=List(“a”->1,“a”->2,“b”->3,“c”->4,“c”->5)
隐式类RichIterableLike[A,CC[~]Boolean):迭代器[CC[A]=新迭代器[CC[A]]{
def hasNext:Boolean=???
def next():CC[A]=???
}
}
断言(x.groupWith(u.\u 1!=u.\u 1)。toList==
列表(列表(“a”->1,“a”->2)、列表(“b”->3)、列表(“c”->4,“c”->5))
)
这是一种递归的
span

虽然我能够实现
,但我想知道

  • 如果我正在监督的收藏中已经存在一些东西
  • 该方法应该调用什么;
    groupWith
    听起来不太正确。它应该简洁,但在某种程度上反映了函数参数是成对操作的。
    groupWhere
    可能更接近,但仍然不清楚
  • 实际上,我想当使用
    groupWith
    时,谓词逻辑应该是反向的,因此我会使用
    x.groupWith(u.\u 1==u.\u 1)
  • 关于类型的想法。返回一个
    迭代器[CC[A]]
    在我看来是合理的。也许它应该需要一个
    CanBuildFrom
    并返回一个
    迭代器[to]

所以我的建议是这样的。我坚持使用
groupWith
,因为
span
在我看来不是很具有描述性。确实
groupBy
具有非常不同的语义,但也有类似的
grouped(size:Int)

我试图纯粹基于组合现有迭代器来创建迭代器,但这很混乱,因此下面是更低级的版本:

import scala.collection.generic.CanBuildFrom
import scala.annotation.tailrec
import language.higherKinds

object Extensions {
  private final class GroupWithIterator[A, CC[~] <: Iterable[~], To](
      it: CC[A], p: (A, A) => Boolean)(implicit cbf: CanBuildFrom[CC[A], A, To])
    extends Iterator[To] {

    private val peer      = it.iterator
    private var consumed  = true
    private var elem      = null.asInstanceOf[A]

    def hasNext: Boolean = !consumed || peer.hasNext

    private def pop(): A = {
      if (!consumed) return elem
      if (!peer.hasNext)
        throw new NoSuchElementException("next on empty iterator")

      val res   = peer.next()
      elem      = res
      consumed  = false
      res
    }


您也可以通过折叠来实现它,对吗?这是一个未优化的版本:

  def groupWith[A](ls: List[A])(p: (A, A) => Boolean): List[List[A]] = 
    ls.foldLeft(List[List[A]]()) { (acc, x) => 
      if(acc.isEmpty)
        List(List(x))
      else
        if(p(acc.last.head, x))
          acc.init ++ List(acc.last ++ List(x))
        else 
          acc ++ List(List(x))
    }

  val x = List("a" -> 1, "a" -> 2, "b" -> 3, "c" -> 4, "c" -> 5, "a" -> 4)    
  println(groupWith(x)(_._1 == _._1))
  //List(List((a,1), (a,2)), List((b,3)), List((c,4), (c,5)), List((a,4)))

您还可以编写使用tailrec/模式匹配的版本:

  def groupWith[A](s: Seq[A])(p: (A, A) => Boolean): Seq[Seq[A]] = {
    @tailrec
    def rec(xs: Seq[A], acc: Seq[Seq[A]] = Vector.empty): Seq[Seq[A]] = {
      (xs.headOption, acc.lastOption) match {
        case (None, _) => acc
        case (Some(a), None) => rec(xs.tail, acc :+ Vector(a))
        case (Some(a), Some(group)) if p(a, group.last) => rec(xs.tail, acc.init :+ (acc.last :+ a))
        case (Some(a), Some(_)) => rec(xs.tail, acc :+ Vector(a))
      }
    }

    rec(s)
  }

除了谓词需要比较两个连续的元素之外,它看起来或多或少与@om-nom-nom重复。从概念上讲,它似乎更类似于
span
,而不是
groupBy
,所以为什么不直接将其称为
span
(如计算列表中的每个span)?
import Extensions._
val x = List("a" -> 1, "a" -> 2, "b" -> 3, "c" -> 4, "c" -> 5)
x.groupWith(_._1 == _._1).to[Vector]
// -> Vector(List((a,1), (a,2)), List((b,3)), List((c,4), (c,5)))
  def groupWith[A](ls: List[A])(p: (A, A) => Boolean): List[List[A]] = 
    ls.foldLeft(List[List[A]]()) { (acc, x) => 
      if(acc.isEmpty)
        List(List(x))
      else
        if(p(acc.last.head, x))
          acc.init ++ List(acc.last ++ List(x))
        else 
          acc ++ List(List(x))
    }

  val x = List("a" -> 1, "a" -> 2, "b" -> 3, "c" -> 4, "c" -> 5, "a" -> 4)    
  println(groupWith(x)(_._1 == _._1))
  //List(List((a,1), (a,2)), List((b,3)), List((c,4), (c,5)), List((a,4)))
  def groupWith[A](s: Seq[A])(p: (A, A) => Boolean): Seq[Seq[A]] = {
    @tailrec
    def rec(xs: Seq[A], acc: Seq[Seq[A]] = Vector.empty): Seq[Seq[A]] = {
      (xs.headOption, acc.lastOption) match {
        case (None, _) => acc
        case (Some(a), None) => rec(xs.tail, acc :+ Vector(a))
        case (Some(a), Some(group)) if p(a, group.last) => rec(xs.tail, acc.init :+ (acc.last :+ a))
        case (Some(a), Some(_)) => rec(xs.tail, acc :+ Vector(a))
      }
    }

    rec(s)
  }