Metadata-Version: 2.4
Name: namedranges
Version: 1.0.0
Summary: Named ranges with utilities for index reflows
License: MIT
License-File: LICENSE
Author: Chris Czarnecki
Author-email: kjczarne@gmail.com
Requires-Python: >=3.10,<4.0
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Description-Content-Type: text/markdown

# `namedranges`

This lib provides a simple way to work with intervals/ranges in Python using a tuple representation for each interval and a string annotation.

## Installation

```bash
pip install namedranges
```

## Usage

### Core classes

The library provides three core classes:

- **`Segment`** — a single contiguous range `(start, end)`, parseable from tuples, strings, or other Segments.
- **`NamedRange`** — a named collection of `Segment` objects (e.g. a range "A" with segments `(1, 10)` and `(12, 13)`).
- **`NamedRanges`** — the main collection class holding multiple `NamedRange` objects.

### Constructing `NamedRanges`

The most common way to use this library is to pass a dict mapping names to lists of segments:

```python
from namedranges import NamedRanges

nr = NamedRanges.from_segments({
    "A": [(1, 10), (12, 13)],
    "B": ["30-35", "45-50"],
})

print(nr["A"].segments)  # [Segment(1, 10), Segment(12, 13)]
print(nr.to_dict())      # {"A": [(1, 10), (12, 13)], "B": [(30, 35), (45, 50)]}
```

For simple one-segment-per-name cases, use `from_flat_dict`:

```python
nr = NamedRanges.from_flat_dict({
    "1": (1, 5),
    "2": (6, 22),
    "3": (23, 26),
    "4": (27, 38),
})
```

Or use `from_dict` which auto-detects the format:

```python
# Detected as flat (values are tuples):
nr = NamedRanges.from_dict({"1": (1, 5), "2": (6, 22)})

# Detected as segmented (values are lists):
nr = NamedRanges.from_dict({"A": [(1, 10), (12, 13)]})
```

### Preconfigured classes with `NamedRangeFactory`

Use `NamedRangeFactory` to create a `NamedRanges` subclass with default settings for indexing style, interval openness, etc.:

```python
from namedranges import NamedRangeFactory

OneIndexed = NamedRangeFactory(indexing=1, right_side_closed=True)
nr = OneIndexed.from_segments({"X": ["1-5", "10-20"]})
print(nr.args.indexing)  # 1
```

### Operations

```python
from namedranges import NamedRanges, namedrange_args

nr = NamedRanges.from_flat_dict({
    "1": (1, 5),
    "2": (6, 22),
    "3": (23, 26),
    "4": (27, 38),
})

# Insert gaps (splits ranges that overlap with gap positions):
nr.add_gaps([(10, 10)])
print(nr.to_flat_dict())
# {'1': (1, 5), '2': (6, 9), '2-1': (11, 22), '3': (23, 26), '4': (27, 38)}

# Compute the complement (gaps between ranges):
nr = NamedRanges.from_flat_dict({"1": (1, 5), "2": (6, 22)}, namedrange_args(indexing=1))
complement = nr.complement()

# Reindex ranges to start from 0 or 1:
nr = NamedRanges.from_flat_dict({"1": (10, 15), "2": (21, 30)},
                                 namedrange_args(indexing=1, right_side_closed=True))
reindexed = nr.reindex(keep_gaps=True)

# Sort ranges:
sorted_ranges = nr.sorted()
```

### Working with `Segment` and `NamedRange`

```python
from namedranges import Segment, NamedRange

# Parse from various formats:
seg = Segment.parse("1-10")       # Segment(1, 10)
seg = Segment.parse((1, 10))      # Segment(1, 10)

# Useful methods:
seg.to_tuple()   # (1, 10)
seg.to_list()    # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
seg.length()     # 10

# NamedRange groups segments under a name:
nr = NamedRange.parse("A", [(1, 10), "12-13", Segment(20, 25)])
nr.to_tuples()   # [(1, 10), (12, 13), (20, 25)]
```

### Utility functions

```python
from namedranges import list_to_ranges, ranges_to_list

# Convert a list of integers to range expressions:
list_to_ranges([2, 3, 8, 15, 16, 17, 18, 20, 23, 24, 25])
# ['2-3', '8-8', '15-18', '20-20', '23-25']

# Convert range expressions back to lists:
ranges_to_list(['2-3', '8-8', '15-18'], flatten=True)
# [2, 3, 8, 15, 16, 17, 18]
```

