Skip to content

Commit b78bc90

Browse files
author
Maciej Rapacz
committed
Add from_iterable
1 parent dc08b28 commit b78bc90

4 files changed

Lines changed: 91 additions & 14 deletions

File tree

README.md

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,54 +9,98 @@
99

1010
## Prerequisites
1111

12-
The only requirement is having Python 3.8+ installed, you can verify this by running:
12+
The only requirement is using **Python 3.8+**. You can verify this by running:
13+
1314
```bash
1415
$ python --version
15-
Python 3.8.16
16+
Python 3.8.18
1617
```
1718

1819
## Installation
20+
1921
```
2022
pip install disjoint-set
2123
```
2224

2325
You can verify you're running the latest package version by running:
26+
2427
```python
2528
>>> import disjoint_set
2629
>>> disjoint_set.__version__
27-
'0.7.4'
30+
'0.8.0'
2831

2932
```
3033

3134
## Usage
3235

36+
### Import & instantiate
37+
3338
```python
3439
>>> from disjoint_set import DisjointSet
40+
>>> DisjointSet()
41+
DisjointSet({})
42+
43+
>>> DisjointSet({1: 1})
44+
DisjointSet({1: 1})
45+
46+
>>> DisjointSet.from_iterable([1,2,3])
47+
DisjointSet({1: 1, 2: 2, 3: 3})
48+
49+
```
50+
51+
### Perform find & union operations
52+
53+
```python
3554
>>> ds = DisjointSet()
3655
>>> ds.find(1)
3756
1
57+
3858
>>> ds.union(1,2)
3959
>>> ds.find(1)
4060
2
61+
4162
>>> ds.find(2)
4263
2
64+
65+
```
66+
67+
### Check if values belong to the same set
68+
69+
```python
70+
>>> ds = DisjointSet({1: 2, 2: 2, 3: 3})
4371
>>> ds.connected(1,2)
4472
True
73+
4574
>>> ds.connected(1,3)
4675
False
4776

77+
```
78+
79+
### Check if values are present within the data structure
80+
81+
```python
82+
>>> ds = DisjointSet()
4883
>>> "a" in ds
4984
False
85+
5086
>>> ds.find("a")
5187
'a'
88+
5289
>>> "a" in ds
5390
True
5491

92+
```
93+
94+
### List elements and sets within the disjoint set
95+
96+
```python
97+
>>> ds = DisjointSet({1: 2, 2: 2, 3: 3})
5598
>>> list(ds)
56-
[(1, 2), (2, 2), (3, 3), ('a', 'a')]
99+
[(1, 2), (2, 2), (3, 3)]
57100

101+
>>> ds = DisjointSet({1: 2, 2: 2, 3: 3})
58102
>>> list(ds.itersets())
59-
[{1, 2}, {3}, {'a'}]
103+
[{1, 2}, {3}]
60104

61105
```
62106

@@ -66,8 +110,7 @@ Feel free to open any issues on github.
66110

67111
## Authors
68112

69-
* [Maciej Rapacz](https://github.com/mrapacz/)
70-
113+
- [Maciej Rapacz](https://github.com/mrapacz/)
71114

72115
## License
73116

disjoint_set/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
name = "disjoint_set"
55
__all__ = ["DisjointSet", "InvalidInitialMappingError"]
6-
__version__ = "0.7.4"
6+
__version__ = "0.8.0"

disjoint_set/main.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
from __future__ import annotations
2+
13
from collections import defaultdict
24
from typing import Any
35
from typing import DefaultDict
46
from typing import Generic
7+
from typing import Iterable
58
from typing import Iterator
6-
from typing import Set
7-
from typing import Tuple
89
from typing import TypeVar
9-
from typing import Union
1010

1111
from disjoint_set.utils import IdentityDict
1212

@@ -32,12 +32,34 @@ class DisjointSet(Generic[T]):
3232
"""A disjoint set data structure."""
3333

3434
def __init__(self, *args, **kwargs) -> None:
35+
"""
36+
Disjoint set data structure.
37+
38+
The data structure can be initialized as an empty disjoint set:
39+
>>> DisjointSet()
40+
DisjointSet({})
41+
42+
But it can also be instantiated from an existing mapping such as:
43+
>>> DisjointSet({1: 2, 2: 2})
44+
DisjointSet({1: 2, 2: 2})
45+
"""
3546
self._data: IdentityDict[T] = IdentityDict(*args, **kwargs)
3647

48+
@classmethod
49+
def from_iterable(cls, iterable: Iterable[T]) -> DisjointSet[T]:
50+
"""Instantiate a DistjointSet instance by transforming each element from an interable to a canonical element."""
51+
return cls({x: x for x in iterable})
52+
53+
def __len__(self) -> int:
54+
"""Return the number of elements in the disjoint set."""
55+
return len(self._data)
56+
3757
def __contains__(self, item: T) -> bool:
58+
"""Return True if `item` is an element of the disjoint set (not necessarily a canonical one)."""
3859
return item in self._data
3960

4061
def __bool__(self) -> bool:
62+
"""Return True if disjoint set contains at least one element."""
4163
return bool(self._data)
4264

4365
def __getitem__(self, element: T) -> T:
@@ -72,15 +94,15 @@ def __str__(self) -> str:
7294
values=", ".join(str(dset) for dset in self.itersets()),
7395
)
7496

75-
def __iter__(self) -> Iterator[Tuple[T, T]]:
97+
def __iter__(self) -> Iterator[tuple[T, T]]:
7698
"""Iterate over items and their canonical elements."""
7799
try:
78100
for key in self._data.keys():
79101
yield key, self.find(key)
80102
except RuntimeError as e:
81103
raise InvalidInitialMappingError() from e
82104

83-
def itersets(self, with_canonical_elements: bool = False) -> Iterator[Union[Set[T], Tuple[T, Set[T]]]]:
105+
def itersets(self, with_canonical_elements: bool = False) -> Iterator[set[T] | tuple[T, set[T]]]:
84106
"""
85107
Yield sets of connected components.
86108
@@ -92,7 +114,7 @@ def itersets(self, with_canonical_elements: bool = False) -> Iterator[Union[Set[
92114
>>> list(ds.itersets(with_canonical_elements=True))
93115
[(2, {1, 2})]
94116
"""
95-
element_classes: DefaultDict[T, Set[T]] = defaultdict(set)
117+
element_classes: DefaultDict[T, set[T]] = defaultdict(set)
96118
for element in self._data:
97119
element_classes[self.find(element)].add(element)
98120

tests/test_disjoint_set.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,15 @@ def test_raises_on_invalid_init_params():
7777
dset = DisjointSet({1: 2, 2: 3})
7878
with pytest.raises(InvalidInitialMappingError):
7979
list(dset)
80+
81+
82+
@pytest.mark.parametrize(
83+
argnames=("iterable", "expected_value"),
84+
argvalues=(
85+
pytest.param([], DisjointSet({}), id="empty list"),
86+
pytest.param(range(3), DisjointSet({2: 2, 1: 1, 0: 0}), id="range(3)"),
87+
pytest.param("abc", DisjointSet({"a": "a", "b": "b", "c": "c"}), id="string"),
88+
),
89+
)
90+
def test_from_iterable(iterable, expected_value):
91+
assert DisjointSet.from_iterable(iterable=iterable) == expected_value

0 commit comments

Comments
 (0)