[英]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.