Scala分割序列或按定界符列出

时间:2018-10-24 19:27:03

标签: scala

假设我有一个这样的整数序列:

val mySeq = Seq(0, 1, 2, 1, 0, -1, 0, 1, 2, 3, 2)

我想用0作为分隔符来分割它,如下所示:

val mySplitSeq = Seq(Seq(0, 1, 2, 1), Seq(0, -1), Seq(0, 1, 2, 3, 2))

在Scala中最优雅的方法是什么?

5 个答案:

答案 0 :(得分:3)

高效的O(n)解决方案

不向列表附加任何内容的尾递归解决方案:

def splitBy[A](sep: A, seq: List[A]): List[List[A]] = {
  @annotation.tailrec
  def rec(xs: List[A], revAcc: List[List[A]]): List[List[A]] = xs match {
    case Nil => revAcc.reverse
    case h :: t => 
      if (h == sep) {
        val (pref, suff) = xs.tail.span(_ != sep)
        rec(suff, (h :: pref) :: revAcc)
      } else {
        val (pref, suff) = xs.span(_ != sep)
        rec(suff, pref :: revAcc)
      }
  }
  rec(seq, Nil)
}

val mySeq = List(0, 1, 2, 1, 0, -1, 0, 1, 2, 3, 2)
println(splitBy(0, mySeq))

产生:

List(List(0, 1, 2, 1), List(0, -1), List(0, 1, 2, 3, 2))

它还处理输入不以分隔符开头的情况。


很有趣:另一个适用于小整数的O(n)解决方案

这更多是警告,而不是解决方案。尝试重用String的{​​{1}}不会导致任何理智:

split

如果整数的范围大于65535,它将失败,并且看起来非常疯狂。不过,我觉得这很有趣:

val mySeq = Seq(0, 1, 2, 1, 0, -1, 0, 1, 2, 3, 2)
val z = mySeq.min
val res = (mySeq
  .map(x => (x - z).toChar)
  .mkString
  .split((-z).toChar)
  .map(s => 0 :: s.toList.map(_.toInt + z)
).toList.tail)

答案 1 :(得分:2)

这行得通

mySeq.foldLeft(Vector.empty[Vector[Int]]) {
  case (acc, i) if acc.isEmpty => Vector(Vector(i))
  case (acc, 0) => acc :+ Vector(0)
  case (acc, i) => acc.init :+ (acc.last :+ i)
}

其中0(或任何数字)是您的分隔符。

答案 2 :(得分:1)

您可以使用foldLeft

val delimiter = 0

val res = mySeq.foldLeft(Seq[Seq[Int]]()) {
  case (acc, `delimiter`) => acc :+ Seq(delimiter)
  case (acc, v) => acc.init :+ (acc.last :+ v)
}

注意:假定输入必须以delimiter开头。

答案 3 :(得分:0)

我认为这是一个简短的解决方案,应该在O(n)中运行:

def seqSplitter[T](s: ArrayBuffer[T], delimiter : T) = 
  (0 +: s.indices.filter(s(_)==delimiter) :+ s.size)  //find split locations
  .sliding(2)
  .map(idx => s.slice(idx.head, idx.last)) //extract the slice
  .dropWhile(_.isEmpty) //take care of the first element
  .toList

这个想法是获取所有出现定界符的索引,在它们上滑动并在这些位置处切片序列。 dropWhile负责第一个元素是否为定界符。

在这里,我将所有数据放入ArrayBuffer中以确保slicing will take O(size_of_slice)

val mySeq = ArrayBuffer(0, 1, 2, 1, 0, -1, 0, 1, 2, 3, 2)
seqSplitter(mySeq, 0).toList

礼物:

List(ArrayBuffer(0, 1, 2, 1), ArrayBuffer(0, -1), ArrayBuffer(0, 1, 2, 3, 2))

更详细的复杂度分析

操作是:

  • 过滤定界符索引(O(n))
  • 循环从上一步获得的索引列表(O(num_of_delimeters));对于对应于切片的每对索引:
    • 从数组中复制切片,并将其放入最终集合(O(size_of_slice))

最后两个步骤总计为O(n)。

答案 4 :(得分:0)

使用索引和反向切片的另一种变体

scala> val s = Seq(0,1, 2, 1, 0, -1, 0, 1, 2, 3, 2)
s: scala.collection.mutable.Seq[Int] = ArrayBuffer(0, 1, 2, 1, 0, -1, 0, 1, 2, 3, 2)

scala> s.indices.filter( s(_)==0).+:(if(s(0)!=0) -1 else -2).filter(_>= -1 ).reverse.map( {var p=0; x=>{ val y=s.slice(x,s.size-p);p=s.size-x;y}}).reverse
res173: scala.collection.immutable.IndexedSeq[scala.collection.mutable.Seq[Int]] = Vector(ArrayBuffer(0, 1, 2, 1), ArrayBuffer(0, -1), ArrayBuffer(0, 1, 2, 3, 2))

如果开头没有定界符,那么它也可以工作..感谢jrook

scala>  val s = Seq(1, 2, 1, 0, -1, 0, 1, 2, 3, 2)
s: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 1, 0, -1, 0, 1, 2, 3, 2)

scala> s.indices.filter( s(_)==0).+:(if(s(0)!=0) -1 else -2).filter(_>= -1 ).reverse.map( {var p=0; x=>{ val y=s.slice(x,s.size-p);p=s.size-x;y}}).reverse
res174: scala.collection.immutable.IndexedSeq[scala.collection.mutable.Seq[Int]] = Vector(ArrayBuffer(1, 2, 1), ArrayBuffer(0, -1), ArrayBuffer(0, 1, 2, 3, 2))

UPDATE1:

通过删除上面的“反向”来更紧凑的版本

scala> val s = Seq(0,1, 2, 1, 0, -1, 0, 1, 2, 3, 2)
s: scala.collection.mutable.Seq[Int] = ArrayBuffer(0, 1, 2, 1, 0, -1, 0, 1, 2, 3, 2)

scala> s.indices.filter( s(_)==0).+:(if(s(0)!=0) -1 else -2).filter(_>= -1 ).:+(s.size).sliding(2,1).map( x=>s.slice(x(0),x(1)) ).toList
res189: List[scala.collection.mutable.Seq[Int]] = List(ArrayBuffer(0, 1, 2, 1), ArrayBuffer(0, -1), ArrayBuffer(0, 1, 2, 3, 2))

scala> val s = Seq(1, 2, 1, 0, -1, 0, 1, 2, 3, 2)
s: scala.collection.mutable.Seq[Int] = ArrayBuffer(1, 2, 1, 0, -1, 0, 1, 2, 3, 2)

scala> s.indices.filter( s(_)==0).+:(if(s(0)!=0) -1 else -2).filter(_>= -1 ).:+(s.size).sliding(2,1).map( x=>s.slice(x(0),x(1)) ).toList
res190: List[scala.collection.mutable.Seq[Int]] = List(ArrayBuffer(1, 2, 1), ArrayBuffer(0, -1), ArrayBuffer(0, 1, 2, 3, 2))

scala>