使用 Kotlin 将数字集合转换为范围格式的字符串

Convert collection of numbers to range formatted string with Kotlin

我有一个非常大的数字列表。我需要将此数字列表作为 URL 查询参数传递。由于这些列表可能会变得如此之大,因此可能会导致请求 URL 超过 the allowed length of a URL;此外,调试一串序列号(例如 1,2,3,..,500,782)有点困难。为了解决这些问题,我想将序列号列表字符串转换为使用范围符号(例如 -5..-3,1..500,782)格式化的字符串。如何使用 Kotlin 创建此范围符号字符串以及如何使用 Kotlin 将字符串解析回数字集合?

这会将 Collection<Int> 转换为使用指定 "range notation" 的字符串:

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

...这会将那些范围标记的字符串解析为 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()
}

...最后,比使用大量数字更好的是,您可以使用 Kotlin 的 IntRange(或 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())