odsek 0.1.0

Lazy, pull-based composition of mathematical interval sets with open/closed endpoints, no_std-compatible.
Documentation
# odsek

[![Crates.io](https://img.shields.io/crates/v/odsek.svg)](https://crates.io/crates/odsek)
[![Docs.rs](https://docs.rs/odsek/badge.svg)](https://docs.rs/odsek)
[![CI](https://github.com/ra1u/odsek/actions/workflows/ci.yml/badge.svg)](https://github.com/ra1u/odsek/actions/workflows/ci.yml)
[![License: MIT OR Apache-2.0](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)
[![MSRV](https://img.shields.io/badge/MSRV-1.70-blue.svg)](#minimum-supported-rust-version)

Lazy, pull-based composition of mathematical interval sets — `no_std`-compatible, allocation-free.

An `Intervals` is an ordered, non-overlapping set of `Interval`s. Sets compose via set operations (intersection `&`, union `|`) and transforms (`map`, `IntervalShift`) without intermediate allocations. Each `Interval` carries a value, so composition can also be read as joining attributed segments.

## When to use this crate

Use `odsek` when you need to combine ordered interval streams while preserving the data attached to each segment: calendars, availability windows, timeline annotations, numeric domains, or other range-like datasets. It is designed for libraries and embedded/no-heap contexts that want lazy composition instead of building intermediate `Vec`s.

## Features

- `no_std` — no heap allocation in the core API.
- Open and closed endpoints (`EndpointOC::{Open, Closed}`), or symmetric `[a, b)` endpoints (`EndpointSymmetric`).
- Pull-based: `head(pos)` returns the next interval whose right endpoint is `> pos`. Random-access friendly.
- Composable: `&` (intersection) yields tupled values, `|` (union) yields `(Option<A>, Option<B>)`.
- Built-in combinators: `IntervalsSingle`, `IntervalsFromArray`, `IntervalShift`, `IntervalsCache`, `IntervalsMap`.

## Example

```rust
use odsek::*;

let ia = Interval::new(EndpointOC::Closed(1), EndpointOC::Closed(3), "A");
let ib = Interval::new(EndpointOC::Open(2),   EndpointOC::Open(4),   "B");

let isa = IntervalsSingle::new(ia);
let isb = IntervalsSingle::new(ib);

// (2, 3] with value ("A", "B")
let intersection = (isa & isb).into_iter().collect::<Vec<_>>();
assert_eq!(
    intersection,
    vec![Interval::new(
        EndpointOC::Open(2),
        EndpointOC::Closed(3),
        ("A", "B"),
    )],
);

let left = IntervalsSingle::new(Interval::new(EndpointOC::Closed(1), EndpointOC::Closed(3), "A"));
let right = IntervalsSingle::new(Interval::new(EndpointOC::Open(2), EndpointOC::Open(4), "B"));

// Union preserves which side contributed to each output segment.
let union = (left | right).into_iter().collect::<Vec<_>>();
assert_eq!(union.len(), 3);
```

More examples in [`examples/`](examples/): [intersection.rs](examples/intersection.rs), [union.rs](examples/union.rs), [shift.rs](examples/shift.rs). Run with `cargo run --example intersection`.

## Endpoint semantics

`EndpointOC::Closed(x)` includes `x`; `EndpointOC::Open(x)` excludes it. When a boundary is reused as the end of one segment and the start of another, the crate toggles open/closed status so adjacent output intervals do not double-count the same boundary. `EndpointSymmetric(x)` is simpler: left endpoints are treated as closed and right endpoints as open, so intervals follow the standard `[a, b)` convention.

## Feature flags

- `operators` (default) — enables the `&` / `|` operator overloads. Disable with `--no-default-features` to use the explicit `and(a, b)` / `or(a, b)` free functions instead.

## `no_std`

The crate is `#![no_std]` outside of tests. Tests opt back into `std` for the convenience of `Vec` and `println!`.

## Minimum Supported Rust Version

Rust 1.70.

## License

Licensed under either of

- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.