【问题标题】:Group elements in a list by range Scala按范围Scala对列表中的元素进行分组
【发布时间】:2016-07-13 11:32:30
【问题描述】:

考虑 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 function for grouping consecutive identical elements

但它适用于在同一个列表中对相同的元素进行分组。

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

我试过这段代码

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中对连续的数字进行分组
  • 你自己实现的时候遇到了什么问题?
  • 哦,对不起,现在我明白了……xD
  • 比平时稍微复杂一点的情况,20分钟后根本没有解决方案:)

标签: list scala


【解决方案1】:

尾递归解

代码

请注意,您也可以使用List[(Int,Int)] 作为结果类型,而不是List[List[Int]]。这将反映这样一个事实,即结果是更合适的范围的List。当然,对于单例范围,您不能将 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))

【讨论】:

  • 这似乎不起作用,如图所示:scala&gt; split(List(4, 5, 6, 7, 8, 12, 13, 14, 17, 23, 24, 25)) res2: List[List[Int]] = List(List(4, 8), List(12, 12), List(13, 13), List(14, 14), List(17, 17), List(23, 23), List(24, 24), List(25, 25))我错过了什么吗?
  • @Toby 谢谢,我在以后的编辑中引入了一个错误。现在已经修好了。
【解决方案2】:

这是另一个例子:

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))

有点复杂,但它确实有效......

【讨论】:

    【解决方案3】:

    复述您引用的问题的答案:

    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 检查每个元素是否恰好是“下一个”整数(数字应该与索引“一起”增加)。然后 - 只取段的“边界”并递归地移动到列表的其余部分。

    【讨论】:

      【解决方案4】:

      我的解决方案:

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-11-26
        • 2019-07-27
        • 2019-02-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-22
        相关资源
        最近更新 更多