Setfield
Setfield is a small Python library providing a framework for expressing a field of sets construction. Also known as a (Boolean) set algebra, this represents the common notion of "overlapping subsets," with the ability to perform setwise operations like union, intersection, and complement.
Applications include:
- Instantiating ontologies like a hierarchy of topics.
- Writing domain-specific languages involving boolean operations.
- Pedagogical applications to aid in learning predicate logic.
Two advantages of setfield over ordinary Python sets are:
- The presence of an ambient universe set makes the complement well-defined.
- Compositional constructs like union-of-ranges and boolean operators can be used to make set construction and membership querying more efficient in both time and memory.
Installation
The library is available on PyPI. To install:
pip install setfield
Usage
Suppose we have a collection (universe) of movies and want to organize them by genre, allowing some movies to belong to multiple genres. We can create a field of sets like so:
from setfield import Subset
movies = {
'Alien',
'Blade Runner',
'Casablanca',
'Dunkirk',
'Frankenstein',
'Her',
'Interstellar',
'The Shining',
}
# construct subsets
sci_fi = Subset(movies, {'Alien', 'Blade Runner', 'Her', 'Interstellar'})
horror = Subset(movies, {'Alien', 'Frankenstein', 'The Shining'})
romance = Subset(movies, {'Casablanca', 'Her', 'Interstellar'})
# check setwise relationships
assert sci_fi < movies
assert 'Her' in horror | romance
assert 'Frankenstein' in horror - sci_fi
assert sci_fi & horror == {'Alien'}
assert 'Dunkirk' in ~(sci_fi | horror | romance)
Parsing Boolean Expressions
We can also use setfield to create a miniature Domain-Specific Language (DSL) for boolean expressions with our custom field of sets. This is useful if we want to provide a way for users to express set combinations with a simple, natural syntax. Here's an example, continuing with movie genres:
from setfield import safe_eval_boolean_expr
genres = {
'sci_fi': sci_fi,
'horror': horror,
'romance': romance,
}
def movies_for_genre(genre: str) -> Subset[str]:
"""Get the movies for a given genre, raising a ValueError if the genre is unknown."""
if genre in genres:
return genres[genre]
raise ValueError(f'unknown genre: {genre}')
def interpret_genres(expr: str) -> Subset[str]:
"""Interpret a boolean expression involving genres."""
return safe_eval_boolean_expr(expr, eval_name=movies_for_genre)
assert interpret_genres('sci_fi & horror') == {'Alien'}
assert interpret_genres('horror - sci_fi') == {'Frankenstein', 'The Shining'}
assert interpret_genres('~(sci_fi | horror | romance)') == {'Dunkirk'}
As the name suggests, safe_eval_boolean_expr is "safe" in that it will not execute arbitrary Python code—it evaluates names exclusively with the provided eval_name function and then applies boolean operations to the results.
Union of Ranges
If we're concerned with integer subsets, there is a RangeUnionSubset data structure which is often more efficient than a typical Subset (which stores all of its elements in a set). This consists of an ordered sequence of non-overlapping ranges, or [start, stop) pairs.
As an example:
from setfield import RangeUnionSubset
subset = RangeUnionSubset(range(100), [range(0, 10), range(50, 75)])
assert len(subset) == 35
assert 9 in subset
assert 20 not in subset
assert 55 in subset
# complement is calculated efficiently
print(~subset)
# RangeUnionSubset(universe_range=range(0, 100), ranges=[range(10, 50), range(75, 100)])
# likewise with intersections, unions, and differences
subset2 = RangeUnionSubset(range(100), [range(40, 60)])
print(subset & subset2)
# RangeUnionSubset(universe_range=range(0, 100), ranges=[range(50, 60)])
print(subset | subset2)
# RangeUnionSubset(universe_range=range(0, 100), ranges=[range(0, 10), range(40, 75)])
print(subset - subset2)
# RangeUnionSubset(universe_range=range(0, 100), ranges=[range(0, 10), range(60, 75)])
License
This library is open-source and licensed under the MIT License.
Contributions are welcome!
Metadata
Release files for setfield 0.2.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| setfield-0.2.5.tar.gz | 20.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| setfield-0.2.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.5 kB
Release files / setfield-0.2.5.tar.gz
| Download URL | setfield-0.2.5.tar.gz |
|---|---|
| Size | 20.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ec910a01f2707c557c81dc21fcb402a6c6574b9d52856303ddef5a1179301fdc
|
|
BLAKE2b-256 checksum How to use checksums |
71bb7b5d606523d99f9288fb7cb797c33574361545745c29cdbe266767ced480
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
Hatch/1.16.5 cpython/3.13.11 HTTPX/0.28.1
|
Release files / setfield-0.2.5-py3-none-any.whl
| Download URL | setfield-0.2.5-py3-none-any.whl |
|---|---|
| Size | 14.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
46a7b470bcca0655607751d3654ed508fc6e76d5862c8590aac927791fca1495
|
|
BLAKE2b-256 checksum How to use checksums |
02381782962f20a7ad901bdeff498fb5dad565f6ebf7d0029934b5ee2e599f3e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
Hatch/1.16.5 cpython/3.13.11 HTTPX/0.28.1
|