split list in scala based on diff between neighbou

2020-05-06 17:02发布

问题:

How do we split list in scala based on difference between neighbouring elements. For example given List(1,3,6,10,12,14) and difference 3, the function would return List(List(1,3),List(6),List(10,12,14)).

Can we user foldLeft to do that? I was trying to create a function

def splitDiff(list:List[Int],diff:Int) = 
     def func(list:List[List[Int]],m:Int):List[List[Int]] = //compare with last element
     list.foldLeft(List(List(0))).foldLeft(func)

But the inner function seems difficult? Any help?

回答1:

Hmm, I have a solution, but I suspect one can do better:

(test.head :: test).sliding(2).toList.map( (pair: List[Int]) => (pair(1), pair(1) - pair(0)) )
                   .foldLeft(List(List.empty[Int])){ (acc, pair) => 
     if (pair._2 < 3) (pair._1 :: acc.head) :: acc.tail else List(pair._1) :: acc }

Note that this gives results in "doubly-reversed" order:

res3: List[List[Int]] = List(List(14, 12, 10), List(6), List(3, 1))

Which can be corrected by adding .map(_.reverse).reverse to the end of the function.

EDIT - alternate attempt:

def func(is: List[Int], diff: Int): List[List[Int]] = {
  def loop(is: List[Int], prev: Int, acc: List[List[Int]]): List[List[Int]] = is match {
    case Nil => acc
    case h :: t if (h - prev < diff) => loop(t, h, (h :: acc.head) :: acc.tail)
    case h :: t => loop(t, h, List(h) :: acc)
  }

  loop(is, is.head, List(List.empty[Int]))
}

Again, gives solution in doubly-reversed form.



回答2:

One more solution using foldLeft:

scala> val x = List(1,3,6,10,12,14)
x: List[Int] = List(1, 3, 6, 10, 12, 14)

scala> val y = x.foldLeft((x.head,List[Int](),List[List[Int]]())) 
     ((x,y)=> if((y- x._1) <3) (y,y :: x._2,x._3) else (y,List(y), x._2 :: x._3))
y: (Int, List[Int], List[List[Int]]) = (14,List(14, 12, 10),List(List(6), List(3
, 1)))

scala> val ans = y._2 :: y._3
ans: List[List[Int]] = List(List(14, 12, 10), List(6), List(3, 1))


回答3:

Pleas keep in mind that this is untested:

  def splitListDiff(li: List[Int], diff: Int): List[Int] =
        {
          li match {
            case hd :: tl => if (hd - tl < diff) hd :: splitListDiff(tl, diff) else return li ++ splitListDiff(tl, diff)
            case _ => return li
          }
        }

But you wanted a solution that leverages foldleft?



回答4:

The following works without additional reversing. Since the OP didn't say whether the list's elements are always in ascending order, I thought it'd be good to use Math.abs as well:

def splitDiff(diff: Int)(xs: List[Int]): List[List[Int]] = xs match {
  case Nil      => Nil
  case h :: t   =>
    val head = h :: ((xs zip t) takeWhile {
      case (a,b) => Math.abs(b-a) < diff
    } map (_._2))
    head :: splitDiff(diff)(xs drop head.length)
}

The downside of this approach is that the tail gets zipped over and over again. However, this could easily be avoided by encapsulating the match and zipping only once at the beginning.