Skip to main content

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

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

canonical_interval-0.1.0a2.tar.gz (4.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

canonical_interval-0.1.0a2-py2.py3-none-any.whl (5.0 kB view details)

Uploaded Python 2Python 3

File details

Details for the file canonical_interval-0.1.0a2.tar.gz.

File metadata

  • Download URL: canonical_interval-0.1.0a2.tar.gz
  • Upload date:
  • Size: 4.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.2

File hashes

Hashes for canonical_interval-0.1.0a2.tar.gz
Algorithm Hash digest
SHA256 5f59c044c276fc6d076f175d9e32b4b731d44925bf95c5a0b8ed49685e0d1b11
MD5 528af3f873a00c2a047b454cd720999b
BLAKE2b-256 c63e7efb4a10863eb11abf59cf056c9520a023ee916ed35766ebff76a7dbd16e

See more details on using hashes here.

File details

Details for the file canonical_interval-0.1.0a2-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for canonical_interval-0.1.0a2-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 8ca8eee4ac9b5b1f352a7c03e91ebd273df01f30741dd4f9162e05f2f66766e4
MD5 7a8429a3938d57274dd7de7088acfe1c
BLAKE2b-256 1c8b8bb40d329aa08bc98befa1d0a11e0bd6096da77fcc0dfbc92bfb42dfe56c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page