Created
January 28, 2023 05:06
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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