| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- import decimal
- from itertools import count, groupby
- __all__ = (
- 'array_to_ranges',
- 'array_to_string',
- 'deepmerge',
- 'drange',
- 'flatten_dict',
- 'shallow_compare_dict',
- )
- #
- # Dictionary utilities
- #
- def deepmerge(original, new):
- """
- Deep merge two dictionaries (new into original) and return a new dict
- """
- merged = dict(original)
- for key, val in new.items():
- if key in original and isinstance(original[key], dict) and val and isinstance(val, dict):
- merged[key] = deepmerge(original[key], val)
- else:
- merged[key] = val
- return merged
- def flatten_dict(d, prefix='', separator='.'):
- """
- Flatten nested dictionaries into a single level by joining key names with a separator.
- :param d: The dictionary to be flattened
- :param prefix: Initial prefix (if any)
- :param separator: The character to use when concatenating key names
- """
- ret = {}
- for k, v in d.items():
- key = separator.join([prefix, k]) if prefix else k
- if type(v) is dict:
- ret.update(flatten_dict(v, prefix=key, separator=separator))
- else:
- ret[key] = v
- return ret
- def shallow_compare_dict(source_dict, destination_dict, exclude=tuple()):
- """
- Return a new dictionary of the different keys. The values of `destination_dict` are returned. Only the equality of
- the first layer of keys/values is checked. `exclude` is a list or tuple of keys to be ignored.
- """
- difference = {}
- for key, value in destination_dict.items():
- if key in exclude:
- continue
- if source_dict.get(key) != value:
- difference[key] = value
- return difference
- #
- # Array utilities
- #
- def array_to_ranges(array):
- """
- 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)]"
- """
- group = (
- list(x) for _, x in groupby(sorted(array), lambda x, c=count(): next(c) - x)
- )
- return [
- (g[0], g[-1])[:len(g)] for g in group
- ]
- def array_to_string(array):
- """
- Generate an efficient, human-friendly string from a set of integers. Intended for use with ArrayField.
- For example:
- [0, 1, 2, 10, 14, 15, 16] => "0-2, 10, 14-16"
- """
- ret = []
- ranges = array_to_ranges(array)
- for value in ranges:
- if len(value) == 1:
- ret.append(str(value[0]))
- else:
- ret.append(f'{value[0]}-{value[1]}')
- return ', '.join(ret)
- #
- # Range utilities
- #
- def drange(start, end, step=decimal.Decimal(1)):
- """
- Decimal-compatible implementation of Python's range()
- """
- start, end, step = decimal.Decimal(start), decimal.Decimal(end), decimal.Decimal(step)
- if start < end:
- while start < end:
- yield start
- start += step
- else:
- while start > end:
- yield start
- start += step
|