繁体   English   中英

按范围Scala将列表中的元素分组

[英]Group elements in a list by range Scala

考虑以下Scala中的列表:

List(4, 5, 6, 7, 8, 12, 13, 14, 17, 23, 24, 25)

我想得到输出

List(List(4,8), List(12,14), List(17), List(23,25))

我有这个答案Scala List函数用于将连续的相同元素分组

但是,它适用于将同一List中的相同元素分组。

如何扩展此解决方案来解决我当前的问题?

我已经尝试过此代码

def sliceByRange[A <% Int](s: List[A]): List[List[A]] = s match {
      case Nil => Nil
     case x :: xs1 =
    val (first, rest) = s.span(y => y - x == 1)
    first :: sliceByRange(rest)
    }

但这是行不通的。

尾递归解决方案

请注意,您也可以使用List[(Int,Int)]作为结果类型,而不是List[List[Int]] 这将反映出这样的事实,即结果是更适当的范围List 当然List(x,x)对于单例范围List(x,x)您无法将List(x,x)转换为List(x) 但是我希望以后无论如何都会再次咬你。

import scala.annotation.tailrec

@tailrec
def split(in: List[Int], acc: List[List[Int]] = Nil): List[List[Int]] = (in,acc) match {
  case (Nil,a) => a.map(_.reverse).reverse
  case (n :: tail, (last :: t) :: tt) if n == last + 1 => split(tail, (n :: t) :: tt)
  case (n :: tail, a ) => split(tail, (n :: n :: Nil) :: a)
}

val result = split(List(4, 5, 6, 7, 8, 12, 13, 14, 17, 23, 24, 25))
println(result)

println("removing duplicates:")
println(result.map{
  case List(x,y) if x == y => List(x)
  case l => l
})

输出量

List(List(4, 8), List(12, 14), List(17, 17), List(23, 25))
removing duplicates:
List(List(4, 8), List(12, 14), List(17), List(23, 25))

这是另一个示例:

val myList = List(4, 5, 7, 8, 12, 13, 14, 17, 23, 24, 25)

def partition(list: List[Int]): (List[Int], List[Int]) = {
    val listPlusOne = (list.head - 1 :: list) // List(1,2,5) => List(0, 1, 2, 5)
    val zipped = list zip listPlusOne // zip List(1,2,5) with List(0, 1, 2, 5) => List((1,0), (2,1), (5,2))

    val (a, b) = zipped span { case (a, b) => b + 1 == a } // (List((1,0), (2,1)), List((5,2)))
    (a.map(_._1), b.map(_._1)) // (List(1, 2),List(5))
}

def group(list: List[Int]): List[List[Int]] = list match {
    case Nil => Nil
    case _ =>
        val (a, b) = partition(list)
        val listA =  List(List(a.head, a.last).distinct) // remove middle numbers..
        val listB = if (b.isEmpty) Nil else group(b)
        listA ++ listB
}

println(group(myList))

稍微复杂一点,但是可以用...

解释您提到的问题的答案:

def split(list: List[Int]) : List[List[Int]] = list match {
  case Nil => Nil
  case h::t =>
    val segment = list.zipWithIndex.takeWhile { case (v, i) => v == h+i }.map(_._1)
    List(h, segment.last).distinct :: split(list drop segment.length)
}

使用zipWithIndex检查每个元素是否正好是“下一个”整数(数字应与索引一起“增加”)。 然后-仅获取细分的“边界”,然后递归移动到列表的其余部分。

我的解决方案:

def sliceByRange(items: List[Int]) =
  items.sorted.foldLeft(Nil: List[List[Int]]) {
    case (initRanges :+ (head :: Nil), next) if head == next - 1 =>
      initRanges :+ (head :: next :: Nil) // append element to the last range
    case (initRanges :+ (head :: last :: Nil), next) if last == next - 1 =>
      initRanges :+ (head :: next :: Nil) // replace last range
    case (ranges, next) =>
      ranges :+ (next :: Nil) // add new range
  }

用法:

sliceByRange(List(1, 2, 3, 5, 8, 9, 12, 13, 14, 19))
// List(List(1, 3), List(5), List(8, 9), List(12, 14), List(19))

如果希望保留中间值,可以使用以下示例:

def makeSegments(items: List[Int]) =
  items.sorted.foldLeft(Nil: List[List[Int]]) {
    case (initSegments :+ lastSegment, next) if lastSegment.last == next - 1 =>
      initSegments :+ (lastSegment :+ next)
    case (segments, next) =>
      segments :+ (next :: Nil)
  }

用法:

makeSegments(List(1, 2, 3, 5, 8, 9, 12, 13, 14, 19))
// List(List(1, 2, 3), List(5), List(8, 9), List(12, 13, 14), List(19))

当范围大小至少为3个元素时:

def sliceByRange3elements(items: List[Int]) =
  items.sorted.foldLeft(Nil: List[List[Int]]) {
    case (initRanges :+ (head :: last :: Nil), next) if last == next - 1 =>
      initRanges :+ (head :: next :: Nil) // replace last range
    case (initRanges :+ (ll :: Nil) :+ (l :: Nil), next) if ll == next - 2 && l == next - 1 =>
      initRanges :+ (ll :: next :: Nil) // make new range
    case (ranges, next) =>
      ranges :+ (next :: Nil)
  }

用法(请注意,(8,9)现在不在范围内):

sliceByRange3elements(List(1, 2, 3, 5, 8, 9, 12, 13, 14, 19))
// List(List(1, 3), List(5), List(8), List(9), List(12, 14), List(19))

您可以定义printRanges方法以获得更直观的输出:

def printRanges(ranges: List[List[Int]]) =
  ranges.map({
    case head :: Nil => head.toString
    case head :: last :: Nil => s"$head-$last"
    case _ => ""
  }).mkString(",")

printRanges(
  sliceByRange(List(1, 2, 3, 5, 8, 9, 12, 13, 14, 19)))
// 1-3,5,8-9,12-14,19

printRanges(
  sliceByRange3elements(List(1, 2, 3, 5, 8, 9, 12, 13, 14, 19)))
// 1-3,5,8,9,12-14,19

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM