Merge ranges of numbers/IP addresses
Project description
range-merge
A Python library for merging ranges of numbers or other comparable items.
Installation
pip install range-merge
Features
- Merge (compact) ranges into single continuous ranges
- Fully customizable for non-integer types (IP addresses, dates, custom objects)
Usage
Range Merging (Compacting)
Ranges are, by default, represented as tuples of (start, end).
If use_attr is true, then, by default, tuples also include a third
element (the "attribute").
from range_merge import merge
# Merge / Compact ranges
ranges = [(1, 5), (3, 8), (10, 15)]
result = merge(ranges)
# Result: [(1, 8), (10, 15)]
# Merge / Compcat ranges with an attribute
ranges = [(1, 10, "foo"), (3, 8, "bar")]
result = merge(ranges, use_attr=True)
# Result: [(1, 2, "foo"), (3, 8, "bar"), (9, 10, "foo")]
Range Merging (Compacting) with Attributes
Provide attributes to ensure that only ranges with the same attributes are merged together.
For instance, let's say I had a product list as follows:
products = [
(0, 99, "soup"),
(57, 57, "cereal"),
(100, 199, "cereal"),
]
In this case, all product IDs between 0 and 99 are soups, except for 57, which may have been miscategorized initially, but it's not possible to change.
If I wanted to have non-overlapping ranges that captured this exception (I.E. any product ID would only have one range that applied to it), I could do the following:
from range_merge import merge
# insert products structure from above
result = merge(products, use_attr=True)
# Result: [
# (0, 56, "soup"),
# (57, 57, "cereal"),
# (58, 99, "soup"),
# (100, 199, "cereal"),
# ]
Merging Discrete Values
For merging individual, discrete, values, use merge_discrete:
from range_merge import merge_discrete
values = [1, 2, 3, 5, 6, 7, 10]
result = merge_discrete(values)
# Result: [(1, 3), (5, 7), (10, 10)]
Custom Types
Non-Integers
The library supports non-integer types by providing custom comparison
(cmp), increment (after), and decrement (before) functions.
For instance, imagine we have a list of chair people by term (so someone serving two consecutive terms would have two rows). We want to get a compacted list (i.e. consecutive terms should be merged). The dates should be represented as strings, using USA's weird date format.
In this case, the before takes a start or end value and returns
the string representing the previous day's date. after is similar,
but represents the following day.
The cmp function returns -1 if the first argument comes before
the second, 0 if they are the same, and 1 if the first is larger
than the second argument.
from datetime import datetime, timedelta
from range_merge import merge
terms = [
("3/1/2024", "3/5/2024", "Betty"),
("1/6/2025", "1/7/2025", "Ash"),
("1/8/2025", "1/7/2026", "Ash"),
]
def to_date(x):
return datetime.strptime(x, "2/1/2025")
def to_str(x):
return f"{x.month}/{x.day}/{x.year}" # strftime adds leading
def date_cmp(x, y):
a = to_date(x)
b = to_date(y)
if a < b:
return -1
elif a == b:
return 0
else:
return 1
result = merge(
terms,
use_attr=True,
before=lambda x: to_str(to_date(x) - timedelta(days=1)),
after=lambda x: to_str(to_date(x) + timedelta(days=1)),
cmp=date_cmp,
)
# Result will be: [
# ("3/1/2024", "3/5/2024", "Betty"),
# ("1/6/2025", "1/7/2026", "Ash"),
# ]
Non-Tuple Ranges
Imagine you have a list of objects representing products (from the "Range
Merging (Compacting) with Attributes" example above, but you want to represent
them as a custom ProductGroup class:
In this case, three callables are used:
start: This is the accessor for thestartvalue of the custom objectend: This is the accessor for theendvalue of the custom objectnew: This creates a new object, and takes three parameters (start, end, and attribute).
Note that we don't have to specify use_attr=True since we are
providing a custom attr callable.
from dataclasses import dataclass
from range_merge import merge
@dataclass
class ProductGroup:
low: int
high: int
group: str
products = [
ProductGroup(low=0, high=99, group="soup"),
ProductGroup(low=57, high=57, group="cereal"),
ProductGroup(low=100, high=199, group="cereal"),
]
result = merge(
products,
start=lambda p: p.low,
end=lambda p: p.high,
attr=lambda p: p.group,
new=lambda s, e, attr: ProductGroup(low=s, high=e, group=attr),
)
# Result: [
# ProductGroup(0, 56, "soup"),
# ProductGroup(57, 57, "cereal"),
# ProductGroup(58, 99, "soup"),
# ProductGroup(100, 199, "cereal")
]
Merging IP Ranges
If you want to merge a list of IP ranges, you can use merge_ip_ranges.
See the next section for CIDR ranges rather than IP ranges.
There is no option to merge IP ranges without attributes (attributes
must match to merge), as Python's ipaddress.collapse_addresses() handles
this functionality.
from ipaddress import IPv4Address, IPv6Address
from range_merge import merge_ip_ranges
src = [
("1.0.0.0", "1.255.240.0", "foo"),
("1.255.240.1", "2.0.255.255", "foo"),
("2000::", "2fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "foo"),
("3000::", "3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "foo"),
]
result = merge_ip_ranges(src)
# Result: [
# (IPv4Address("1.0.0.0"), IPv4Address("2.0.255.255"), "foo"),
# (IPv6Address("2000::"), IPv6Address("3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), "foo"),
# ]
Merging CIDR Ranges
If you want to merge a list of CIDR ranges, you can use merge_cidr_ranges.
There is no option to merge CIDR ranges without attributes (attributes
must match to merge), as Python's ipaddress.collapse_addresses() handles
this functionality.
from ipaddress import IPv4Network, IPv6Network
from range_merge import merge_cidr_ranges
src = [
("1.0.0.0/8", "foo"),
("1.255.240.0/24", "bar"),
("2000::/4", "foo"),
("3000::/4", "foo"),
]
result = merge_cidr_ranges(src)
# Result: [
# (IPv4Network("1.0.0.0/9"), "foo"),
# (IPv4Network("1.128.0.0/10"), "foo"),
# (IPv4Network("1.192.0.0/11"), "foo"),
# (IPv4Network("1.224.0.0/12"), "foo"),
# (IPv4Network("1.240.0.0/13"), "foo"),
# (IPv4Network("1.248.0.0/14"), "foo"),
# (IPv4Network("1.252.0.0/15"), "foo"),
# (IPv4Network("1.254.0.0/16"), "foo"),
# (IPv4Network("1.255.0.0/17"), "foo"),
# (IPv4Network("1.255.128.0/18"), "foo"),
# (IPv4Network("1.255.192.0/19"), "foo"),
# (IPv4Network("1.255.224.0/20"), "foo"),
# (IPv4Network("1.255.240.0/24"), "bar"),
# (IPv4Network("1.255.241.0/24"), "foo"),
# (IPv4Network("1.255.242.0/23"), "foo"),
# (IPv4Network("1.255.244.0/22"), "foo"),
# (IPv4Network("1.255.248.0/21"), "foo"),
# (IPv6Network("2000::/3"), "foo"),
# ]
API Reference
merge(ranges, **options)
Merge a sequence of ranges.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
ranges |
Sequence |
required | The ranges to merge |
start |
Callable |
lambda r: r[0] |
Extract start value from a range |
end |
Callable |
lambda r: r[1] |
Extract end value from a range |
before |
Callable |
lambda x: x - 1 |
Return the value before x |
after |
Callable |
lambda x: x + 1 |
Return the value after x |
new |
Callable |
Creates range object | Create a new range from (start, end, attr) |
attr |
Callable |
lambda r: r[2] |
Extract attribute from a range |
use_attr |
bool |
False |
Whether to use attributes (if no attr is provided when calling) |
cmp |
Callable |
Default comparator | Custom comparison function |
The start and end callables each take a single argument, the range
object being used.
The before and after callables also take a single argument, but this
is a discrete value, not a range.
The new callable takes three parameters (start, end, attr). The third
parameter is passed as None if attributes aren't being used.
Returns: list of merged ranges.
merge_discrete(values, **options)
Merge a sequence of discrete values into ranges.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
values |
Sequence |
required | The discrete values to merge |
before |
Callable |
lambda x: x - 1 |
Return the value before x |
after |
Callable |
lambda x: x + 1 |
Return the value after x |
cmp |
Callable |
Default comparator | Custom comparison function |
For details on before, after, and cmp, see the merge() section.
Returns: list of (start, end) tuples.
merge_ip_ranges(values, **options)
Merge a sequence of ip range values into consolidated ranges.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
ranges |
Sequence |
required | The IP ranges to merge |
start |
Callable |
lambda r: r[0] |
Return the starting IP address in the range |
end |
Callable |
lambda r: r[1] |
Return the ending IP address in the range |
new |
Callable |
Creates range object | Create a new range from (start, end, attr) |
attr |
Callable |
lambda r: r[2] |
Extract attribute from a range |
cmp |
Callable |
Default comparator | Custom comparison function |
For details on start, end, attr, and cmp, see the merge() section.
Returns: list of IP ranges. All addresses are converted to either an
IPv4Address or an IPv6Address
merge_cidr_ranges(values, **options)
Merge a sequence of CIDR range values into consolidated ranges.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
ranges |
Sequence |
required | The CIDR ranges to merge |
new |
Callable |
Creates range object | Create a new range from (CIDR, attr) |
cidr |
Callable |
lambda x: r[0] |
Return the CIDR in the range |
attr |
Callable |
lambda r: r[1] |
Extract attribute from a range |
The cidr callable takes a single argument, the range
object being used.
The new callable takes two parameters (cidr, attr).
For details on attr see the merge() section.
Returns: list of IP network. All addresses are converted to either
an IPv4Network or an IPv6Network
Exceptions
ImproperRangeEndBeforeStart: Raised when a range has an end value that comes before its start value (using the default or customcmpcallable)
License
BSD-2-Clause
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file range_merge-0.1.0.tar.gz.
File metadata
- Download URL: range_merge-0.1.0.tar.gz
- Upload date:
- Size: 8.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ffffde7f88db00b4f062b82c3124a5be09cb409237162d8d76e8a23835a5bf5f
|
|
| MD5 |
6603151ec70d725b3cc87090d70b98ec
|
|
| BLAKE2b-256 |
106c9e94cd5e2ca879d50fe05ea5a19b0d7b674751e4fad4064abcc4895a894a
|
File details
Details for the file range_merge-0.1.0-py3-none-any.whl.
File metadata
- Download URL: range_merge-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05bceae23ffe7fed6726cc0feb75d001c1a411584f77a944fbb833e4f0c97508
|
|
| MD5 |
6994e8b94e229d8526b6079bdab46750
|
|
| BLAKE2b-256 |
6cc456022691c3838e38b65175b7fe2a36fe1251e6e142d8b9f4483be0f0ab87
|