按范围Scala对列表中的元素进行分组

时间:2016-07-13 11:32:30

标签: list 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 function for grouping consecutive identical elements

但它正在将相同的元素分组到同一个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)
    }

但它没有用。

4 个答案:

答案 0 :(得分: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))

答案 1 :(得分:1)

这是另一个例子:

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

有点复杂,但有效......

答案 2 :(得分:0)

解释你引用的问题的答案:

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

答案 3 :(得分:0)

我的解决方案:

int

用法:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <defaultDocument>
            <files>
                <clear />
                <add value="index.php" />
            </files>
        </defaultDocument>
        <rewrite>
            <rules>
                <clear />
                <rule name="WordPress Rule" stopProcessing="true">
                    <match url=".*" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="false">
                        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                        <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                    </conditions>
                    <action type="Rewrite" url="index.php" />
                </rule>
            </rules>
            <outboundRules>
                <clear />
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>

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

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

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

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

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

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

您可以定义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) } 方法以获得更多视觉输出:

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