diff --git a/README.md b/README.md index 42d1bf2..0dc2465 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ [![Build](https://github.com/rubenperezm/pystrukts/actions/workflows/test.yml/badge.svg)](https://github.com/rubenperezm/pystrukts/actions/workflows/test.yml/badge.svg) [![codecov](https://codecov.io/gh/rubenperezm/pystrukts/graph/badge.svg?token=OLV3EOPYFI)](https://codecov.io/gh/rubenperezm/pystrukts) [![PyPI Version](https://img.shields.io/pypi/v/pystrukts.svg)](https://pypi.org/project/pystrukts) - +[![Python Versions](https://img.shields.io/pypi/pyversions/pystrukts.svg)](https://pypi.org/project/pystrukts) -A Python library for data structures. This library provides more than 20 advanced data structures not available in the Python standard library. +A Python library for data structures. This library provides more than 10 advanced data structures not available in the Python standard library. ## Installation @@ -21,23 +21,12 @@ pip install pystrukts - [x] Doubly Linked Node - [x] Doubly Linked List - [x] Circular Linked List (Single, Double) -- [ ] Skip List ### Trees - [x] Binary Tree - [x] Generic Tree - [x] Binary Search Tree -- [ ] Ternary Search Tree -- [ ] Suffix Tree -- [ ] AVL Tree -- [ ] Red-Black Tree -- [ ] Splay Tree -- [ ] B-Tree -- [ ] B+ Tree -- [ ] R-Tree -- [ ] Interval Tree -- [ ] Segment Tree -- [ ] Fenwick Tree +- [x] Fenwick Trees (RUPQ, RURQ) - [x] Trie ### Graphs diff --git a/pyproject.toml b/pyproject.toml index b17b659..58ac608 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,11 @@ build-backend = "setuptools.build_meta" [project] name = "pystrukts" +description = "Advanced data structures for Python." +keywords = ["data structures", "structures", "tree", "graph", "list", "heap", "trie", "union find", "pystrukts"] readme = "README.md" requires-python = ">=3.8" -dynamic = ["version", "description", "authors", "urls", "keywords"] +dynamic = ["version"] license = { file = "LICENSE" } classifiers = [ "License :: OSI Approved :: Apache Software License", @@ -21,6 +23,15 @@ classifiers = [ dependencies = [ ] +authors = [ + { name = "Rubén Pérez Mercado", email = "rubenpermerc@gmail.com" } +] + +[project.urls] +Repository = "https://github.com/rubenperezm/pystrukts" +Issues = "https://github.com/rubenperezm/pystrukts/issues" +Changelog = "https://github.com/rubenperezm/pystrukts/blob/main/CHANGELOG.md" + [tool.setuptools_scm] write_to = "pystrukts/_version.py" diff --git a/pystrukts/tree/__init__.py b/pystrukts/tree/__init__.py index 97e3b96..86de43a 100644 --- a/pystrukts/tree/__init__.py +++ b/pystrukts/tree/__init__.py @@ -3,4 +3,5 @@ from .trie import Trie, TrieNode from .generic_tree import GenericTree from .binary_tree import BinaryTree -from .binary_search_tree import BinarySearchTree \ No newline at end of file +from .binary_search_tree import BinarySearchTree +from .fenwick_tree import FenwickTree, RUPQ, RURQ \ No newline at end of file diff --git a/pystrukts/tree/fenwick_tree.py b/pystrukts/tree/fenwick_tree.py new file mode 100644 index 0000000..0ff482d --- /dev/null +++ b/pystrukts/tree/fenwick_tree.py @@ -0,0 +1,193 @@ +''' +Fenwick Tree implementation in Python. + +This module contains the implementation of Fenwick Trees, as well as two extensions of it: +- Range Update Point Query (RUPQ) +- Range Update Range Query (RURQ) +''' + +from typing import List + +class FenwickTree: + ''' + Fenwick Tree implementation in Python. + + Attributes: + - n: int - the size of the Fenwick Tree + - ftree: list - the Fenwick Tree itself + + Methods: + - lsone(s: int) -> int: returns the least significant bit of s + - query(i: int, j: int) -> int: returns the sum of the elements in the range [i, j] + - update(i: int, v: int): updates the element at index i with value v + - select(k: int) -> int: returns the index of the k-th element in the Fenwick Tree + ''' + def __init__(self, f: List[int]): + self.n = len(f) + self.ftree = [0] * (self.n + 1) + + for i in range(1, self.n + 1): + self.ftree[i] += f[i - 1] + if i + self._lsone(i) <= self.n: + self.ftree[i + self._lsone(i)] += self.ftree[i] + + def _lsone(self, s: int) -> int: + ''' + Returns the least significant bit of s. + + Args: + s (int): The number to get the least significant bit from. + + Returns: + out (int): The least significant bit of s. + ''' + return s & -s + + def query(self, i: int, j: int) -> int: + ''' + Queries the Fenwick Tree for the sum of the elements in the range [i, j]. + + Args: + i (int): The lower bound of the range. + j (int): The upper bound of the range. + + Returns: + s (int): The sum of the elements in the range [i, j]. + ''' + if i > 1: + return self.query(1, j) - self.query(1, i - 1) + + s = 0 + while j > 0: + s += self.ftree[j] + j -= self._lsone(j) + + return s + + def update(self, i: int, v: int): + ''' + Updates the element at index i with value v. + + Args: + i (int): The index of the element to update. + v (int): The new value of the element. + ''' + + while i <= self.n: + self.ftree[i] += v + i += self._lsone(i) + + def select(self, k: int) -> int: + ''' + Returns the index of the k-th element in the Fenwick Tree. + + Args: + k (int): The index of the element to return. + + Returns: + i (int): The index of the k-th element in the Fenwick Tree. + ''' + + p = 1 + while p*2 <= self.n: + p *= 2 + + i = 0 + while p > 0: + if k > self.ftree[i + p]: + k -= self.ftree[i + p] + i += p + p //= 2 + + return i + 1 + +class RUPQ: + ''' + Range Update Point Query (RUPQ) implementation. + + This class extends the Fenwick Tree to support range updates and point queries. + + Attributes: + - ftree: FenwickTree - the Fenwick Tree used for the range updates + + Methods: + - query(i: int) -> int: returns the value of the element at index i + - update(i: int, j: int, v: int): updates the elements in the range [i, j] with value v + ''' + def __init__(self, n: int): + self.ftree = FenwickTree([0] * n) + + def query(self, i: int) -> int: + ''' + Queries the Fenwick Tree for the value of the element at index i. + + Args: + i (int): The index of the element to query. + + Returns: + s (int): The value of the element at index i. + ''' + + return self.ftree.query(1, i) + + def update(self, i: int, j: int, v: int): + ''' + Updates the elements in the range [i, j] with value v. + + Args: + i (int): The lower bound of the range. + j (int): The upper bound of the range. + v (int): The value to update the elements with. + ''' + + self.ftree.update(i, v) + self.ftree.update(j + 1, -v) + +class RURQ: + ''' + Range Update Range Query (RURQ) implementation. + + This class extends the Fenwick Tree to support range updates and range queries. + + Attributes: + - f: FenwickTree - the Fenwick Tree used for the range updates + - r: RUPQ - the RUPQ used for the range queries + + Methods: + - query(i: int, j: int) -> int: returns the sum of the elements in the range [i, j] + - update(i: int, j: int, v: int): updates the elements in the range [i, j] with value v + ''' + + def __init__(self, n: int): + self.ftree = FenwickTree([0] * n) + self.rupq = RUPQ(n) + + def query(self, i: int, j: int) -> int: + ''' + Queries the Fenwick Tree for the sum of the elements in the range [i, j]. + + Args: + i (int): The lower bound of the range. + j (int): The upper bound of the range. + + Returns: + s (int): The sum of the elements in the range [i, j]. + ''' + + if i > 1: + return self.query(1, j) - self.query(1, i - 1) + return self.rupq.query(j) * j - self.ftree.query(1, j) + + def update(self, i: int, j: int, v: int): + ''' + Updates the elements in the range [i, j] with value v. + + Args: + i (int): The lower bound of the range. + j (int): The upper bound of the range. + v (int): The value to update the elements with. + ''' + + self.rupq.update(i, j, v) + self.ftree.update(i, v * (i - 1)) + self.ftree.update(j + 1, -1 * v * j) diff --git a/tests/test_fenwick_tree.py b/tests/test_fenwick_tree.py new file mode 100644 index 0000000..ae58983 --- /dev/null +++ b/tests/test_fenwick_tree.py @@ -0,0 +1,36 @@ +import pytest +from pystrukts.tree import FenwickTree, RUPQ, RURQ + + +def test_initialization_ftree(): + f = [0, 1, 0, 1, 2, 3, 2, 1, 1, 0] + ft = FenwickTree(f) + assert ft.n == 10 + assert ft.ftree == [0, 0, 1, 0, 2, 2, 5, 2, 10, 1, 1] + assert ft.query(1, 6) == 7 + assert ft.query(1, 3) == 1 + assert ft.select(7) == 6 + ft.update(5, 1) + assert ft.query(1, 10) == 12 + +def test_rupq(): + r = RUPQ(10) + r.update(2, 9, 7) + r.update(6, 7, 3) + assert r.query(1) == 0 + assert r.query(2) == 7 + assert r.query(3) == 7 + assert r.query(4) == 7 + assert r.query(5) == 7 + assert r.query(6) == 10 + assert r.query(7) == 10 + assert r.query(8) == 7 + assert r.query(9) == 7 + assert r.query(10) == 0 + +def test_rurq(): + r = RURQ(10) + r.update(2, 9, 7) + r.update(6, 7, 3) + assert r.query(3, 5) == 21 + assert r.query(7, 8) == 17 \ No newline at end of file