beset 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- beset-0.1.0/PKG-INFO +151 -0
- beset-0.1.0/pyproject.toml +69 -0
- beset-0.1.0/pyproject.toml.orig +64 -0
- beset-0.1.0/readme.md +140 -0
- beset-0.1.0/src/beset/__init__.py +43 -0
- beset-0.1.0/src/beset/_interval.py +623 -0
- beset-0.1.0/src/beset/_interval_data.py +10 -0
- beset-0.1.0/src/beset/_itertools.py +25 -0
- beset-0.1.0/src/beset/_operations.py +298 -0
- beset-0.1.0/src/beset/_protocol.py +7 -0
- beset-0.1.0/src/beset/py.typed +0 -0
beset-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: beset
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Immutable, typed intervals as sets
|
|
5
|
+
Author: Maarten Oosten
|
|
6
|
+
Author-email: Maarten Oosten <oosten@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Dist: typing-extensions>=4.16.0 ; python_full_version < '3.11'
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# `⟨ beset ⟩`
|
|
13
|
+
|
|
14
|
+
_immutable, typed intervals with the interface of Python sets_
|
|
15
|
+
|
|
16
|
+
- Intervals are typed as generics and pass type checks by all common Python type checkers:
|
|
17
|
+
- `mypy --strict`
|
|
18
|
+
- `ty`
|
|
19
|
+
- `pyright`
|
|
20
|
+
- `pyrefly`
|
|
21
|
+
- Intervals are immutable and hashable
|
|
22
|
+
- Intervals behave like sets
|
|
23
|
+
- If you know Python set operations, you know how to use this library
|
|
24
|
+
- Intervals can be used on any data type that supports `<`, the less-than operator, for linear ordering
|
|
25
|
+
- Typical: `int`, `float`, `datetime`
|
|
26
|
+
- Even a class like `str` with no notion of distance between values, can be used for intervals (e.g. dictionary ranges)
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
Intervals are sets that contain all possible values between their lower and upper bounds.
|
|
31
|
+
The `Closed` interval also includes the bounds themselves.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
>>> from beset import Closed
|
|
35
|
+
|
|
36
|
+
>>> x = Closed(1, 3)
|
|
37
|
+
|
|
38
|
+
>>> print(x)
|
|
39
|
+
[1 ; 3]
|
|
40
|
+
|
|
41
|
+
>>> 0 in x, 1 in x, 2 in x, 3 in x, 4 in x
|
|
42
|
+
(False, True, True, True, False)
|
|
43
|
+
|
|
44
|
+
>>> x.start, x.stop
|
|
45
|
+
(1, 3)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The half-open `ClosedOpen` interval includes its lower bound but not its upper one.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
>>> from beset import ClosedOpen
|
|
52
|
+
|
|
53
|
+
>>> y = ClosedOpen(1, 3)
|
|
54
|
+
|
|
55
|
+
>>> print(y)
|
|
56
|
+
[1 ; 3)
|
|
57
|
+
|
|
58
|
+
>>> 2 in y, 3 in y, 4 in y
|
|
59
|
+
(True, False, False)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The `Open` interval excludes both its bounds and the `OpenClosed` interval includes its upper bound, but not its lower one.
|
|
63
|
+
|
|
64
|
+
You can also create the required interval dynamically using their `Interval` base class.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
>>> from beset import Interval
|
|
68
|
+
|
|
69
|
+
>>> Interval(9, 99, start_closed=False, stop_closed=True)
|
|
70
|
+
OpenClosed(9, 99)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Intervals support all Python `set` operations. Some examples:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
>>> ClosedOpen(10, 20) & ClosedOpen(15, 25) # intersection
|
|
77
|
+
ClosedOpen(15, 20)
|
|
78
|
+
|
|
79
|
+
>>> ClosedOpen(3, 9) < Open(0, 10) # is proper subset
|
|
80
|
+
True
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Set subtraction can lead to disjoint sets. The `beset` library represents these using the class `IntervalSet`.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
>>> s = Open(0, 10) - Open(3, 5)
|
|
87
|
+
|
|
88
|
+
>>> s
|
|
89
|
+
IntervalSet([OpenClosed(0, 3), ClosedOpen(5, 10)])
|
|
90
|
+
|
|
91
|
+
>>> print(s)
|
|
92
|
+
(0 ; 3] | [5 ; 10)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
You can also create an `IntervalSet` explicitly, but it's often easier to use the union operator on simple intervals.
|
|
96
|
+
The results are equal.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
>>> IntervalSet([Open(10, 20), Open(30, 40)]) == Open(10, 20) | Open(30, 40)
|
|
100
|
+
True
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The `beset` library supports unbounded intervals without upper or lower bound.
|
|
104
|
+
Create such intervals by using `None` as a bound.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
>>> x = Closed(10, None)
|
|
108
|
+
>>> print(x)
|
|
109
|
+
[10 ; +inf⟩
|
|
110
|
+
>>> 100 in x
|
|
111
|
+
True
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Unbounded intervals allow for the introduction of the _complement_ operation that returns the complementary interval, containing everything not in the original interval.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
>>> Closed(-3, 7).complement()
|
|
118
|
+
IntervalSet([Open(None, -3), Open(7, None)])
|
|
119
|
+
|
|
120
|
+
>>> print(~ClosedOpen(0, 100)) # the ~-operator returns the complement
|
|
121
|
+
⟨-inf ; 0) | [100 ; +inf⟩
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Typing
|
|
125
|
+
|
|
126
|
+
The `Interval` and `IntervalSet` class are generics.
|
|
127
|
+
Type checkers automatically infer the correct type.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
>>> reveal_type(ClosedOpen(2.718, 6.283)) # Revealed type is beset.ClosedOpen[float]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Taking the complement of an interval can introduce `None` values.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
>>> print(x := ~Closed(0, 10))
|
|
137
|
+
⟨-inf ; 0) | (10 ; +inf⟩
|
|
138
|
+
|
|
139
|
+
>>> reveal_type(x) # Revealed type is beset.IntervalSet[int | None]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Getting rid of the union with `None` can be accomplished using intersection.
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
>>> domain = Closed(-100, 100)
|
|
146
|
+
>>> y = domain & x
|
|
147
|
+
>>> print(y)
|
|
148
|
+
[-100 ; 0) | (10 ; 100]
|
|
149
|
+
|
|
150
|
+
>>> reveal_type(y) # Revealed type is beset.IntervalSet[int]
|
|
151
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "beset"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Immutable, typed intervals as sets"
|
|
5
|
+
readme = "readme.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = ["typing-extensions>=4.16.0 ; python_full_version < '3.11'"]
|
|
9
|
+
|
|
10
|
+
[[project.authors]]
|
|
11
|
+
name = "Maarten Oosten"
|
|
12
|
+
email = "oosten@gmail.com"
|
|
13
|
+
|
|
14
|
+
[dependency-groups]
|
|
15
|
+
lint = ["ruff>=0.16.5"]
|
|
16
|
+
type-check = [
|
|
17
|
+
"mypy>=2.3.1",
|
|
18
|
+
"pyrefly>=1.2.0",
|
|
19
|
+
"pyright>=1.1.411",
|
|
20
|
+
"ty>=0.0.75",
|
|
21
|
+
]
|
|
22
|
+
test = ["pytest>=9.1.1"]
|
|
23
|
+
coverage = [
|
|
24
|
+
"pytest-cov>=7.1.0",
|
|
25
|
+
{ include-group = "test" },
|
|
26
|
+
]
|
|
27
|
+
slotscheck = ["slotscheck==0.21.0b1"]
|
|
28
|
+
|
|
29
|
+
[[dependency-groups.dev]]
|
|
30
|
+
include-group = "lint"
|
|
31
|
+
|
|
32
|
+
[[dependency-groups.dev]]
|
|
33
|
+
include-group = "type-check"
|
|
34
|
+
|
|
35
|
+
[[dependency-groups.dev]]
|
|
36
|
+
include-group = "test"
|
|
37
|
+
|
|
38
|
+
[[dependency-groups.dev]]
|
|
39
|
+
include-group = "coverage"
|
|
40
|
+
|
|
41
|
+
[[dependency-groups.dev]]
|
|
42
|
+
include-group = "slotscheck"
|
|
43
|
+
|
|
44
|
+
[build-system]
|
|
45
|
+
requires = ["uv_build>=0.12.7,<0.13.0"]
|
|
46
|
+
build-backend = "uv_build"
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
line-length = 120
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = [
|
|
53
|
+
"E",
|
|
54
|
+
"F",
|
|
55
|
+
"I",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.mypy]
|
|
59
|
+
strict = true
|
|
60
|
+
|
|
61
|
+
[tool.pyrefly]
|
|
62
|
+
preset = "strict"
|
|
63
|
+
|
|
64
|
+
[tool.pyrefly.errors]
|
|
65
|
+
unused-ignore = false
|
|
66
|
+
missing-override-decorator = false
|
|
67
|
+
|
|
68
|
+
[tool.coverage.run]
|
|
69
|
+
omit = ["test/*"]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "beset"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Immutable, typed intervals as sets"
|
|
5
|
+
readme = "readme.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Maarten Oosten", email = "oosten@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
license = "MIT"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"typing-extensions>=4.16.0 ; python_full_version < '3.11'",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[dependency-groups]
|
|
16
|
+
lint = [
|
|
17
|
+
"ruff>=0.16.5",
|
|
18
|
+
]
|
|
19
|
+
type-check = [
|
|
20
|
+
"mypy>=2.3.1",
|
|
21
|
+
"pyrefly>=1.2.0",
|
|
22
|
+
"pyright>=1.1.411",
|
|
23
|
+
"ty>=0.0.75",
|
|
24
|
+
]
|
|
25
|
+
test = [
|
|
26
|
+
"pytest>=9.1.1",
|
|
27
|
+
]
|
|
28
|
+
coverage = [
|
|
29
|
+
"pytest-cov>=7.1.0",
|
|
30
|
+
{include-group = "test"},
|
|
31
|
+
]
|
|
32
|
+
slotscheck = [
|
|
33
|
+
"slotscheck==0.21.0b1",
|
|
34
|
+
]
|
|
35
|
+
dev = [
|
|
36
|
+
{include-group = "lint"},
|
|
37
|
+
{include-group = "type-check"},
|
|
38
|
+
{include-group = "test"},
|
|
39
|
+
{include-group = "coverage"},
|
|
40
|
+
{include-group = "slotscheck"},
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["uv_build>=0.12.7,<0.13.0"]
|
|
45
|
+
build-backend = "uv_build"
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 120
|
|
49
|
+
|
|
50
|
+
[tool.ruff.lint]
|
|
51
|
+
select = ["E", "F", "I"]
|
|
52
|
+
|
|
53
|
+
[tool.mypy]
|
|
54
|
+
strict = true
|
|
55
|
+
|
|
56
|
+
[tool.pyrefly]
|
|
57
|
+
preset = "strict"
|
|
58
|
+
|
|
59
|
+
[tool.pyrefly.errors]
|
|
60
|
+
unused-ignore = false
|
|
61
|
+
missing-override-decorator = false
|
|
62
|
+
|
|
63
|
+
[tool.coverage.run]
|
|
64
|
+
omit = ["test/*"]
|
beset-0.1.0/readme.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# `⟨ beset ⟩`
|
|
2
|
+
|
|
3
|
+
_immutable, typed intervals with the interface of Python sets_
|
|
4
|
+
|
|
5
|
+
- Intervals are typed as generics and pass type checks by all common Python type checkers:
|
|
6
|
+
- `mypy --strict`
|
|
7
|
+
- `ty`
|
|
8
|
+
- `pyright`
|
|
9
|
+
- `pyrefly`
|
|
10
|
+
- Intervals are immutable and hashable
|
|
11
|
+
- Intervals behave like sets
|
|
12
|
+
- If you know Python set operations, you know how to use this library
|
|
13
|
+
- Intervals can be used on any data type that supports `<`, the less-than operator, for linear ordering
|
|
14
|
+
- Typical: `int`, `float`, `datetime`
|
|
15
|
+
- Even a class like `str` with no notion of distance between values, can be used for intervals (e.g. dictionary ranges)
|
|
16
|
+
|
|
17
|
+
## Examples
|
|
18
|
+
|
|
19
|
+
Intervals are sets that contain all possible values between their lower and upper bounds.
|
|
20
|
+
The `Closed` interval also includes the bounds themselves.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
>>> from beset import Closed
|
|
24
|
+
|
|
25
|
+
>>> x = Closed(1, 3)
|
|
26
|
+
|
|
27
|
+
>>> print(x)
|
|
28
|
+
[1 ; 3]
|
|
29
|
+
|
|
30
|
+
>>> 0 in x, 1 in x, 2 in x, 3 in x, 4 in x
|
|
31
|
+
(False, True, True, True, False)
|
|
32
|
+
|
|
33
|
+
>>> x.start, x.stop
|
|
34
|
+
(1, 3)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The half-open `ClosedOpen` interval includes its lower bound but not its upper one.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
>>> from beset import ClosedOpen
|
|
41
|
+
|
|
42
|
+
>>> y = ClosedOpen(1, 3)
|
|
43
|
+
|
|
44
|
+
>>> print(y)
|
|
45
|
+
[1 ; 3)
|
|
46
|
+
|
|
47
|
+
>>> 2 in y, 3 in y, 4 in y
|
|
48
|
+
(True, False, False)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The `Open` interval excludes both its bounds and the `OpenClosed` interval includes its upper bound, but not its lower one.
|
|
52
|
+
|
|
53
|
+
You can also create the required interval dynamically using their `Interval` base class.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
>>> from beset import Interval
|
|
57
|
+
|
|
58
|
+
>>> Interval(9, 99, start_closed=False, stop_closed=True)
|
|
59
|
+
OpenClosed(9, 99)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Intervals support all Python `set` operations. Some examples:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
>>> ClosedOpen(10, 20) & ClosedOpen(15, 25) # intersection
|
|
66
|
+
ClosedOpen(15, 20)
|
|
67
|
+
|
|
68
|
+
>>> ClosedOpen(3, 9) < Open(0, 10) # is proper subset
|
|
69
|
+
True
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Set subtraction can lead to disjoint sets. The `beset` library represents these using the class `IntervalSet`.
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
>>> s = Open(0, 10) - Open(3, 5)
|
|
76
|
+
|
|
77
|
+
>>> s
|
|
78
|
+
IntervalSet([OpenClosed(0, 3), ClosedOpen(5, 10)])
|
|
79
|
+
|
|
80
|
+
>>> print(s)
|
|
81
|
+
(0 ; 3] | [5 ; 10)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
You can also create an `IntervalSet` explicitly, but it's often easier to use the union operator on simple intervals.
|
|
85
|
+
The results are equal.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
>>> IntervalSet([Open(10, 20), Open(30, 40)]) == Open(10, 20) | Open(30, 40)
|
|
89
|
+
True
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The `beset` library supports unbounded intervals without upper or lower bound.
|
|
93
|
+
Create such intervals by using `None` as a bound.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
>>> x = Closed(10, None)
|
|
97
|
+
>>> print(x)
|
|
98
|
+
[10 ; +inf⟩
|
|
99
|
+
>>> 100 in x
|
|
100
|
+
True
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Unbounded intervals allow for the introduction of the _complement_ operation that returns the complementary interval, containing everything not in the original interval.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
>>> Closed(-3, 7).complement()
|
|
107
|
+
IntervalSet([Open(None, -3), Open(7, None)])
|
|
108
|
+
|
|
109
|
+
>>> print(~ClosedOpen(0, 100)) # the ~-operator returns the complement
|
|
110
|
+
⟨-inf ; 0) | [100 ; +inf⟩
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Typing
|
|
114
|
+
|
|
115
|
+
The `Interval` and `IntervalSet` class are generics.
|
|
116
|
+
Type checkers automatically infer the correct type.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
>>> reveal_type(ClosedOpen(2.718, 6.283)) # Revealed type is beset.ClosedOpen[float]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Taking the complement of an interval can introduce `None` values.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
>>> print(x := ~Closed(0, 10))
|
|
126
|
+
⟨-inf ; 0) | (10 ; +inf⟩
|
|
127
|
+
|
|
128
|
+
>>> reveal_type(x) # Revealed type is beset.IntervalSet[int | None]
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Getting rid of the union with `None` can be accomplished using intersection.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
>>> domain = Closed(-100, 100)
|
|
135
|
+
>>> y = domain & x
|
|
136
|
+
>>> print(y)
|
|
137
|
+
[-100 ; 0) | (10 ; 100]
|
|
138
|
+
|
|
139
|
+
>>> reveal_type(y) # Revealed type is beset.IntervalSet[int]
|
|
140
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from beset._interval import (
|
|
2
|
+
EMPTY,
|
|
3
|
+
UNBOUNDED,
|
|
4
|
+
Closed,
|
|
5
|
+
ClosedOpen,
|
|
6
|
+
ClosedOpenSet,
|
|
7
|
+
ClosedSet,
|
|
8
|
+
Empty,
|
|
9
|
+
Interval,
|
|
10
|
+
IntervalSet,
|
|
11
|
+
LeftClosed,
|
|
12
|
+
LeftOpen,
|
|
13
|
+
Open,
|
|
14
|
+
OpenClosed,
|
|
15
|
+
OpenClosedSet,
|
|
16
|
+
OpenSet,
|
|
17
|
+
RightClosed,
|
|
18
|
+
RightOpen,
|
|
19
|
+
Unbounded,
|
|
20
|
+
)
|
|
21
|
+
from beset._protocol import Sortable
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Sortable",
|
|
25
|
+
"IntervalSet",
|
|
26
|
+
"OpenSet",
|
|
27
|
+
"ClosedSet",
|
|
28
|
+
"OpenClosedSet",
|
|
29
|
+
"ClosedOpenSet",
|
|
30
|
+
"Interval",
|
|
31
|
+
"Open",
|
|
32
|
+
"Closed",
|
|
33
|
+
"OpenClosed",
|
|
34
|
+
"ClosedOpen",
|
|
35
|
+
"LeftOpen",
|
|
36
|
+
"RightOpen",
|
|
37
|
+
"LeftClosed",
|
|
38
|
+
"RightClosed",
|
|
39
|
+
"Unbounded",
|
|
40
|
+
"Empty",
|
|
41
|
+
"UNBOUNDED",
|
|
42
|
+
"EMPTY",
|
|
43
|
+
]
|