Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

canonical-interval

Self-normalizing interval data structure that automatically maintains valid form with useful utility methods.

The CanonicalInterval class represents left-closed, right-open intervals ([start, stop)) that, given a comparator (default: operator.lt), automatically enforce the following standardized valid form:

  • Guarantees start <= stop according to the comparator
    • Empty intervals become [start, start)
  • All operations maintain canonical form

Key Features:

  • Automatic Canonicalization
  • Custom Comparison: Supports any comparator function
  • Useful Utility Methods:
    • __bool__: Determines if the current interval is empty.
    • relative_position_of(item): Determines if item is before/within/after the current interval.
    • three_way_split(target): Split a target interval into three parts:
      • Parts of target before current interval
      • Overlapping parts between target and current interval
      • Parts of target after current interval
  • Supports Python 2+

Installation

pip install canonical-interval

Examples

from canonical_interval import CanonicalInterval

# Automatically becomes [20, 20) (empty interval)
assert CanonicalInterval(20, 10).start == 20
assert CanonicalInterval(20, 10).stop == 20
assert not CanonicalInterval(20, 10)
assert 15 not in CanonicalInterval(20, 10)

# Automatically becomes ['z', 'z') (empty interval)
assert CanonicalInterval('z', 'a').start == 'z'
assert CanonicalInterval('z', 'a').stop == 'z'
assert not CanonicalInterval('z', 'a')
assert 'm' not in CanonicalInterval('z', 'a')

# Respects custom comparator
# [20, 10) (non-empty interval)
assert CanonicalInterval(20, 10, comparator=lambda a, b: a > b).start == 20
assert CanonicalInterval(20, 10, comparator=lambda a, b: a > b).stop == 10
assert CanonicalInterval(20, 10, comparator=lambda a, b: a > b)
assert 15 in CanonicalInterval(20, 10, comparator=lambda a, b: a > b)

# Respects custom comparator
# ['z', 'a') (non-empty interval)
assert CanonicalInterval('z', 'a', comparator=lambda a, b: a > b).start == 'z'
assert CanonicalInterval('z', 'a', comparator=lambda a, b: a > b).stop == 'a'
assert CanonicalInterval('z', 'a', comparator=lambda a, b: a > b)
assert 'm' in CanonicalInterval('z', 'a', comparator=lambda a, b: a > b)

# Various three-way splits

## Non-empty current
non_empty_current = CanonicalInterval(0, 2)

### Non-empty targets
assert non_empty_current.three_way_split(CanonicalInterval(-3, -1)) == (
    CanonicalInterval(-3, -1), CanonicalInterval(0, 0), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(-2, 0)) == (
    CanonicalInterval(-2, 0), CanonicalInterval(0, 0), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(-1, 1)) == (
    CanonicalInterval(-1, 0), CanonicalInterval(0, 1), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(0, 2)) == (
    CanonicalInterval(0, 0), CanonicalInterval(0, 2), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(0.5, 1.5)) == (
    CanonicalInterval(0, 0), CanonicalInterval(0.5, 1.5), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(1, 3)) == (
    CanonicalInterval(0, 0), CanonicalInterval(1, 2), CanonicalInterval(2, 3)
)

assert non_empty_current.three_way_split(CanonicalInterval(2, 4)) == (
    CanonicalInterval(0, 0), CanonicalInterval(2, 2), CanonicalInterval(2, 4)
)

assert non_empty_current.three_way_split(CanonicalInterval(3, 5)) == (
    CanonicalInterval(0, 0), CanonicalInterval(2, 2), CanonicalInterval(3, 5)
)

### Empty targets
assert non_empty_current.three_way_split(CanonicalInterval(-1, -1)) == (
    CanonicalInterval(-1, -1), CanonicalInterval(0, 0), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(0, 0)) == (
    CanonicalInterval(0, 0), CanonicalInterval(0, 0), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(1, 1)) == (
    CanonicalInterval(0, 0), CanonicalInterval(1, 1), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(2, 2)) == (
    CanonicalInterval(0, 0), CanonicalInterval(2, 2), CanonicalInterval(2, 2)
)

assert non_empty_current.three_way_split(CanonicalInterval(3, 3)) == (
    CanonicalInterval(0, 0), CanonicalInterval(2, 2), CanonicalInterval(3, 3)
)

## Empty current
empty_current = CanonicalInterval(0, 0)

### Non-empty targets
assert empty_current.three_way_split(CanonicalInterval(-3, -1)) == (
    CanonicalInterval(-3, -1), CanonicalInterval(0, 0), CanonicalInterval(0, 0)
)

assert empty_current.three_way_split(CanonicalInterval(-2, 0))  == (
    CanonicalInterval(-2, 0), CanonicalInterval(0, 0), CanonicalInterval(0, 0)
)

assert empty_current.three_way_split(CanonicalInterval(-1, 1)) == (
    CanonicalInterval(-1, 0), CanonicalInterval(0, 0), CanonicalInterval(0, 1)
)

assert empty_current.three_way_split(CanonicalInterval(0, 2)) == (
    CanonicalInterval(0, 0), CanonicalInterval(0, 0), CanonicalInterval(0, 2)
)

assert empty_current.three_way_split(CanonicalInterval(1, 3)) == (
    CanonicalInterval(0, 0), CanonicalInterval(0, 0), CanonicalInterval(1, 3)
)

### Empty targets
assert empty_current.three_way_split(CanonicalInterval(-1, -1)) == (
    CanonicalInterval(-1, -1), CanonicalInterval(0, 0), CanonicalInterval(0, 0)
)

assert empty_current.three_way_split(CanonicalInterval(0, 0))  == (
    CanonicalInterval(0, 0), CanonicalInterval(0, 0), CanonicalInterval(0, 0)
)

assert empty_current.three_way_split(CanonicalInterval(1, 1)) == (
    CanonicalInterval(0, 0), CanonicalInterval(0, 0), CanonicalInterval(1, 1)
)

Contributing

Contributions are welcome! Please submit pull requests or open issues on the GitHub repository.

License

This project is licensed under the MIT License.

Release files for canonical-interval 0.1.0a2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for canonical-interval 0.1.0a2
File Size Uploaded
canonical_interval-0.1.0a2.tar.gz 4.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for canonical-interval 0.1.0a2
File Interpreter ABI Platform
canonical_interval-0.1.0a2-py2.py3-none-any.whl Python 3, Python 2 none any Details

Total release size: 9.7 kB

Release files / canonical_interval-0.1.0a2.tar.gz

Download URL canonical_interval-0.1.0a2.tar.gz
Size 4.7 kB
Tags Source
SHA-256 checksum
How to use checksums
5f59c044c276fc6d076f175d9e32b4b731d44925bf95c5a0b8ed49685e0d1b11
BLAKE2b-256 checksum
How to use checksums
c63e7efb4a10863eb11abf59cf056c9520a023ee916ed35766ebff76a7dbd16e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.2

Release files / canonical_interval-0.1.0a2-py2.py3-none-any.whl

Download URL canonical_interval-0.1.0a2-py2.py3-none-any.whl
Size 5.0 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
8ca8eee4ac9b5b1f352a7c03e91ebd273df01f30741dd4f9162e05f2f66766e4
BLAKE2b-256 checksum
How to use checksums
1c8b8bb40d329aa08bc98befa1d0a11e0bd6096da77fcc0dfbc92bfb42dfe56c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.2

Release history Release notifications | RSS feed

This release

0.1.0a2 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page