Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) -->
[![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

Expand All @@ -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
Expand Down
13 changes: 12 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -21,6 +23,15 @@ classifiers = [
dependencies = [
]

authors = [
{ name = "Rubén Pérez Mercado", email = "[email protected]" }
]

[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"

Expand Down
3 changes: 2 additions & 1 deletion pystrukts/tree/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
from .binary_search_tree import BinarySearchTree
from .fenwick_tree import FenwickTree, RUPQ, RURQ
193 changes: 193 additions & 0 deletions pystrukts/tree/fenwick_tree.py
Original file line number Diff line number Diff line change
@@ -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)
36 changes: 36 additions & 0 deletions tests/test_fenwick_tree.py
Original file line number Diff line number Diff line change
@@ -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