intranges.py 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. """
  2. Given a list of integers, made up of (hopefully) a small number of long runs
  3. of consecutive integers, compute a representation of the form
  4. ((start1, end1), (start2, end2) ...). Then answer the question "was x present
  5. in the original list?" in time O(log(# runs)).
  6. """
  7. import bisect
  8. def intranges_from_list(list_: list[int]) -> tuple[int, ...]:
  9. """Represent a list of integers as a sequence of ranges:
  10. ((start_0, end_0), (start_1, end_1), ...), such that the original
  11. integers are exactly those x such that start_i <= x < end_i for some i.
  12. Ranges are encoded as single integers (start << 32 | end), not as tuples.
  13. """
  14. sorted_list = sorted(list_)
  15. ranges = []
  16. last_write = -1
  17. for i in range(len(sorted_list)):
  18. if i + 1 < len(sorted_list) and sorted_list[i] == sorted_list[i + 1] - 1:
  19. continue
  20. current_range = sorted_list[last_write + 1 : i + 1]
  21. ranges.append(_encode_range(current_range[0], current_range[-1] + 1))
  22. last_write = i
  23. return tuple(ranges)
  24. def _encode_range(start: int, end: int) -> int:
  25. return (start << 32) | end
  26. def _decode_range(r: int) -> tuple[int, int]:
  27. return (r >> 32), (r & ((1 << 32) - 1))
  28. def intranges_contain(int_: int, ranges: tuple[int, ...]) -> bool:
  29. """Determine if `int_` falls into one of the ranges in `ranges`."""
  30. tuple_ = _encode_range(int_, 0)
  31. pos = bisect.bisect_left(ranges, tuple_)
  32. # we could be immediately ahead of a tuple (start, end)
  33. # with start < int_ <= end
  34. if pos > 0:
  35. left, right = _decode_range(ranges[pos - 1])
  36. if left <= int_ < right:
  37. return True
  38. # or we could be immediately behind a tuple (int_, end)
  39. if pos < len(ranges):
  40. left, _ = _decode_range(ranges[pos])
  41. if left == int_:
  42. return True
  43. return False