Skip to main content

linkedset

An ordered set that is robust against mutation during iteration, implemented in pure Python.

DoublyLinkedSet behaves like an ordered set backed by a doubly linked list. Inserting and removing elements is O(1), and — unlike Python's built-in list or set — you can safely add, remove, or move elements while iterating over the container.

It implements both collections.abc.Sequence (ordered, indexable) and collections.abc.MutableSet (set algebra and in-place updates).

📖 Documentation: https://linkedset.readthedocs.io/

Installation

pip install linkedset

Or install from source:

git clone https://github.com/justinchuby/linkedset
cd linkedset
pip install -e ".[dev]"

Usage

from linkedset import DoublyLinkedSet

s = DoublyLinkedSet(["a", "b", "c"])

s.append("d")                 # -> a, b, c, d
s.insert_after("a", ["x"])    # -> a, x, b, c, d
s.insert_before("b", ["y"])   # -> a, x, y, b, c, d
s.remove("c")                 # -> a, x, y, b, d

print(s[0])    # "a"  (O(1))
print(s[-1])   # "d"  (O(1))
print(list(s)) # ['a', 'x', 'y', 'b', 'd']

Safe mutation during iteration

s = DoublyLinkedSet(["a", "b", "c"])
for x in s:
    if x == "a":
        s.insert_after("a", ["d"])  # inserted after current -> iterated
        s.remove("b")               # removed before reached -> skipped
# Iterated: a, d, c

Iteration rules:

  • Elements inserted after the current node are iterated over.
  • Elements inserted before the current node are not iterated over in the current pass.
  • If the current node is moved to a different location, iteration continues from the node that followed it at its original location.

Per-element mutation (add, remove, discard, append, insert, clear, …) is safe during iteration. The global reorders reverse() and rotate() are not — calling them mid-iteration may cause an in-progress iterator to skip or repeat elements.

Set operations

Because it is a MutableSet, the usual set algebra works and returns a new DoublyLinkedSet (order preserved):

a = DoublyLinkedSet(["a", "b", "c"])
b = DoublyLinkedSet(["c", "d"])

a | b   # union        -> a, b, c, d
a & b   # intersection -> c
a - b   # difference   -> a, b
a ^ b   # symmetric    -> a, b, d

a.add("x")        # idempotent add (no-op if already present)
a.discard("z")    # remove if present, never raises
a.pop()           # remove and return the last element (list-style; pass an index too)
a |= b            # in-place update

# `==` is order-sensitive, because the set is ordered:
DoublyLinkedSet(["a", "b"]) == DoublyLinkedSet(["a", "b"])  # True
DoublyLinkedSet(["a", "b"]) == DoublyLinkedSet(["b", "a"])  # False

Deque- and list-style methods

Because it is ordered, it also offers the familiar deque/list mutators (all keeping the set's uniqueness and value equality semantics):

s = DoublyLinkedSet(["a", "b", "c"])

s.appendleft("z")       # -> z, a, b, c
s.extendleft(["x", "y"])# -> y, x, z, a, b, c  (prepended, reversed like deque)
s.popleft()             # removes and returns "y"
s.pop()                 # removes and returns the last element ("c")
s.pop(1)                # removes and returns the element at index 1
s.insert(1, "q")        # insert before index 1 (index clamped like list.insert)
s.rotate(1)             # rotate right; negative rotates left
s.reverse()             # reverse in place
s2 = s.copy()           # shallow copy, order preserved

Semantics

  • Membership and set operations are based on value equality (==), not object identity. Two distinct objects that compare equal are treated as the same element.
  • == is order-sensitive (it is an ordered set): equal only when the same elements, by value equality, appear in the same order. Instances are not hashable (mutable set). Ordering/subset comparisons (<, <=, >, >=) are not supported (they raise TypeError), since a subset relation would be ambiguous next to order-sensitive equality; use the set algebra (&, |, -, ^) or isdisjoint() instead.
  • None is not a valid value.
  • Accessing by index is O(n), except the ends (s[0], s[-1]) which are O(1).
  • All values must be hashable (can be used as dictionary keys) for efficient membership lookups.

Complexity

DoublyLinkedSet is a doubly linked list paired with a dict mapping each element's value to its list node. That combination gives set-like O(1) membership and endpoint mutation, while preserving order and safe mutation during iteration.

Let n be the size of the set (and m the size of the other operand for binary set operations).

Operation Complexity Notes
x in s, s.count(x) O(1) dict lookup by value (requires hashable)
len(s) O(1) length is tracked, not counted
s.append(x), s.add(x), s.appendleft(x) O(1) insert at a known end
s.remove(x), s.discard(x) O(1) unlink the node, no shifting
s.extend(xs), s.extendleft(xs) O(k) k = len(xs); O(1) per element
s.insert_after(v, xs), s.insert_before(v, xs) O(k) k = len(xs); O(1) per element
s.pop(), s.popleft() O(1) remove from an end
s.pop(i), s.insert(i, x) O(|i|) walks to the index from the nearer end
s[0], s[-1] O(1) endpoints are reachable directly
s[i] O(|i|) walks from the nearer end
s.index(x) O(n) linear scan for position
s.rotate(n) O(n mod len(s)) relink only, no node churn
s.reverse(), s.copy(), s.clear() O(n)
iteration, reversed(s), s == other O(n)
s[a:b] (slice) O(n) materialises a tuple
s | t, s & t, s - t, s ^ t O(n + m) each membership test is O(1)
s.isdisjoint(t) O(n) one pass with O(1) lookups

Space is O(n): every element is wrapped in a small link node and referenced once from the value-keyed index.

Mutating during iteration stays O(1) per operation. Removed nodes are unlinked from their neighbours immediately, so a traversal never pays to skip over dead nodes — the only cost is following the next pointer you already hold.

Development

pip install -e ".[dev]"
python -m pytest      # run tests
python -m ruff check  # lint

License

MIT. Portions derived from the ONNX Project Contributors (Apache-2.0); see linkedset/__init__.py.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

linkedset-0.2.0.tar.gz (16.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

linkedset-0.2.0-py3-none-any.whl (10.5 kB view details)

Uploaded Python 3

File details

Details for the file linkedset-0.2.0.tar.gz.

File metadata

  • Download URL: linkedset-0.2.0.tar.gz
  • Upload date:
  • Size: 16.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for linkedset-0.2.0.tar.gz
Algorithm Hash digest
SHA256 bd06a0868cb70b8521e9267214539fea4522c8af46146d975ac053375d5f25c1
MD5 46f505e8d01a9ec1156e1646d1bad49b
BLAKE2b-256 9846ad6acc7ab0cb7e2113352829a84aaba3d076be1ec4a70f95538cdca7f96d

See more details on using hashes here.

Provenance

The following attestation bundles were made for linkedset-0.2.0.tar.gz:

Publisher: publish.yml on justinchuby/linkedset

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file linkedset-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: linkedset-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 10.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for linkedset-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 320b9976039ad74db21f26f4e8d644077d76423221cf5a070a2a2ad5396a5495
MD5 532558effdff1b5904583273d5b9a777
BLAKE2b-256 835213f1bca35f62ab8d50566a04148b9077eedd27799320159207dbeac54442

See more details on using hashes here.

Provenance

The following attestation bundles were made for linkedset-0.2.0-py3-none-any.whl:

Publisher: publish.yml on justinchuby/linkedset

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page