Stop Writing Classes
See Stop writing classes by Jack Diederich. Short summary: if you instantiate a class, use the class once, and then throw it away, (eg, print(PrettySet(data))
) you should just write a function.
def pretty_set(data: set[int]) -> str: """Create a string representation of a set of integers, with the integers in increasing order, and consecutive integers represented by a start value, two dots, and the end value. >>> pretty_set({1, 5, 12, 2, 3, 13, 6, 7}) '{1..3, 5..7, 12, 13}' """ ... implementation here ...
Minor nit
The empty set will return the string {}
which appears to be an empty dictionary, not an empty set.
Readability
Your code needs blank lines before method definitions.
You need to use type hints consistently. In def _seg_to_str(segment: list):
, segment should be list[int]
, and the return type should be -> str
.
groups = itertools.groupby(enumerate(sorted(self)), lambda x: x[1] - x[0])
is very dense and opaque. It returns something like ((1, [(0, 1), (1, 2), (2, 3)]), (3, [(3, 6), (4, 7), (5, 8)]), (4, [(6, 10), (7, 11)]), (6, [(8, 14), (9, 15), (10, 16)]), (7, [(11, 18)]), (8, [(12, 20), (13, 21), (14, 22), (15, 23), (16, 24), (17, 25)]))
, which is an iterable of tuples of an int and a list of tuples of two ints. No comment in sight as to what those mean, so maintainability is low.
Next, you have [list(map(operator.itemgetter(1), g)) for _, g in groups]
, which is making a nested list of lists ... plus for _, g in groups
is extracting the second item of elements of groups, and operator.itemgetter(1)
is also extracting the second item of something. Again, no comment in sight.
Different implementation
The segments = [list(...) for ...]
statement could be turned into a generator to prevent unnecessary construction of the outer loop, but the inner lists all get unnecessarily created. A lighter weight implementation could be made with a function that takes elements one-at-a-time, collect sequential elements, and yield
string of either single elements or ranges. No tuples or lists need to be constructed.
Perhaps:
def pretty_set(data: set[int]) -> str: """Create a string representation of a set of integers, with the integers in increasing order, and consecutive integers represented by a start value, two dots, and the end value. >>> pretty_set({1, 5, 12, 2, 3, 13, 6, 7}) '{1..3, 5..7, 12, 13}' """ def segments(): it = iter(sorted(data)) start = end = next(it) for item in it: if item == end + 1: end = item else: if end > start + 1: yield f"{start}..{end}" else: yield str(start) if start != end: yield str(end) start = item end = item if end > start + 1: yield f"{start}..{end}" else: yield str(start) if start != end: yield str(end) if len(data) < 2: return str(data) return "{" + ", ".join(segments()) + "}" data = {1, 2, 3, 6, 7, 8, 10, 11, 14, 15, 16, 18, 20, 21, 22, 23, 24, 25} print(pretty_set(data))
It is longer, but perhaps it might be more understandable, and possibly a little more efficient.