使用Scala集合方法将列表[0,0,0,1,1,1,1,0,0,1,1]转换为[3,4,2,2]。

5
我有一个由0和1组成的列表,我想找到每个元素的数量并将其输出到一个列表中。我可以想到一种使用函数递归的方法,但是否有辅助函数可以帮助转换?
我认为groupBy可能有用,但它似乎将所有元素分组到一个或另一个分区中,而不是按照我想要的方式进行分组。
我想要一个数字计数列表,直到从0到1和从1到0的每个转换。例如,如果我们有0,0,0,..好的,我们数了3个零,所以记住3,然后我们有1,1,1,1,所以我们数了4个1,所以我们记住4,到目前为止,我们有一个列表[3,4...]等等。

Ruby有非常有用的Enumerable#chunk方法:`[0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1].chunk {|e| e }.map(&:last).map(&:size)

=> [3, 4, 2, 2]`。这可能是Scala标准库中一个有用的补充。

- Jörg W Mittag
7个回答

5
尾递归版本的Yann Moisan解决方案:
    def pack[A](ls: Seq[A], prev: Seq[Int] = Seq.empty): Seq[Int] = {
        if (ls.isEmpty) prev
        else {
            val (packed, next) = ls span {_ == ls.head }
            pack(next, prev :+ packed.size)
        }
    }

3
def pack[A](ls: List[A]): List[Int] = {
  if (ls.isEmpty) List(0)
  else {
    val (packed, next) = ls span { _ == ls.head }
    if (next == Nil) List(packed.size)
    else packed.size :: pack(next)
  }
}

1
这可以被实现为尾递归函数。 - Sergey Passichenko
这是一个有用的函数,它是否包含在任何Scala库中? - Phil
“pack”不包含在标准库中,但它基于标准方法“span”。 - Yann Moisan

1

这是 groupBy。

 val tuple = list.foldRight((0,0)) { (x, accum) =>
     if (x == 0) (accum._1 +1, accum._2) else (accum._1, accum._2 +1) 
 }
 List(tuple._1, tuple._2)

同样的思路,这里有一个 fliptracker(用于非空列表):

def fliptracker(list: List[Int]) = {

    val initial = (list.head, 0, List.empty[Int])

    val result = list.foldLeft(initial) {

        (acc, x) =>

          if (acc._1 == x) (acc._1, acc._2 + 1, acc._3) 
          else (x, 1, acc._3 ::: List(acc._2))
    }

    result._3 ::: List (result._2)
}       

fliptracker(List(0,0,0,1,1,1,1,0,0,1,1)) // List(3, 4, 2, 2)

抱歉,这与groupBy相同,我想要一个数字计数列表,直到从0到1和从1到0的每个转换。即,如果我们有0,0,0,..好的,我们计算了3个零,所以记住3,然后我们有1,1,1,1,所以我们计算了4个1,所以我们记住4,到目前为止,我们有一个列表[3,4...]等等。 - Phil
好的,请将这个示例添加到您的问题中。从文本中并不清楚。 - Shyamendra Solanki
添加了fliptracker,与之前的方法类似。 - Shyamendra Solanki

1
这可能有点复杂,但我会继续进行。
scala> implicit class ListHelper[A](ls:List[A]) {
         def partitionBy(f: (A, A) => Boolean) = if (ls.isEmpty) List.empty[Int] 
           else (ls zip (ls.head :: ls)).foldLeft(List.empty[Int]){
             case (Nil, _) => List(1)
             case (x :: xs, (a, b)) => if (a == b) (x + 1) :: xs else 1 :: x :: xs
         }.reverse
       }
defined class ListHelper

scala> List(0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1).partitionBy(_ == _)
res27: List[Int] = List(3, 4, 2, 2)

这是基于Clojure函数partition-by的。


复杂的实现,简单的使用。 - Phil
我选择这个作为答案,因为它是我使用过的,并且我使用它是因为它可以在其他情况下重复使用。 - Phil

1

我会做

List(0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1).foldLeft (List.empty[(Any,Int)]) { 
    (acc,a) => acc match {
        case (`a`, occ) :: tail => (a,occ+1) :: tail
        case _                  => (a,1) :: acc
}}.reverse.map(_._2)

res1: List[Int] = List(3, 4, 2, 2)

0

另一种基于takeWhile的替代答案。在此1 ==黑色,0 ==白色

case class IndexCount(index: Int, count: Int, black: Boolean)

@tailrec
def takeWhileSwitch(black: Boolean, index:Int, list: List[Boolean], 
        result: List[IndexCount]): List[IndexCount] = {
  if (list == Nil) return result.reverse

  val takenWhile = list.takeWhile(black == _)
  val takenLength = takenWhile.length

  val resultToBuild = if (takenLength != 0) {
    val indexCount = IndexCount(index, takenLength, black)
    indexCount :: result
  } else result

  takeWhileSwitch(!black, index + takenLength, list.drop(takenLength), resultToBuild)
}
val items = takeWhileSwitch(true, 0, rowsWithBlack, List[IndexCount]())

0

没有人预料到这个命令式解决方案:

def chunk[A](xs: List[A]) = {
  val ys = collection.mutable.Buffer[Int]()
  var prev = xs.head
  var count = 1
  for (x <- xs.tail) {      
    if (x != prev) {
      ys += count
      prev = x
      count = 1
    }
    else count += 1 
  }
  ys += count
  ys.toList
}

这是我写C和C++代码时使用的相同风格,但现在我尝试使用函数式编程。对于我的思维来说,你的解决方案非常清晰易读。 - Phil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接