|
@@ -1,7 +1,8 @@
|
|
|
import decimal
|
|
import decimal
|
|
|
-from django.db.backends.postgresql.psycopg_any import NumericRange
|
|
|
|
|
from itertools import count, groupby
|
|
from itertools import count, groupby
|
|
|
|
|
|
|
|
|
|
+from django.db.backends.postgresql.psycopg_any import NumericRange
|
|
|
|
|
+
|
|
|
__all__ = (
|
|
__all__ = (
|
|
|
'array_to_ranges',
|
|
'array_to_ranges',
|
|
|
'array_to_string',
|
|
'array_to_string',
|
|
@@ -10,6 +11,7 @@ __all__ = (
|
|
|
'drange',
|
|
'drange',
|
|
|
'flatten_dict',
|
|
'flatten_dict',
|
|
|
'ranges_to_string',
|
|
'ranges_to_string',
|
|
|
|
|
+ 'ranges_to_string_list',
|
|
|
'shallow_compare_dict',
|
|
'shallow_compare_dict',
|
|
|
'string_to_ranges',
|
|
'string_to_ranges',
|
|
|
)
|
|
)
|
|
@@ -73,8 +75,10 @@ def shallow_compare_dict(source_dict, destination_dict, exclude=tuple()):
|
|
|
def array_to_ranges(array):
|
|
def array_to_ranges(array):
|
|
|
"""
|
|
"""
|
|
|
Convert an arbitrary array of integers to a list of consecutive values. Nonconsecutive values are returned as
|
|
Convert an arbitrary array of integers to a list of consecutive values. Nonconsecutive values are returned as
|
|
|
- single-item tuples. For example:
|
|
|
|
|
- [0, 1, 2, 10, 14, 15, 16] => [(0, 2), (10,), (14, 16)]"
|
|
|
|
|
|
|
+ single-item tuples.
|
|
|
|
|
+
|
|
|
|
|
+ Example:
|
|
|
|
|
+ [0, 1, 2, 10, 14, 15, 16] => [(0, 2), (10,), (14, 16)]
|
|
|
"""
|
|
"""
|
|
|
group = (
|
|
group = (
|
|
|
list(x) for _, x in groupby(sorted(array), lambda x, c=count(): next(c) - x)
|
|
list(x) for _, x in groupby(sorted(array), lambda x, c=count(): next(c) - x)
|
|
@@ -87,7 +91,8 @@ def array_to_ranges(array):
|
|
|
def array_to_string(array):
|
|
def array_to_string(array):
|
|
|
"""
|
|
"""
|
|
|
Generate an efficient, human-friendly string from a set of integers. Intended for use with ArrayField.
|
|
Generate an efficient, human-friendly string from a set of integers. Intended for use with ArrayField.
|
|
|
- For example:
|
|
|
|
|
|
|
+
|
|
|
|
|
+ Example:
|
|
|
[0, 1, 2, 10, 14, 15, 16] => "0-2, 10, 14-16"
|
|
[0, 1, 2, 10, 14, 15, 16] => "0-2, 10, 14-16"
|
|
|
"""
|
|
"""
|
|
|
ret = []
|
|
ret = []
|
|
@@ -135,6 +140,29 @@ def check_ranges_overlap(ranges):
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def ranges_to_string_list(ranges):
|
|
|
|
|
+ """
|
|
|
|
|
+ Convert numeric ranges to a list of display strings.
|
|
|
|
|
+
|
|
|
|
|
+ Each range is rendered as "lower-upper" or "lower" (for singletons).
|
|
|
|
|
+ Bounds are normalized to inclusive values using ``lower_inc``/``upper_inc``.
|
|
|
|
|
+ This underpins ``ranges_to_string()``, which joins the result with commas.
|
|
|
|
|
+
|
|
|
|
|
+ Example:
|
|
|
|
|
+ [NumericRange(1, 6), NumericRange(8, 9), NumericRange(10, 13)] => ["1-5", "8", "10-12"]
|
|
|
|
|
+ """
|
|
|
|
|
+ if not ranges:
|
|
|
|
|
+ return []
|
|
|
|
|
+
|
|
|
|
|
+ output: list[str] = []
|
|
|
|
|
+ for r in ranges:
|
|
|
|
|
+ # Compute inclusive bounds regardless of how the DB range is stored.
|
|
|
|
|
+ lower = r.lower if r.lower_inc else r.lower + 1
|
|
|
|
|
+ upper = r.upper if r.upper_inc else r.upper - 1
|
|
|
|
|
+ output.append(f"{lower}-{upper}" if lower != upper else str(lower))
|
|
|
|
|
+ return output
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def ranges_to_string(ranges):
|
|
def ranges_to_string(ranges):
|
|
|
"""
|
|
"""
|
|
|
Converts a list of ranges into a string representation.
|
|
Converts a list of ranges into a string representation.
|
|
@@ -151,12 +179,7 @@ def ranges_to_string(ranges):
|
|
|
"""
|
|
"""
|
|
|
if not ranges:
|
|
if not ranges:
|
|
|
return ''
|
|
return ''
|
|
|
- output = []
|
|
|
|
|
- for r in ranges:
|
|
|
|
|
- lower = r.lower if r.lower_inc else r.lower + 1
|
|
|
|
|
- upper = r.upper if r.upper_inc else r.upper - 1
|
|
|
|
|
- output.append(f"{lower}-{upper}" if lower != upper else str(lower))
|
|
|
|
|
- return ','.join(output)
|
|
|
|
|
|
|
+ return ','.join(ranges_to_string_list(ranges))
|
|
|
|
|
|
|
|
|
|
|
|
|
def string_to_ranges(value):
|
|
def string_to_ranges(value):
|