如何高效/优雅地提取连续的Ints范围?

时间:2016-05-19 16:22:28

标签: scala range seq

让我们从一个整数序列开始,如:

val seq = List(1,2,3,4,5,6,9,10,11,14,15,16,18)

我想获得一系列表示连续集的对,例如:

val ranges = List(1,6,9,11,14,16,18,18)

替代格式Seq[(Int,Int)]也可以接受:

val ranges = List((1,6),(9,11),(14,16),(18,18))

说明:   - 范围1..611..16中的整数位于seq   - 整数18位于seq,但没有后继者或前任,因此在18,18中显示为ranges

请注意,单元素序列应始终作为对报告,例如:

val seq = List(18, 19, 21)

应该给出结果:

val ranges = List(18,19,21,21)

或者,如果您更喜欢 Tuple2样式

val ranges = List((18,19),(21,21))

我希望有一个函数从ranges派生seq;解决方案(由同事提供)是:

def toRanges(a: Seq[Int]): Seq[Int] = {
  val min = a.map(x => (x, a contains x - 1)).filter(!_._2).map(_._1)
  val max = a.map(x => (x, a contains x + 1)).filter(!_._2).map(_._1)
  return (min ++ max).sorted
}

确实很优雅,但由于contains的使用,我不确定效率。

任何人都可以在效率或优雅方面提供更好的解决方案吗?

谢谢!

2 个答案:

答案 0 :(得分:3)

如果输入是有序的(或者您愿意先对其进行排序),您可以使用foldLeft一次性完成这一步骤(嗯,两次使用reverse,但是这是一个使用列表的工件,如果你愿意放弃一些优雅,可以避免这样做:

seq.foldLeft[List[(Int, Int)]](Nil) {
  case ((a, b) :: rest, i) if i == b + 1 => (a, i) :: rest
  case (acc, i) => (i, i) :: acc
}.reverse

在这种情况下,我们提供以下内容:

res0: List[(Int, Int)] = List((1,6), (9,11), (14,16), (18,18))

对于每个元素,我们检查它是否是我们添加的最后一个范围结束的后继。如果是,我们将替换该范围内的结束。如果不是,我们开始新的范围。

答案 1 :(得分:2)

或许这样的事情:

(None +: seq.toStream.map(x => Some(x)) :+ None).sliding(2).flatMap {
    case Seq(None, Some(b)) => List(b)
    case Seq(Some(a), None) => List(a)
    case Seq(Some(a), Some(b)) if b - a == 1 => Nil
    case Seq(Some(a), Some(b)) => List(a,b)
}.toList