My solution:
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
}
Usage:
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))
If you wish to keep middle values you can use the following example:
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)
}
Usage:
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))
When range size at least 3 elements:
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)
}
Usage (note that (8,9) are not range now):
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))
You can define printRanges
method to more visual output:
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