Skip to content

Instantly share code, notes, and snippets.

@kkew3
Created January 28, 2023 05:06
Show Gist options
  • Save kkew3/a2269aa5b018e14beca4e24b75a4d294 to your computer and use it in GitHub Desktop.
Save kkew3/a2269aa5b018e14beca4e24b75a4d294 to your computer and use it in GitHub Desktop.
Compress a list of integers into a list of ranges. For example, `1 2 3 4 5 7 8 9` becomes `1:6 7:10`. The code can be used both as a library and as an executable.
#!/usr/bin/env python3
def nums2ranges(nums):
"""
>>> list(nums2ranges([]))
[]
>>> list(nums2ranges([0, 1, 2, 3, 4]))
[slice(0, 5, None)]
>>> list(nums2ranges([0, 1, 4, 5, 6]))
[slice(0, 2, None), slice(4, 7, None)]
>>> list(nums2ranges([0, 2, 7, 9, 10, 13]))
[slice(0, 1, None), slice(2, 3, None), slice(7, 8, None), slice(9, 11, None), slice(13, 14, None)]
>>> list(nums2ranges([0, 0, 0]))
[slice(0, 1, None), slice(0, 1, None), slice(0, 1, None)]
>>> list(nums2ranges([0, 1, 2, 1, 2, 3]))
[slice(0, 3, None), slice(1, 4, None)]
"""
start = None
stop = None
counter = None
for x in nums:
if start is None:
start = x
counter = 0
stop = x + 1
elif x - start == counter:
stop = x + 1
else:
yield slice(start, stop)
start = x
counter = 0
stop = x + 1
counter += 1
if start is not None:
yield slice(start, stop)
if __name__ == '__main__':
import argparse
import sys
def make_parser():
parser = argparse.ArgumentParser(
description=('Convert integers to ranges. For example, given '
'numbers 1, 2, 3, 6, 7, 10, 0, 1, '
'ranges 1:4, 6:8, 10:11, 0:2 '
'will be printed. Numbers are expected from stdin '
'one per line. Ranges are printed to stdout one per '
'line.'),
epilog=('Return code: 0) success; 1) non-integer line occurred '
'when in strict mode (see --strict flag); 130) when '
'aborted by Ctrl-c'))
parser.add_argument(
'-s',
'--strict',
action='store_true',
help=('by default non-integer lines will be omitted; using this '
'flag aborts and raises error upon invalid line'))
parser.add_argument(
'-i',
'--inclusive',
action='store_true',
help=('making the second token of a range (a.k.a. the stop), '
'inclusive'))
return parser
def parse_ints(lines, strict):
for l in map(str.strip, lines):
try:
x = int(l)
except ValueError:
if strict:
raise
else:
yield x
def main():
args = make_parser().parse_args()
try:
if args.inclusive:
for sl in nums2ranges(parse_ints(sys.stdin, args.strict)):
print('{}:{}'.format(sl.start, sl.stop - 1))
else:
for sl in nums2ranges(parse_ints(sys.stdin, args.strict)):
print('{}:{}'.format(sl.start, sl.stop))
except ValueError as err:
print(err, file=sys.stderr)
return 1
except BrokenPipeError:
sys.stderr.close()
return 130
except KeyboardInterrupt:
return 130
return 0
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment