-1

I have a very large list of numbers. I need to pass this list of numbers as a URL query parameter. Since these lists can get so large, it could potentially cause the request URL to exceed the allowed length of a URL; also, it's a bit difficult to debug a string of sequential numbers (E.G. 1,2,3,..,500,782). To remedy these issues, I would like to convert the sequential number list string to one that is formatted using a range notation (E.G. -5..-3,1..500,782). How do I create this range notation string using Kotlin and how do I parse the string back to a collection of numbers also using Kotlin?

bsara
  • 7,940
  • 3
  • 29
  • 47

1 Answers1

1

This will convert a Collection<Int> to a string that uses the "range notation" specified:

fun Collection<Int>.toRangesString(): String {
  if (this.isEmpty()) {
    return ""
  }


  if (this.size <= 2) {
    return this.toSortedSet().joinToString(",")
  }


  val rangeStrings = mutableListOf<String>()

  var start: Int? = null
  var prev: Int? = null

  for (num in this.toSortedSet()) {
    if (prev == null) {
      start = num
      prev = num
      continue
    }

    if (num != (prev + 1)) {
      _addRangeString(rangeStrings, start!!, prev)
      start = num
      prev = num
      continue
    }

    prev = num
  }

  if (start != null) {
    _addRangeString(rangeStrings, start, prev!!)
  }

  return rangeStrings.joinToString(",")
}


private fun _addRangeString(rangeStrings: MutableList<String>, start: Int, prev: Int) {
  rangeStrings.add(
    when {
      (start == prev) -> start.toString()
      ((start + 1) == prev) -> "${start},${prev}"
      else -> "${start}..${prev}"
    }
  )
}

...and this will parse those range notated strings into a Set<Int>:

fun parseRangesString(str: String): Set<Int> {
  if (str.isBlank()) {
    return setOf()
  }


  val ranges = str.trim().split(",")
  val numbers = mutableListOf<Int>()

  for (range in ranges) {
    if (range.contains("..")) {
      val (start, end) = range.split("..")
      numbers.addAll(start.toInt()..end.toInt())
      continue
    }

    numbers.add(range.toInt())
  }

  return numbers.toSet()
}

...and, finally, even better than using a huge collection of numbers, you can use Kotlin's IntRange (or LongRange) class:

fun toIntRanges(str: String): Collection<IntRange> = _toRanges(str, ::_createIntRange)
fun toLongRanges(str: String): Collection<LongRange> = _toRanges(str, ::_createLongRange)

private fun <T : ClosedRange<*>> _toRanges(str: String, createRange: (start: String, end: String) -> T): Collection<T> {
  if (str.isBlank()) {
    return listOf()
  }

  val rangeStrs = str.trim().split(",")
  val ranges = mutableListOf<T>()

  for (rangeStr in rangeStrs) {
    if (rangeStr.contains("..")) {
      val (start, end) = rangeStr.split("..")
      ranges.add(createRange(start, end))
      continue
    }

    ranges.add(createRange(rangeStr, rangeStr))
  }

  return ranges.toList()
}


private fun _createIntRange(start: String, end: String) = IntRange(start.toInt(), end.toInt())
private fun _createLongRange(start: String, end: String) = LongRange(start.toLong(), end.toLong())

bsara
  • 7,940
  • 3
  • 29
  • 47