From 97faf40de4b7a1dc458738e5602802d92d61587c Mon Sep 17 00:00:00 2001 From: mishaga Date: Fri, 3 May 2024 12:35:58 +0100 Subject: [PATCH] version 0.0.6 --- .github/workflows/jobs.yml | 123 ++++ .github/workflows/lint-n-test.yml | 30 - .gitignore | 1 + README.md | 396 +----------- basic_data_structure/__init__.py | 193 +++++- basic_data_structure/exceptions.py | 14 - basic_data_structure/exceptions/__init__.py | 5 + .../exceptions/list_exceptions.py | 21 + .../exceptions/stack_exceptions.py | 5 + basic_data_structure/iterators/__init__.py | 5 + .../iterators/list_node_iterator.py | 44 ++ .../list_value_iterator.py} | 43 +- basic_data_structure/linked_list.py | 581 ++++++++++++++++++ basic_data_structure/list/__init__.py | 0 basic_data_structure/list/linked_list.py | 293 --------- basic_data_structure/list/list_node.py | 19 - basic_data_structure/nodes/__init__.py | 5 + basic_data_structure/nodes/list_node.py | 47 ++ basic_data_structure/nodes/tree_node.py | 114 ++++ basic_data_structure/stack.py | 162 +++++ basic_data_structure/stack/__init__.py | 0 basic_data_structure/stack/stack.py | 79 --- basic_data_structure/tree/__init__.py | 0 basic_data_structure/tree/tree_node.py | 22 - poetry.lock | 115 +++- pyproject.toml | 18 +- tests/test_linked_list/test_append.py | 19 + tests/test_linked_list/test_delete.py | 55 -- tests/test_linked_list/test_delete_item.py | 67 ++ tests/test_linked_list/test_get_item.py | 35 +- tests/test_linked_list/test_has_cycle.py | 6 +- tests/test_linked_list/test_head.py | 16 + tests/test_linked_list/test_insert.py | 42 +- tests/test_linked_list/test_iter_nodes.py | 3 +- tests/test_linked_list/test_iter_values.py | 4 +- tests/test_linked_list/test_len.py | 10 +- tests/test_linked_list/test_prepend.py | 11 + tests/test_linked_list/test_reverse.py | 2 +- tests/test_linked_list/test_reversed.py | 2 +- tests/test_linked_list/test_rotate.py | 2 +- tests/test_stack/test_bool.py | 32 +- tests/test_stack/test_clear.py | 30 +- tests/test_stack/test_init.py | 8 +- tests/test_stack/test_len.py | 24 +- tests/test_stack/test_pop.py | 68 +- tests/test_stack/test_push.py | 24 +- 46 files changed, 1703 insertions(+), 1092 deletions(-) create mode 100644 .github/workflows/jobs.yml delete mode 100644 .github/workflows/lint-n-test.yml delete mode 100644 basic_data_structure/exceptions.py create mode 100644 basic_data_structure/exceptions/__init__.py create mode 100644 basic_data_structure/exceptions/list_exceptions.py create mode 100644 basic_data_structure/exceptions/stack_exceptions.py create mode 100644 basic_data_structure/iterators/__init__.py create mode 100644 basic_data_structure/iterators/list_node_iterator.py rename basic_data_structure/{list/iterators.py => iterators/list_value_iterator.py} (50%) create mode 100644 basic_data_structure/linked_list.py delete mode 100644 basic_data_structure/list/__init__.py delete mode 100644 basic_data_structure/list/linked_list.py delete mode 100644 basic_data_structure/list/list_node.py create mode 100644 basic_data_structure/nodes/__init__.py create mode 100644 basic_data_structure/nodes/list_node.py create mode 100644 basic_data_structure/nodes/tree_node.py create mode 100644 basic_data_structure/stack.py delete mode 100644 basic_data_structure/stack/__init__.py delete mode 100644 basic_data_structure/stack/stack.py delete mode 100644 basic_data_structure/tree/__init__.py delete mode 100644 basic_data_structure/tree/tree_node.py create mode 100644 tests/test_linked_list/test_append.py delete mode 100644 tests/test_linked_list/test_delete.py create mode 100644 tests/test_linked_list/test_delete_item.py create mode 100644 tests/test_linked_list/test_prepend.py diff --git a/.github/workflows/jobs.yml b/.github/workflows/jobs.yml new file mode 100644 index 0000000..6abce47 --- /dev/null +++ b/.github/workflows/jobs.yml @@ -0,0 +1,123 @@ +name: Jobs + + +on: + push: + branches: + - main + + +permissions: + contents: read + + +jobs: + + lint: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + export POETRY_VIRTUALENVS_IN_PROJECT=false + export POETRY_VIRTUALENVS_CREATE=false + export POETRY_NO_INTERACTION=1 + export POETRY_VERSION=1.8.2 + curl -sSL https://install.python-poetry.org | python3 - + poetry install --no-root + + - name: Analysing the code with flake8 + run: flake8 --count basic_data_structure/ + + + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + export POETRY_VIRTUALENVS_IN_PROJECT=false + export POETRY_VIRTUALENVS_CREATE=false + export POETRY_NO_INTERACTION=1 + export POETRY_VERSION=1.8.2 + curl -sSL https://install.python-poetry.org | python3 - + poetry install --no-root + + - name: Testing code with pytest + run: pytest -v + + + build-doc: + needs: + - lint + - test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + export POETRY_VIRTUALENVS_IN_PROJECT=false + export POETRY_VIRTUALENVS_CREATE=false + export POETRY_NO_INTERACTION=1 + export POETRY_VERSION=1.8.2 + curl -sSL https://install.python-poetry.org | python3 - + poetry install --no-root + + - name: Generate documentation + run: | + mkdir docs + pdoc -o docs/ -d google --no-search --no-show-source basic_data_structure + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/ + + + deploy-doc: + needs: build-doc + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/lint-n-test.yml b/.github/workflows/lint-n-test.yml deleted file mode 100644 index 50a8b89..0000000 --- a/.github/workflows/lint-n-test.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Lint & Test - -on: push - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [ "3.9", "3.10", "3.11", "3.12" ] - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - export POETRY_VIRTUALENVS_IN_PROJECT=false - export POETRY_VIRTUALENVS_CREATE=false - export POETRY_NO_INTERACTION=1 - export POETRY_VERSION=1.8.2 - curl -sSL https://install.python-poetry.org | python3 - - poetry install --no-root - - name: Analysing the code with flake8 - run: | - flake8 --count basic_data_structure/ - - name: Testing code with pytest - run: | - pytest -v diff --git a/.gitignore b/.gitignore index 9ae6849..a124685 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__ /.idea /dist +/docs diff --git a/README.md b/README.md index 86cf591..2d6101e 100644 --- a/README.md +++ b/README.md @@ -2,399 +2,27 @@ Implementation of basic sata structures in Python -Supported python versions: `3.9` → `3.12` - Including: -1. [Stack](#stack) -2. [Linked list](#linked-list) -3. [Binary tree](#binary-tree) +1. Stack +2. Linked list +3. Binary tree Expected data structures in future versions: 1. Queue 2. Double-ended queue -3. Actual binary tree class (now we have `TreeNode` class only) +3. Actual binary tree class (now package contains `TreeNode` class only) 4. Red-black tree -# Stack - -A stack is a data type that serves as a collection of elements with two main operations – -`push` (adds an element to the collection), and `pop` (removes the most recently added element). - -## Methods - -### `def __init__(*args) -> None` -Initialises stack with given sequence of elements -This sequence (`args`) could be empty, so the stack will be empty at start - -Example: -```python -initially_empty_stack = Stack() -not_empty_stack = Stack(1, 2, 3) # the order of elements is stack will be reversed -``` - -### `def __bool__() -> bool` -Returns `False` if stack is empty -If stack contains at least one element, returns `True` - -### `def __len__() -> int` -Returns length (count of elements) of the stack -If the stack is empty, returns `0` - -### `def push(value: Any) -> None` -Adds element to the stack - -### `def pop() -> Any` -Pops the last element from the stack -Function returns the element and deletes it from the stack -If the stack is empty, this function raises `EmptyStackError` exception - -### `def clear() -> None` -Clears the stack (removes all the elements) -Length of the stack afterward will be equal to `0` - -## Example - -```python -from basic_data_structure import Stack - - -def init_from_iterable() -> None: - print('Init from iterable:') - - stack = Stack(0, 1, 2, 3, 4, 5) - # or like this: - # stack = Stack(*[0, 1, 2, 3, 4, 5]) - # stack = Stack(*range(6)) - stack.push(6) - stack.push(7) - stack.push(8) - print('Stack length:', len(stack)) - - while stack: - value = stack.pop() - print(value, end=' ') - - print('') - - -def manual_pushing() -> None: - print('Manual pushing:') - - stack = Stack() - stack.push('a') - stack.push('b') - stack.push('c') - - while stack: - value = stack.pop() - print(value, end=' ') - - print('') - - -if __name__ == '__main__': - init_from_iterable() - manual_pushing() -``` - -The `__iter__` and `__next__` methods are not implemented intentionally. -If you want to iterate over a Stack like this `for i in stack`, you're better off -using built-in list instead, because this kind of iteration will implicitly -call `pop()` on each iteration. Therefore, your stack will be empty by the end -of a loop. -It is better to call `pop()` explicitly, so use while loop like this: - -```python -while stack: - element = stack.pop() - ... -``` - - -# Linked list - -A linked list is a linear collection of data elements whose order is not given by their physical -placement in memory. Instead, each element points to the next. It is a data structure consisting -of a collection of nodes which together represent a sequence. - -## Methods - -### `def __init__(*args) -> None` -Initialises list with given sequence of elements -This sequence (`args`) could be empty, so the list will be empty at the start - -Example: -```python -initially_empty_list = LinkedList() -not_empty_list = LinkedList(1, 2, 3) -``` - -### `def __bool__() -> bool` -Returns `False` if the list is empty -If list contains at least one element, returns `True` - -### `def __len__() -> int` -Returns length (count of elements) in the list -If the list is empty, returns `0` - -If the list contains a cycle, raises `ListHasCycleError` exception -This is `O(n)` operation - -### `def __getitem__(index: int) -> ListNode` -Returns a node at particular index - -If index is out of range, raises `ListIndexError` exception -If the list contains a cycle, raises `ListHasCycleError` exception -Negative indexes supported -Slices are not supported -This is `O(n)` operation - -Example: -```python -lst = LinkedList(1, 2, 3) -node = lst[1] # will return node with value `2` -# to retrieve value itself, use attr `value` -print(node.value) # will print out `2` -``` - -### `def __delitem__(index: int) -> None` -Returns a node at particular index - -If index is out of range, raises `ListIndexError` exception -If the list contains a cycle, raises `ListHasCycleError` exception -Negative indexes supported -Slices are not supported -This is `O(n)` operation - -Example: -```python -lst = LinkedList(1, 2, 3) -del lst[1] # will delete node with value `2` -``` - -### `def __reversed__() -> LinkedList` -Returns a reversed copy of the list, the original list remains the same - -If the list contains a cycle, raises `ListHasCycleError` exception -This is `O(n)` operation - -Example: -```python -lst = LinkedList(1, 2, 3) -rev = reversed(lst) # contains nodes `3` → `2` → `1` -``` - -### `def __iter__() -> ListNodeIterator` -Returns an iterator, each call `next` method on which returns a `ListNode` - -Example: -```python -lst = LinkedList(1, 2, 3) -for node in list: - print(node.value) -``` - -### `def values() -> ListValueIterator` -Returns an iterator, each call `next` method on which returns a value of a node - -Example: -```python -lst = LinkedList(1, 2, 3) -for val in list.values(): - print(val) -``` - -### `def insert(index: int, value: Any) -> None` -Insert a value into given index -Mind, that "value" in this case is not a `ListNode`, but actual value of a future node - -If the index is greater than or equal to the length of the list, the element will be inserted -at the end of the list. If the negative index is out of range, the element will be inserted -at the beginning of the list. - -If the list contains a cycle, raises `ListHasCycleError` exception -Negative indexes supported -This is `O(n)` operation - -Example: -```python -lst = LinkedList(1, 3) -lst.insert(1, 2) # will add element betwin `1` and `3` -lst.insert(len(lst), 4) # will add to the end -lst.insert(100, 5) # will add to the end -lst.insert(-6, 0) # will add to the beginning -lst.insert(-100, -1) # will add to the beginning -``` - -### `def clear() -> None` -Clears the list (removes all the elements) -The length of the list afterward will be equal to `0` - -### `def reverse() -> None` -Reverses the list -Unlike `reversed(lst)`, `lst.reverse()` will rearrange the list itself and will return `None` - -If the list contains a cycle, raises `ListHasCycleError` exception -This is `O(n)` operation - -Example: -```python -lst = LinkedList(1, 2, 3) # contains nodes `1` → `2` → `3` -lst.reverse() # now contains nodes `3` → `2` → `1` -``` - -### `def rotate(count: int) -> None` -Rotates (shifts) list to the right or left - -If the list contains a cycle, raises `ListHasCycleError` exception -This is `O(n)` operation - -Example: -```python -lst = LinkedList(1, 2, 3, 4, 5) # `1` → `2` → `3` → `4` → `5` -lst.rotate(1) # `5` → `1` → `2` → `3` → `4` -lst.rotate(-2) # `2` → `3` → `4` → `5` → `1` -``` - -### `def has_cycle() -> bool` -Returns `True` if the list contains cycle, `False` otherwise - -This is `O(n)` operation - -Example: -```python -lst = LinkedList(1, 2, 3, 4, 5) -print(lst.has_cycle()) # False -lst[-1].next = lst[0] -print(lst.has_cycle()) # True -``` - -### property `def head() -> Optional[ListNode]` -If you want to iterate through the sequence of nodes by yourself, feel free -to use `head` property of the list. -If the list is empty, property will return `None` - -### property setter `def head(new_head: Optional[ListNode]) -> None` -Sets new head to the list -Clears the list, if new head is `None` - -## Examples - -```python -from basic_data_structure import LinkedList - - -def main(): - lst = LinkedList(8, 7, 6, 5, 3) - - lst.insert(4, 4) - lst.insert(100, 2) - lst.insert(0, 9) - lst.insert(0, 1) - - lst.rotate(-1) - lst.reverse() - - for val in lst.values(): - print(val, end=' ') - - -if __name__ == '__main__': - main() -``` - -If you'd like to create linked list by yourself, you can use `ListNode` class like this, for example: - -```python -from basic_data_structure import ListNode - - -def main(): - head = ListNode(1, ListNode(2, ListNode(3))) - while head: - print(head.value, end=' ') - head = head.next - - -if __name__ == '__main__': - main() -``` - - -## Binary tree - -A binary tree is a tree data structure in which each node has at most two children, -referred to as the left child and the right child. - -There is no `BinaryTree` class so far in the project, but you can create a tree -using `TreeNode` class like this: - -```python -from typing import Generator, Optional - -from basic_data_structure import TreeNode - - -def generate_tree() -> TreeNode: - """Generate binary tree. - - 8 - ┌──────┴───────┐ - 4 12 - ┌───┴───┐ ┌───┴───┐ - 2 6 10 14 - ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ - 1 3 5 7 9 11 13 15 - """ - return TreeNode( - 8, - left=TreeNode( - 4, - left=TreeNode( - 2, - left=TreeNode(1), - right=TreeNode(3), - ), - right=TreeNode( - 6, - left=TreeNode(5), - right=TreeNode(7), - ), - ), - right=TreeNode( - 12, - left=TreeNode( - 10, - left=TreeNode(9), - right=TreeNode(11), - ), - right=TreeNode( - 14, - left=TreeNode(13), - right=TreeNode(15), - ), - ), - ) - - -def dfs(root: Optional[TreeNode]) -> Generator[int, None, None]: - """Depth-first search.""" - if root: - if root.left: - yield from dfs(root.left) - - yield root.value - - if root.right: - yield from dfs(root.right) +Supported python versions: `3.9` → `3.12` +Links: -def main(): - print('Depth-first search (DFS)') - for data in dfs(root=generate_tree()): - print(data, end=' ') +* [GitHub](https://github.com/mishaga/basic_data_structure) +* [PyPi](https://pypi.org/project/basic-data-structure/) +* [Documentation](https://mishaga.github.io/basic_data_structure/) +Install: -if __name__ == '__main__': - main() +```bash +pip install basic-data-structure ``` diff --git a/basic_data_structure/__init__.py b/basic_data_structure/__init__.py index 3f1ef6e..54ee398 100644 --- a/basic_data_structure/__init__.py +++ b/basic_data_structure/__init__.py @@ -1,4 +1,189 @@ -from basic_data_structure.list.linked_list import LinkedList -from basic_data_structure.list.list_node import ListNode -from basic_data_structure.stack.stack import Stack -from basic_data_structure.tree.tree_node import TreeNode +"""# Basic data structures in python + +Implementation of basic sata structures in Python + +Including: +1. [Stack](basic_data_structure/stack.html) +2. [Linked list](basic_data_structure/linked_list.html) +3. [Binary tree](basic_data_structure/nodes/tree_node.html) + +Expected data structures in future versions: +1. Queue +2. Double-ended queue +3. Actual binary tree class (now package contains `TreeNode` class only) +4. Red-black tree + +Supported python versions: `3.9` → `3.12` + +Links: + +* [GitHub](https://github.com/mishaga/basic_data_structure) +* [PyPi](https://pypi.org/project/basic-data-structure/) +* [Documentation](https://mishaga.github.io/basic_data_structure/) + +Installation: + +```bash +pip install basic-data-structure +``` + +## Examples + +### Stack + +```python +from basic_data_structure import Stack + + +def main() -> None: + # to initialise empty stack do this: stack = Stack() + + stack = Stack(0, 1, 2, 3, 4, 5) + # or like this: stack = Stack(*[0, 1, 2, 3, 4, 5]) + # or like this: stack = Stack(*range(6)) + + stack.push(6) + stack.push(7) + stack.push(8) + + print('Stack length:', len(stack)) + + while stack: + value = stack.pop() + print(value, end=' ') + + # Output: 8 7 6 5 4 3 2 1 0 + + +if __name__ == '__main__': + main() + +``` + +### Linked list + +```python +from basic_data_structure import LinkedList + + +def main(): + lst = LinkedList('oops', 8, 7, 6, 5, 3) + + del lst[0] # delete node + node = lst[4] # get node + + print('Saved node:', node.value) + + lst.insert(4, 4) # insert new value into list + lst.append(2) # append a new value + lst.prepend(9) # prepend a new value (ad to the beginning) + lst.prepend(1) + + lst.rotate(-1) # rotate (shift) the list + lst.reverse() # reverse list + + print('List length:', len(lst)) # length of the list + + for val in lst.values(): + # iteration over values + print(val, end=' ') + + # Output: 1 2 3 4 5 6 7 8 9 + + print('') + + for node in lst: + # iteration over nodes + print(node.value, end=' ') + + # Output: 1 2 3 4 5 6 7 8 9 + + +if __name__ == '__main__': + main() + +``` + +### Binary tree + +```python +from typing import Generator, Optional + +from basic_data_structure import TreeNode + + +def generate_tree() -> TreeNode: + \"""Generate binary tree. + + 8 + ┌──────┴───────┐ + 4 12 + ┌───┴───┐ ┌───┴───┐ + 2 6 10 14 + ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ + 1 3 5 7 9 11 13 15 + \""" + return TreeNode( + 8, + left=TreeNode( + 4, + left=TreeNode( + 2, + left=TreeNode(1), + right=TreeNode(3), + ), + right=TreeNode( + 6, + left=TreeNode(5), + right=TreeNode(7), + ), + ), + right=TreeNode( + 12, + left=TreeNode( + 10, + left=TreeNode(9), + right=TreeNode(11), + ), + right=TreeNode( + 14, + left=TreeNode(13), + right=TreeNode(15), + ), + ), + ) + + +def dfs(root: Optional[TreeNode]) -> Generator[int, None, None]: + \"""Depth-first search.\""" + if root: + if root.left: + yield from dfs(root.left) + + yield root.value + + if root.right: + yield from dfs(root.right) + + +def main(): + print('Depth-first search (DFS)') + for data in dfs(root=generate_tree()): + print(data, end=' ') + + # Output: + # Depth-first search (DFS) + # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + + +if __name__ == '__main__': + main() + +``` +""" + + +from basic_data_structure.linked_list import LinkedList +from basic_data_structure.nodes.list_node import ListNode +from basic_data_structure.nodes.tree_node import TreeNode +from basic_data_structure.stack import Stack diff --git a/basic_data_structure/exceptions.py b/basic_data_structure/exceptions.py deleted file mode 100644 index e041f96..0000000 --- a/basic_data_structure/exceptions.py +++ /dev/null @@ -1,14 +0,0 @@ -class EmptyStackError(Exception): - """Empty stack error.""" - - -class ListHasCycleError(Exception): - """List has cycle error.""" - - -class ListIndexError(Exception): - """List index error.""" - - -class NotListNodeError(Exception): - """Not list node error.""" diff --git a/basic_data_structure/exceptions/__init__.py b/basic_data_structure/exceptions/__init__.py new file mode 100644 index 0000000..ee3537d --- /dev/null +++ b/basic_data_structure/exceptions/__init__.py @@ -0,0 +1,5 @@ +"""# Exceptions + +1. Linked list exceptions +2. Stack exceptions +""" diff --git a/basic_data_structure/exceptions/list_exceptions.py b/basic_data_structure/exceptions/list_exceptions.py new file mode 100644 index 0000000..bc4482d --- /dev/null +++ b/basic_data_structure/exceptions/list_exceptions.py @@ -0,0 +1,21 @@ +"""# List exceptions""" + + +class ListHasCycleError(Exception): + """When list has cycle.""" + + +class ListIndexOfRangeError(Exception): + """When list index out of range.""" + + +class ListIndexIsNotAnIntError(Exception): + """When list index is not an integer.""" + + +class ListNegativeIndexError(Exception): + """When list index is negative.""" + + +class NotListNodeError(Exception): + """When assigning not a ListNode object.""" diff --git a/basic_data_structure/exceptions/stack_exceptions.py b/basic_data_structure/exceptions/stack_exceptions.py new file mode 100644 index 0000000..2b41ae0 --- /dev/null +++ b/basic_data_structure/exceptions/stack_exceptions.py @@ -0,0 +1,5 @@ +"""# Stack exceptions""" + + +class StackIsEmptyError(Exception): + """When stack is empty.""" diff --git a/basic_data_structure/iterators/__init__.py b/basic_data_structure/iterators/__init__.py new file mode 100644 index 0000000..5c7a6d2 --- /dev/null +++ b/basic_data_structure/iterators/__init__.py @@ -0,0 +1,5 @@ +"""# Iterators + +1. Linked list node iterator +2. Linked list value iterator +""" diff --git a/basic_data_structure/iterators/list_node_iterator.py b/basic_data_structure/iterators/list_node_iterator.py new file mode 100644 index 0000000..a2e4d87 --- /dev/null +++ b/basic_data_structure/iterators/list_node_iterator.py @@ -0,0 +1,44 @@ +"""# List node iterator""" + +from typing import Optional + +from basic_data_structure.nodes.list_node import ListNode + + +class ListNodeIterator: + """Linked list node iterator. + + Iterates through list and returns ListNode. + """ + + def __init__(self, head: Optional[ListNode]) -> None: + """Init iterator. + + Parameters: + head: linked list head node + """ + self.__pointer = head + + def __iter__(self) -> 'ListNodeIterator': + """Return iterator. + + Returns: + iterator (self) + """ + return self + + def __next__(self) -> ListNode: + """Retrieve next node from linked list. + + Returns: + next ListNode + + Raises: + StopIteration: when reached the end of the list + """ + if self.__pointer is None: + raise StopIteration + + response = self.__pointer + self.__pointer = self.__pointer.next + return response diff --git a/basic_data_structure/list/iterators.py b/basic_data_structure/iterators/list_value_iterator.py similarity index 50% rename from basic_data_structure/list/iterators.py rename to basic_data_structure/iterators/list_value_iterator.py index a7eeb53..7952d70 100644 --- a/basic_data_structure/list/iterators.py +++ b/basic_data_structure/iterators/list_value_iterator.py @@ -1,45 +1,8 @@ -from typing import Any, Optional - -from basic_data_structure.list.list_node import ListNode - - -class ListNodeIterator: - """Linked list node iterator. - - Iterates through list and returns ListNode. - """ - - def __init__(self, head: Optional[ListNode]) -> None: - """Init iterator. - - Parameters: - head: linked list head node - """ - self.__pointer = head - - def __iter__(self) -> 'ListNodeIterator': - """Return iterator. +"""# List value iterator""" - Returns: - iterator (self) - """ - return self - - def __next__(self) -> ListNode: - """Retrieve next node from linked list. - - Returns: - next ListNode - - Raises: - StopIteration: when reached the end of the list - """ - if self.__pointer is None: - raise StopIteration +from typing import Any, Optional - response = self.__pointer - self.__pointer = self.__pointer.next - return response +from basic_data_structure.nodes.list_node import ListNode class ListValueIterator: diff --git a/basic_data_structure/linked_list.py b/basic_data_structure/linked_list.py new file mode 100644 index 0000000..c73d4cd --- /dev/null +++ b/basic_data_structure/linked_list.py @@ -0,0 +1,581 @@ +"""# Linked list + +A linked list is a linear collection of data elements whose order is not given by their physical +placement in memory. Instead, each element points to the next. It is a data structure consisting +of a collection of nodes which together represent a sequence. + +## Example + +```python +from basic_data_structure import LinkedList + + +def main(): + lst = LinkedList('oops', 8, 7, 6, 5, 3) + + del lst[0] # delete node + node = lst[4] # get node + + print('Saved node:', node.value) + + lst.insert(4, 4) # insert new value into list + lst.append(2) # append a new value + lst.prepend(9) # prepend a new value (ad to the beginning) + lst.prepend(1) + + lst.rotate(-1) # rotate (shift) the list + lst.reverse() # reverse list + + print('List length:', len(lst)) # length of the list + + for val in lst.values(): + # iteration over values + print(val, end=' ') + + # Output: 1 2 3 4 5 6 7 8 9 + + print('') + + for node in lst: + # iteration over nodes + print(node.value, end=' ') + + # Output: 1 2 3 4 5 6 7 8 9 + + +if __name__ == '__main__': + main() + +``` + +## Dunder methods + +1. `__bool__` – return `False` if the list is empty, `True` otherwise + ```python + if linked_list: + ... + ``` +2. `__len__` – return length (count of elements) of the list + ```python + if len(linked_list) > 2: + ... + ``` +3. `__getitem__` – return a node at particular index + ```python + second_node = linked_list[2] + # mind that actual "ListNode" will be returned, not its value + # mind that negative indexes or slices are not supported + ``` +4. `__delitem__` – delete a node at a specific index + ```python + del linked_list[0] + # mind that negative indexes or slices are not supported + ``` +5. `__reversed__` – return a reversed **copy** of the list + ```python + rev = reversed(linked_list) + # original linked_list will remain unchanged + # to reverse list itself, use this: linked_list.reverse() + ``` +6. `__iter__` – return a node iterator `ListNodeIterator` + ```python + iterator = iter(linked_list) + node1 = next(iterator) + node2 = next(iterator) + ... + + # or + + for node in linked_list: + ... + # iterates over nodes + # to iterate over values, use this: linked_list.values() + + ``` + +## ListNode + +`basic_data_structure.nodes.list_node.ListNode` + +If you'd like to create linked list by yourself, you can use `ListNode` class like this, for example: + +```python +from basic_data_structure import ListNode + + +def main(): + head = ListNode(1, ListNode(2, ListNode(3))) + while head: + print(head.value, end=' ') + head = head.next + + # Output: 1 2 3 + + +if __name__ == '__main__': + main() + +``` +""" + +from typing import Any, Optional + +from basic_data_structure.exceptions.list_exceptions import ( + ListHasCycleError, + ListIndexIsNotAnIntError, + ListIndexOfRangeError, + ListNegativeIndexError, + NotListNodeError, +) +from basic_data_structure.iterators.list_node_iterator import ListNodeIterator +from basic_data_structure.iterators.list_value_iterator import ( + ListValueIterator, +) +from basic_data_structure.nodes.list_node import ListNode + + +class LinkedList: # noqa: WPS214 Found too many methods + """Linked list data structure.""" + + def __init__(self, *args) -> None: + """Initialise a linked list with given sequence of elements. + + `args` could be empty, so the list will be empty at the start + + Time complexity: `O(n)` (when `n` is length of `args`) + + Parameters: + args: a tuple of values to start with (could be empty) + """ + self.__head = None + + previous = None + for arg in args: + pointer = ListNode(value=arg) + if self.__head is None: + self.__head = pointer + else: + previous.next = pointer + previous = pointer + + def __bool__(self) -> bool: + """Return `False` if the list is empty, `True` otherwise. + + Time complexity: `O(1)` + + Returns: + `False` if the list is empty, `True` if there is at least one element + """ + return self.__head is not None + + def __len__(self) -> int: + """Return length (count of elements) of the list. + + Time complexity: `O(n)` + + Returns: + length of the list + + Raises: + ListHasCycleError: when the list has a cycle + """ + length = 0 + + slow = self.__head + fast = self.__head + while slow: + slow = slow.next + if fast and fast.next: + fast = fast.next.next + if fast == slow: + raise ListHasCycleError + + length += 1 + + return length + + def __getitem__(self, index: int) -> ListNode: + """Return a node at particular index. + + Mind that actual `ListNode` will be returned, not its value + + Time complexity: `O(n)` + + Parameters: + index: index + + Returns: + node at given index + + Raises: + ListIndexOfRangeError: when there is no such index in the list + """ + self.__validate_index(index) + if self.__head is None: + raise ListIndexOfRangeError + + pointer = self.__head + idx = 0 + while pointer: + if idx == index: + return pointer + pointer = pointer.next + idx += 1 + + raise ListIndexOfRangeError + + def __delitem__(self, index: int) -> None: # noqa: WPS603 Found using restricted magic method + """Delete a node at a specific index. + + Time complexity: `O(n)` + + Parameters: + index: index + + Raises: + ListIndexOfRangeError: when there is no such index in the list + """ + self.__validate_index(index) + if self.__head is None: + raise ListIndexOfRangeError + + if index == 0: + self.__head = self.__head.next + return + + previous = None + pointer = self.__head + idx = 0 + while pointer: + if idx == index: + previous.next = pointer.next + return + previous = pointer + pointer = pointer.next + idx += 1 + + raise ListIndexOfRangeError + + def __reversed__(self) -> 'LinkedList': + """Return a reversed copy of the list. + + Time complexity: `O(n)` + + Returns: + a reversed copy of the list + + Raises: + ListHasCycleError: when the list has a cycle + """ + new_list = LinkedList() + slow = self.__head + fast = self.__head + while slow: + new_list.prepend(slow.value) + if fast and fast.next: + fast = fast.next.next + if slow == fast: + raise ListHasCycleError + slow = slow.next + return new_list + + def __iter__(self) -> ListNodeIterator: + """Return nodes iterator. + + Returns: + ListNodeIterator + """ + return ListNodeIterator(self.__head) + + @property + def head(self) -> Optional[ListNode]: + """Head of the list. + + This is `@property` with `setter`, so you can assign a new `ListNode` + to the head (or `None` to clear the list) + + ```python + lst = LinkedList('a', 'b', 'c') + node = lst.head # a ListNode with value 'a' and link to next ListNode + + # this is the same as + node = lst[0] + ``` + + Returns: + head of the list (first node) or `None` if the list is empty + """ + return self.__head + + @head.setter + def head(self, new_head: Optional[ListNode]) -> None: + """Set new head. + + Parameters: + new_head: new ListNode to be a head of the list (or `None` to clear the list) + + Raises: + NotListNodeError: when trying to assign not a `ListNode` or `None` + """ + if not isinstance(new_head, (ListNode, type(None))): + raise NotListNodeError + self.__head = new_head + + def values(self) -> ListValueIterator: # noqa: WPS110 Found wrong variable name + """Return values iterator. + + On each call `next` function on the iterator, you'll be given actual value of a `ListNode`, + not the node itself. + + If a list has a cycle, iteration over a list will be endless. + + ```python + lst = LinkedList(1, 2, 3) + + for node in lst: + # iterates over nodes + print(node.value) + + for val in lst.values(): + # iterates over values + print(val) + + ``` + + Returns: + ListValueIterator + """ + return ListValueIterator(self.__head) + + def append( + self, + value: Any, # noqa: WPS110 Found wrong variable name + ) -> None: + """Append value to the end of list. + + If list has a cycle, exception `ListHasCycleError` will be risen + + Time complexity: `O(n)` + + ```python + lst = LinkedList(1, 2) + lst.append(3) + # 1 → 2 → 3 + ``` + + Parameters: + value: a value to append (not a `ListNode`, but actual value) + + Raises: + ListHasCycleError: when the list has a cycle + """ + if self.__head is None: + self.insert(0, value) + return + + previous = None + pointer = self.__head + fast_pointer = self.__head + while pointer: + previous = pointer + pointer = pointer.next + if fast_pointer and fast_pointer.next: + fast_pointer = fast_pointer.next.next + if fast_pointer == pointer: + raise ListHasCycleError + + node = ListNode(value, pointer) + previous.next = node + + def prepend( + self, + value: Any, # noqa: WPS110 Found wrong variable name + ) -> None: + """Prepend a value to the list (add to the beginning). + + Time complexity: `O(1)` + + ```python + lst = LinkedList(2, 3) + lst.prepend(1) + # 1 → 2 → 3 + ``` + + Parameters: + value: a value to prepend (not a `ListNode`, but actual value) + """ + self.insert(0, value) + + def insert( + self, + index: int, + value: Any, # noqa: WPS110 Found wrong variable name + ) -> None: + """Insert value into given index. + + Time complexity: `O(n)` + + ```python + lst = LinkedList(1, 3) + lst.insert(index=1, value=2) # will insert element between 1 and 3 + lst.insert(index=100, value=4) # will add to the end (preferably use "append" method) + lst.insert(index=len(lst), value=5) # will add to the end (preferably use "append" method) + lst.insert(index=0, value=0) # will add to the beginning (preferably use "prepend" method) + # 0 → 1 → 2 → 3 → 4 → 5 + ``` + + Parameters: + index: a position to insert a value to (positive `int` or zero) + value: a value to insert (not a `ListNode`, but actual value) + """ + self.__validate_index(index) + + if index == 0 or self.__head is None: + node = ListNode(value, self.__head) + self.__head = node + return + + previous = None + pointer = self.__head + idx = 0 + while pointer: + previous = pointer + pointer = pointer.next + idx += 1 + if idx == index: + break + + node = ListNode(value, pointer) + previous.next = node + + def clear(self) -> None: + """Clear the list. + + Time complexity: `O(1)` + + ```python + lst = LinkedList('a', 'b', 'c') + lst.clear() + # this is the equivalent to: lst.head = None + ``` + """ + self.__head = None + + def reverse(self) -> None: + """Reverse the list. + + If list has a cycle, exception `ListHasCycleError` will be risen + + Time complexity: `O(n)` + + ```python + lst = LinkedList('a', 'b', 'c', 'd', 'e') + # before: a → b → c → d → e + + lst.reverse() + # after: e → d → c → b → a + ``` + + Raises: + ListHasCycleError: when the list has a cycle + """ + if self.has_cycle(): + raise ListHasCycleError + + pointer = self.__head + previous = None + + while pointer: + nxt = pointer.next + pointer.next = previous + previous = pointer + pointer = nxt + + self.__head = previous + + def rotate(self, count: int) -> None: # noqa: WPS210 Found too many local variables + """Rotate (shift) list. + + To rotate right, pass a positive value. To rotate left, pass a negative value. + + If list has a cycle, exception `ListHasCycleError` will be risen + + Time complexity: `O(n)` + + ```python + lst = LinkedList('a', 'b', 'c', 'd', 'e') + # a → b → c → d → e + + lst.rotate(1) + # e → a → b → c → d + + lst.rotate(-3) + # c → d → e → a → b + ``` + + Parameters: + count: number of nodes to shift + """ + if count == 0: + return + + length = len(self) + if length < 2: + return + + count = count % length # noqa: WPS350 Found usable augmented assign pattern + if count == 0: + return + + pointer = ListNode(None, self.__head) + new_end = None + idx = 0 + while pointer.next: + if idx == length - count: + new_end = pointer + pointer = pointer.next + idx += 1 + + new_head = new_end.next + new_end.next = None + pointer.next = self.__head + self.__head = new_head + + def has_cycle(self) -> bool: + """Return `True` if the list has cycle, `False` otherwise. + + Time complexity: `O(n)` + + Returns: + `True` if the list has cycle, `False` otherwise + """ + slow = self.__head + fast = self.__head + while slow: + slow = slow.next + if fast and fast.next: + fast = fast.next.next + if fast == slow: + return True + + return False + + def __validate_index(self, index: int) -> None: + """Validate "index" parameter. + + Index should be a positive int. + Slices are not supported. + Negative indexes are not supported. + + Parameters: + index: a value to validate + + Raises: + ListIndexIsNotAnIntError: when given index is not an integer (slices are not supported) + ListNegativeIndexError: when given index is less than zero + """ + if not isinstance(index, int): + raise ListIndexIsNotAnIntError + + if index < 0: + raise ListNegativeIndexError diff --git a/basic_data_structure/list/__init__.py b/basic_data_structure/list/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/basic_data_structure/list/linked_list.py b/basic_data_structure/list/linked_list.py deleted file mode 100644 index efbe876..0000000 --- a/basic_data_structure/list/linked_list.py +++ /dev/null @@ -1,293 +0,0 @@ -from typing import Any, Optional - -from basic_data_structure.exceptions import ( - ListHasCycleError, - ListIndexError, - NotListNodeError, -) -from basic_data_structure.list.iterators import ( - ListNodeIterator, - ListValueIterator, -) -from basic_data_structure.list.list_node import ListNode - - -class LinkedList: # noqa: WPS214 Found too many methods - """Linked list data structure.""" - - def __init__(self, *args) -> None: - """Init a linked list. - - Parameters: - args: a tuple of values to start with (could be empty) - """ - self.__head = None - - previous = None - for arg in args: - pointer = ListNode(value=arg) - if self.__head is None: - self.__head = pointer - else: - previous.next = pointer - previous = pointer - - def __bool__(self) -> bool: - """Return False if the list is empty, True otherwise. - - Returns: - False if the list is empty, True if there is at least one element - """ - return self.__head is not None - - def __len__(self) -> int: - """Return length of the list. - - Returns: - length of the list - - Raises: - ListHasCycleError: when the list has a cycle - """ - length = 0 - - slow = self.__head - fast = self.__head - while slow: - slow = slow.next - if fast and fast.next: - fast = fast.next.next - if fast == slow: - raise ListHasCycleError - - length += 1 - - return length - - def __getitem__(self, index: int) -> ListNode: - """Return a node at particular index. - - Parameters: - index: index - - Returns: - node at given index - """ - index = self.__validate_index(index) - pointer = self.__head - for _ in range(index): - pointer = pointer.next - return pointer - - def __delitem__(self, index: int) -> None: # noqa: WPS603 Found using restricted magic method - """Delete a node at particular index. - - Parameters: - index: index - """ - index = self.__validate_index(index) - - if index == 0: - self.__head = self.__head.next - else: - previous = None - pointer = self.__head - for _ in range(index): - previous = pointer - pointer = pointer.next - previous.next = pointer.next - - def __reversed__(self) -> 'LinkedList': - """Return a reversed copy of the list. - - Returns: - a reversed copy of the list - - Raises: - ListHasCycleError: when the list has a cycle - """ - new_list = LinkedList() - slow = self.__head - fast = self.__head - while slow: - new_list.insert(index=0, value=slow.value) - if fast and fast.next: - fast = fast.next.next - if slow == fast: - raise ListHasCycleError - slow = slow.next - return new_list - - def __iter__(self) -> ListNodeIterator: - """Return nodes iterator. - - Returns: - ListNodeIterator - """ - return ListNodeIterator(self.__head) - - @property - def head(self) -> Optional[ListNode]: - """Return head of the list. - - Returns: - head of the list - """ - return self.__head - - @head.setter - def head(self, new_head: Optional[ListNode]) -> None: - """Set new head. - - Parameters: - new_head: new ListNode to be a head of the list - - Raises: - NotListNodeError: when trying to assign not a ListNode or None - """ - if not isinstance(new_head, (ListNode, type(None))): - raise NotListNodeError - self.__head = new_head - - def values(self) -> ListValueIterator: # noqa: WPS110 Found wrong variable name - """Return values iterator. - - Returns: - ListValueIterator - """ - return ListValueIterator(self.__head) - - def insert( - self, - index: int, - value: Any, # noqa: WPS110 Found wrong variable name - ) -> None: - """Insert a value into given index. - - Parameters: - index: a position to insert a value to - value: a value to insert - """ - length = len(self) - - if index < 0: - index += length - - if index < 0: - index = 0 - - if index > length: - index = length - - if index == 0: - node = ListNode(value, self.__head) - self.__head = node - else: - previous = None - pointer = self.__head - for _ in range(index): - previous = pointer - pointer = pointer.next - node = ListNode(value, pointer) - previous.next = node - - def clear(self) -> None: - """Clear the list.""" - self.__head = None - - def reverse(self) -> None: - """Reverse the list. - - Raises: - ListHasCycleError: when the list has a cycle - """ - if self.has_cycle(): - raise ListHasCycleError - - pointer = self.__head - previous = None - - while pointer: - nxt = pointer.next - pointer.next = previous - previous = pointer - pointer = nxt - - self.__head = previous - - def rotate(self, count: int) -> None: # noqa: WPS210 Found too many local variables - """Rotate list. - - To rotate right, pass a positive value. - To rotate left, pass a negative value. - - Parameters: - count: number of nodes to shift - """ - if count == 0: - return - - length = len(self) - if length < 2: - return - - count = count % length # noqa: WPS350 Found usable augmented assign pattern - if count == 0: - return - - pointer = ListNode(None, self.__head) - new_end = None - idx = 0 - while pointer.next: - if idx == length - count: - new_end = pointer - pointer = pointer.next - idx += 1 - - new_head = new_end.next - new_end.next = None - pointer.next = self.__head - self.__head = new_head - - def has_cycle(self) -> bool: - """Return True if the list has cycle. - - Returns: - True if the list has cycle, False otherwise - """ - slow = self.__head - fast = self.__head - while slow: - slow = slow.next - if fast and fast.next: - fast = fast.next.next - if fast == slow: - return True - - return False - - def __validate_index(self, index: int) -> int: - """Validate index. - - If given negative index, trying to convert it into positive. - - Parameters: - index: index to validate - - Returns: - positive index if it's in rage - - Raises: - ListIndexError: when given index ins not an integer or when it's out of range - """ - if not isinstance(index, int): - raise ListIndexError('Index should be an integer value') - - length = len(self) - if index < 0: - index += length - - if index < 0 or length == 0 or index >= length: - raise ListIndexError('Index out of range') - - return index diff --git a/basic_data_structure/list/list_node.py b/basic_data_structure/list/list_node.py deleted file mode 100644 index 8e2bc57..0000000 --- a/basic_data_structure/list/list_node.py +++ /dev/null @@ -1,19 +0,0 @@ -from typing import Any, Optional - - -class ListNode: - """A node of a linked list.""" - - def __init__( - self, - value: Any, # noqa: WPS110 Found wrong variable name - nxt: Optional['ListNode'] = None, - ): - """Init a list node. - - Parameters: - value: a value of the node - nxt: (optional) next node - """ - self.value = value # noqa: WPS110 Found wrong variable name - self.next = nxt diff --git a/basic_data_structure/nodes/__init__.py b/basic_data_structure/nodes/__init__.py new file mode 100644 index 0000000..4fc7ca5 --- /dev/null +++ b/basic_data_structure/nodes/__init__.py @@ -0,0 +1,5 @@ +"""# Nodes + +1. Linked list node +2. Binary tree node +""" diff --git a/basic_data_structure/nodes/list_node.py b/basic_data_structure/nodes/list_node.py new file mode 100644 index 0000000..cc6c1bd --- /dev/null +++ b/basic_data_structure/nodes/list_node.py @@ -0,0 +1,47 @@ +"""# List node + +An element of a `LinkedList` data structure. + +Contains a value and a link to the next node. +If this node is the last one, link to the next node will be `None`. + +# Example + +```python +from basic_data_structure import ListNode + + +def main(): + head = ListNode(1, ListNode(2, ListNode(3))) + while head: + print(head.value, end=' ') + head = head.next + + # Output: 1 2 3 + + +if __name__ == '__main__': + main() + +``` +""" + +from typing import Any, Optional + + +class ListNode: + """A node of a linked list.""" + + def __init__( + self, + value: Any, # noqa: WPS110 Found wrong variable name + nxt: Optional['ListNode'] = None, + ): + """Init a list node. + + Parameters: + value: a value of the node + nxt: (optional) link to next node + """ + self.value = value # noqa: WPS110 Found wrong variable name + self.next = nxt diff --git a/basic_data_structure/nodes/tree_node.py b/basic_data_structure/nodes/tree_node.py new file mode 100644 index 0000000..9fbb926 --- /dev/null +++ b/basic_data_structure/nodes/tree_node.py @@ -0,0 +1,114 @@ +"""# Tree node + +An element (node) of a binary tree + +A binary tree is a data structure in which each node has at most two children, +referred to as the left child and the right child. + +`TreeNode` contains a value and links to the left and right nodes. +This links could be empty (i.e. `None`). If there is no links to both left +and right nodes, the node is considered to be a "leaf". + +There is no `BinaryTree` class so far in the project, but you can create a tree +using `TreeNode` like show in example + +# Example + +```python +from typing import Generator, Optional + +from basic_data_structure import TreeNode + + +def generate_tree() -> TreeNode: + \"""Generate binary tree. + + 8 + ┌──────┴───────┐ + 4 12 + ┌───┴───┐ ┌───┴───┐ + 2 6 10 14 + ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ + 1 3 5 7 9 11 13 15 + \""" + return TreeNode( + 8, + left=TreeNode( + 4, + left=TreeNode( + 2, + left=TreeNode(1), + right=TreeNode(3), + ), + right=TreeNode( + 6, + left=TreeNode(5), + right=TreeNode(7), + ), + ), + right=TreeNode( + 12, + left=TreeNode( + 10, + left=TreeNode(9), + right=TreeNode(11), + ), + right=TreeNode( + 14, + left=TreeNode(13), + right=TreeNode(15), + ), + ), + ) + + +def dfs(root: Optional[TreeNode]) -> Generator[int, None, None]: + \"""Depth-first search.\""" + if root: + if root.left: + yield from dfs(root.left) + + yield root.value + + if root.right: + yield from dfs(root.right) + + +def main(): + print('Depth-first search (DFS)') + for data in dfs(root=generate_tree()): + print(data, end=' ') + + # Output: + # Depth-first search (DFS) + # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + + +if __name__ == '__main__': + main() + +``` +""" + +from typing import Any, Optional + + +class TreeNode: + """A node of a binary tree.""" + + def __init__( + self, + value: Any, # noqa: WPS110 Found wrong variable name + left: Optional['TreeNode'] = None, + right: Optional['TreeNode'] = None, + ) -> None: + """Init a tree node. + + Parameters: + value: a value of the node + left: (optional) left child of the node + right: (optional) right child of the node + """ + self.value = value # noqa: WPS110 Found wrong variable name + self.left = left + self.right = right diff --git a/basic_data_structure/stack.py b/basic_data_structure/stack.py new file mode 100644 index 0000000..72af6c6 --- /dev/null +++ b/basic_data_structure/stack.py @@ -0,0 +1,162 @@ +"""# Stack + +A stack is a data type that serves as a collection of elements with two main operations – +`push` (adds an element to the collection), and `pop` (removes the most recently added element). + +The order is FILO (First In Last Out) which implies that the element that is inserted first, +comes out last. + +## Example + +```python +from basic_data_structure import Stack + + +def main() -> None: + # to initialise empty stack do this: stack = Stack() + + stack = Stack(0, 1, 2, 3, 4, 5) + # or like this: stack = Stack(*[0, 1, 2, 3, 4, 5]) + # or like this: stack = Stack(*range(6)) + + stack.push(6) + stack.push(7) + stack.push(8) + + print('Stack length:', len(stack)) + + while stack: + value = stack.pop() + print(value, end=' ') + + # Output: 8 7 6 5 4 3 2 1 0 + + +if __name__ == '__main__': + main() + +``` + +## Dunder methods + +1. `__bool__` – return `False` if the stack is empty, `True` otherwise + ```python + while stack: + value = stack.pop() + ``` +2. `__len__` – return length (count of elements) of the stack + ```python + if len(stack) > 2: + ... + ``` + +### A note on iteration + +The `__iter__` and `__next__` methods are not implemented **intentionally**. If you want to iterate +over a Stack like this `for i in stack`, you're better off using built-in `list` instead, because +this kind of iteration will implicitly call `pop()` on each iteration. Therefore, your stack +will be empty by the end of a loop. + +It is better to call `pop()` **explicitly**, so use `while` loop like this: + +```python +while stack: + element = stack.pop() + ... +``` +""" + +from typing import Any + +from basic_data_structure.exceptions.stack_exceptions import StackIsEmptyError +from basic_data_structure.nodes.list_node import ListNode + + +class Stack: + """Stack data structure. + + LIFO (Last In First Out). + """ + + def __init__(self, *args) -> None: + """Initialise stack with given sequence of elements. + + `args` could be empty, so the stack will be empty at start + + Time complexity: `O(n)` (when `n` is length of `args`) + + Parameters: + args: a tuple of values to start with (could be empty) + """ + length = 0 + previous = None + pointer = None + for arg in args: + pointer = ListNode(value=arg, nxt=previous) + previous = pointer + length += 1 + + self.__head = pointer + self.__length = length + + def __bool__(self) -> bool: + """Return `False` if the stack is empty, `True` otherwise. + + Time complexity: `O(1)` + + Returns: + False if the stack is empty, True if there is at least one element + """ + return self.__length > 0 + + def __len__(self) -> int: + """Return length (count of elements) of the stack. + + Time complexity: `O(1)` + + Returns: + length of the stack + """ + return self.__length + + def push( + self, + value: Any, # noqa: WPS110 Found wrong variable name + ) -> None: + """Add a new element to the stack. + + Time complexity: `O(1)` + + Parameters: + value: a value to add to stack + """ + self.__head = ListNode(value=value, nxt=self.__head) + self.__length += 1 + + def pop(self) -> Any: + """Retrieve (read and delete) element from the stack. + + Time complexity: `O(1)` + + Returns: + a value from the head of the stack + + Raises: + StackIsEmptyError: when stack is empty + """ + if self.__length == 0: + raise StackIsEmptyError + + response = self.__head.value + self.__head = self.__head.next + self.__length -= 1 + + return response + + def clear(self) -> None: + """Clear the stack. + + Time complexity: `O(1)` + """ + self.__head = None + self.__length = 0 diff --git a/basic_data_structure/stack/__init__.py b/basic_data_structure/stack/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/basic_data_structure/stack/stack.py b/basic_data_structure/stack/stack.py deleted file mode 100644 index f7df9c3..0000000 --- a/basic_data_structure/stack/stack.py +++ /dev/null @@ -1,79 +0,0 @@ -from typing import Any - -from basic_data_structure.exceptions import EmptyStackError -from basic_data_structure.list.list_node import ListNode - - -class Stack: - """Stack data structure. - - LIFO (Last In, First Out). - """ - - def __init__(self, *args) -> None: - """Init a stack. - - Parameters: - args: a tuple of values to start with (could be empty) - """ - length = 0 - previous = None - pointer = None - for arg in args: - pointer = ListNode(value=arg, nxt=previous) - previous = pointer - length += 1 - - self.__head = pointer - self.__length = length - - def __bool__(self) -> bool: - """Return False if the stack is empty, True otherwise. - - Returns: - False if the stack is empty, True if there is at least one element - """ - return self.__length > 0 - - def __len__(self) -> int: - """Return length of the stack. - - Returns: - length of the stack - """ - return self.__length - - def push( - self, - value: Any, # noqa: WPS110 Found wrong variable name - ) -> None: - """Add a new element to the stack. - - Parameters: - value: a value to add to stack - """ - self.__head = ListNode(value=value, nxt=self.__head) - self.__length += 1 - - def pop(self) -> Any: - """Retrieve element from the stack. - - Returns: - a value from the head of the stack - - Raises: - EmptyStackError: when stack is empty - """ - if self.__length == 0: - raise EmptyStackError - - response = self.__head.value - self.__head = self.__head.next - self.__length -= 1 - - return response - - def clear(self) -> None: - """Clear the stack.""" - self.__head = None - self.__length = 0 diff --git a/basic_data_structure/tree/__init__.py b/basic_data_structure/tree/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/basic_data_structure/tree/tree_node.py b/basic_data_structure/tree/tree_node.py deleted file mode 100644 index 95e36fe..0000000 --- a/basic_data_structure/tree/tree_node.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Any, Optional - - -class TreeNode: - """A node of a binary tree.""" - - def __init__( - self, - value: Any, # noqa: WPS110 Found wrong variable name - left: Optional['TreeNode'] = None, - right: Optional['TreeNode'] = None, - ) -> None: - """Init a tree node. - - Parameters: - value: a value of the node - left: (optional) left child of the node - right: (optional) right child of the node - """ - self.value = value # noqa: WPS110 Found wrong variable name - self.left = left - self.right = right diff --git a/poetry.lock b/poetry.lock index bec89bc..09c804d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -78,13 +78,13 @@ files = [ [[package]] name = "docutils" -version = "0.21.2" +version = "0.20.1" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.9" +python-versions = ">=3.7" files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] [[package]] @@ -356,6 +356,23 @@ files = [ [package.extras] colors = ["colorama (>=0.4.6)"] +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -380,6 +397,75 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + [[package]] name = "mccabe" version = "0.7.0" @@ -424,6 +510,25 @@ files = [ {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, ] +[[package]] +name = "pdoc" +version = "14.4.0" +description = "API Documentation for Python Projects" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pdoc-14.4.0-py3-none-any.whl", hash = "sha256:6ea4fe07620b1f7601e2708a307a257636ec206e20b5611640b30f2e3cab47d6"}, + {file = "pdoc-14.4.0.tar.gz", hash = "sha256:c92edc425429ccbe287ace2a027953c24f13de53eab484c1a6d31ca72dd2fda9"}, +] + +[package.dependencies] +Jinja2 = ">=2.11.0" +MarkupSafe = "*" +pygments = ">=2.12.0" + +[package.extras] +dev = ["hypothesis", "mypy", "pdoc-pyo3-sample-library (==1.0.11)", "pygments (>=2.14.0)", "pytest", "pytest-cov", "pytest-timeout", "ruff", "tox", "types-pygments"] + [[package]] name = "pep8-naming" version = "0.13.3" @@ -719,4 +824,4 @@ typing_extensions = ">=4.0,<5.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "b15b57515ac0ceb435721e0915b85d929d3734b7e0b3e17a8018bea7484702d1" +content-hash = "99dba69ca54575712e32a1640041e9a9bb2c788e59ffabfe8d2fe9fb62f4244a" diff --git a/pyproject.toml b/pyproject.toml index 3f96895..8083930 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "basic-data-structure" -version = "0.0.5" +version = "0.0.6" description = "Implementation of basic sata structures in Python" authors = ["Mikhail Shagov "] readme = "README.md" @@ -15,6 +15,7 @@ keywords = ["data structure", "data structures", "python data structures", "basi [tool.poetry.urls] "Issues" = "https://github.com/mishaga/basic_data_structure/issues" +"Documentation" = "https://mishaga.github.io/basic_data_structure/" [tool.poetry.dependencies] python = "^3.9" @@ -24,6 +25,7 @@ wemake-python-styleguide = "^0.19.2" flake8-pyproject = "^1.2.3" pytest = "^8.2.0" isort = "^5.13.2" +pdoc = "^14.4.0" [tool.isort] @@ -47,14 +49,18 @@ class_attributes_order = [ "method", "private_method", ] -ignore = ["D100", "D104", "WPS112"] +ignore = ["D300", "D301", "D400", "RST214", "RST215", "RST201", "RST213", "RST301", "WPS112"] per-file-ignores = [ - "*/__init__.py: F401, WPS300, WPS412", + "*/__init__.py: F401, WPS412", ] # B001 – Do not use bare except # B008 – Do not perform function calls in argument defaults # D100 – Missing docstring in public module +# D103 – Missing docstring in public function # D104 – Missing docstring in public package +# D300 – Use """triple double quotes""" +# D301 – Use r""" if any backslashes in a docstring +# D400 – First line should end with a period # DAR101 – Missing parameter(s) in Docstring # DAR201 – Missing "Returns" in Docstring # DAR301 – Missing "Yields" in Docstring @@ -63,6 +69,11 @@ per-file-ignores = [ # F401 – Imported but unused # E711 – comparison to None # N805 – first argument of a method should be named 'self' +# RST214 – Inline literal start-string without end-string +# RST215 – Inline interpreted text or phrase reference start-string without end-string +# RST201 – Block quote ends without a blank line; unexpected unindent +# RST213 – Inline emphasis start-string without end-string +# RST301 – Unexpected indentation # S101 – Use of assert detected # WPS110 – Found wrong variable name # WPS112 – Found private name pattern @@ -91,5 +102,6 @@ per-file-ignores = [ # WPS442 – Found outer scope names shadowing # WPS450 – Found protected object import # WPS451 – Found positional-only argument +# WPS507 – Found useless `len()` compare # WPS520 – Found compare with falsy constant # Q001 – Single quote multiline found but double quotes preferred diff --git a/tests/test_linked_list/test_append.py b/tests/test_linked_list/test_append.py new file mode 100644 index 0000000..8e42e1d --- /dev/null +++ b/tests/test_linked_list/test_append.py @@ -0,0 +1,19 @@ +import pytest + +from basic_data_structure import LinkedList +from basic_data_structure.exceptions.list_exceptions import ListHasCycleError + + +def test_empty_list(empty_list: LinkedList): + empty_list.append('a') + assert list(empty_list.values()) == ['a'] + + +def test_not_empty(str_list: LinkedList): + str_list.append('d') + assert list(str_list.values()) == ['a', 'b', 'c', 'd'] + + +def test_cycle(cycled_list: LinkedList): + with pytest.raises(ListHasCycleError): + cycled_list.append(6) diff --git a/tests/test_linked_list/test_delete.py b/tests/test_linked_list/test_delete.py deleted file mode 100644 index d895a35..0000000 --- a/tests/test_linked_list/test_delete.py +++ /dev/null @@ -1,55 +0,0 @@ -import pytest - -from basic_data_structure import LinkedList -from basic_data_structure.exceptions import ListIndexError, ListHasCycleError - - -def test_fist(): - lst = LinkedList() - lst.insert(0, 1) - lst.insert(1, 2) - lst.insert(2, 3) - assert list(lst.values()) == [1, 2, 3] - del lst[0] - assert list(lst.values()) == [2, 3] - - -def test_not_first(int_list: LinkedList): - assert list(int_list.values()) == [1, 2, 3] - del int_list[1] - assert list(int_list.values()) == [1, 3] - del int_list[1] - assert list(int_list.values()) == [1] - - -def test_negative_index(int_list: LinkedList): - del int_list[-2] - assert list(int_list.values()) == [1, 3] - del int_list[-1] - assert list(int_list.values()) == [1] - del int_list[-1] - assert list(int_list.values()) == [] - - -def test_delete_in_loop(int_list: LinkedList): - assert list(int_list.values()) == [1, 2, 3] - for _ in range(len(int_list)): - del int_list[0] - assert list(int_list) == [] - - -@pytest.mark.parametrize('idx', (0, 1, -1, 2, -2, 10, -10)) -def test_empty_list(empty_list: LinkedList, idx: int): - with pytest.raises(ListIndexError): - del empty_list[idx] - - -@pytest.mark.parametrize('idx', (3, -4, 10, -10)) -def test_index_range(str_list: LinkedList, idx: int): - with pytest.raises(ListIndexError): - del str_list[idx] - - -def test_cycle(cycled_list: LinkedList): - with pytest.raises(ListHasCycleError): - del cycled_list[0] diff --git a/tests/test_linked_list/test_delete_item.py b/tests/test_linked_list/test_delete_item.py new file mode 100644 index 0000000..3839ded --- /dev/null +++ b/tests/test_linked_list/test_delete_item.py @@ -0,0 +1,67 @@ +import pytest + +from basic_data_structure import LinkedList +from basic_data_structure.exceptions.list_exceptions import ( + ListIndexIsNotAnIntError, + ListIndexOfRangeError, + ListNegativeIndexError, +) + + +def test_fist(int_list: LinkedList): + del int_list[0] + assert list(int_list.values()) == [2, 3] + + +def test_middle(int_list: LinkedList): + del int_list[1] + assert list(int_list.values()) == [1, 3] + + +def test_last(int_list: LinkedList): + del int_list[2] + assert list(int_list.values()) == [1, 2] + + +def test_not_first(int_list: LinkedList): + assert list(int_list.values()) == [1, 2, 3] + del int_list[1] + assert list(int_list.values()) == [1, 3] + del int_list[1] + assert list(int_list.values()) == [1] + + +@pytest.mark.parametrize('idx', (-1, -2, -3)) +def test_negative_index(int_list: LinkedList, idx: int): + with pytest.raises(ListNegativeIndexError): + del int_list[idx] + + +def test_delete_in_loop(int_list: LinkedList): + assert list(int_list.values()) == [1, 2, 3] + for _ in range(len(int_list)): + del int_list[0] + assert list(int_list) == [] + + +@pytest.mark.parametrize('idx', (0, 1, 2, 10)) +def test_empty_list(empty_list: LinkedList, idx: int): + with pytest.raises(ListIndexOfRangeError) as q: + del empty_list[idx] + print(q) + + +@pytest.mark.parametrize('idx', (3, 4, 10)) +def test_index_range(str_list: LinkedList, idx: int): + with pytest.raises(ListIndexOfRangeError): + del str_list[idx] + + +@pytest.mark.parametrize('idx', range(20)) +def test_cycle(cycled_list: LinkedList, idx: int): + del cycled_list[idx] + + +def test_slice(str_list: LinkedList): + with pytest.raises(ListIndexIsNotAnIntError): + del str_list[:] diff --git a/tests/test_linked_list/test_get_item.py b/tests/test_linked_list/test_get_item.py index 69ff3fd..01b3e91 100644 --- a/tests/test_linked_list/test_get_item.py +++ b/tests/test_linked_list/test_get_item.py @@ -1,7 +1,12 @@ import pytest from basic_data_structure import LinkedList -from basic_data_structure.exceptions import ListIndexError, ListHasCycleError +from basic_data_structure.exceptions.list_exceptions import ( + ListHasCycleError, + ListIndexIsNotAnIntError, + ListIndexOfRangeError, + ListNegativeIndexError, +) def test_get_item(int_list: LinkedList): @@ -22,19 +27,35 @@ def test_negative_index(int_list: LinkedList): assert int_list[-4].value == 1 -@pytest.mark.parametrize('idx', (0, 1, -1, 2, -2, 10, -10)) +@pytest.mark.parametrize('idx', (0, 1, 2, 10)) def test_empty_list(empty_list: LinkedList, idx: int): - with pytest.raises(ListIndexError): + with pytest.raises(ListIndexOfRangeError): _ = empty_list[idx] -@pytest.mark.parametrize('idx', (3, -4, 10, -10)) +@pytest.mark.parametrize('idx', (3, 4, 10)) def test_index_range(str_list: LinkedList, idx: int): - with pytest.raises(ListIndexError): + with pytest.raises(ListIndexOfRangeError): _ = str_list[idx] -@pytest.mark.parametrize('idx', (-2, -1, 0, 1, 2, 10)) +@pytest.mark.parametrize('idx', (-1, -2, -10)) +def test_negative_index(str_list: LinkedList, idx: int): + with pytest.raises(ListNegativeIndexError): + _ = str_list[idx] + + +@pytest.mark.parametrize('idx', range(20)) def test_cycle(cycled_list: LinkedList, idx: int): with pytest.raises(ListHasCycleError): - _ = cycled_list[idx] + len(cycled_list) + + item = cycled_list[idx] + assert item is not None + assert item.value is not None + assert item.next is not None + + +def test_slice(str_list: LinkedList): + with pytest.raises(ListIndexIsNotAnIntError): + _ = str_list[:] diff --git a/tests/test_linked_list/test_has_cycle.py b/tests/test_linked_list/test_has_cycle.py index c78dced..569d4f4 100644 --- a/tests/test_linked_list/test_has_cycle.py +++ b/tests/test_linked_list/test_has_cycle.py @@ -3,14 +3,14 @@ from basic_data_structure import LinkedList, ListNode -def test_no_cycle(cycled_list: LinkedList): +def test_cycle(cycled_list: LinkedList): assert cycled_list.has_cycle() is True -@pytest.mark.parametrize('idx', (0, 1, 2, -1, -2, -3)) +@pytest.mark.parametrize('idx', (0, 1, 2)) def test_different_indexes(str_list: LinkedList, idx: int): assert str_list.has_cycle() is False - last = str_list[-1] + last = str_list[2] last.next = str_list[idx] assert str_list.has_cycle() is True diff --git a/tests/test_linked_list/test_head.py b/tests/test_linked_list/test_head.py index c625103..e687f3d 100644 --- a/tests/test_linked_list/test_head.py +++ b/tests/test_linked_list/test_head.py @@ -1,6 +1,7 @@ import pytest from basic_data_structure import LinkedList, ListNode +from basic_data_structure.exceptions.list_exceptions import NotListNodeError def test_empty_list(empty_list: LinkedList): @@ -30,3 +31,18 @@ def test_set_new(int_list: LinkedList): assert len(int_list) == 1 assert list(int_list.values()) == ['new value'] assert int_list.has_cycle() is False + + +@pytest.mark.parametrize('new_head', ( + object, + object(), + 1, + 'qwerty', + [1, 2, 3], + {4, 5, 6}, + {7: 8, 9: 10}, +)) +def test_set_incorrect(str_list: LinkedList, new_head): + assert str_list.head is not None + with pytest.raises(NotListNodeError): + str_list.head = new_head diff --git a/tests/test_linked_list/test_insert.py b/tests/test_linked_list/test_insert.py index 9eb3519..f4a2a3a 100644 --- a/tests/test_linked_list/test_insert.py +++ b/tests/test_linked_list/test_insert.py @@ -1,13 +1,13 @@ import pytest from basic_data_structure import LinkedList -from basic_data_structure.exceptions import ListHasCycleError +from basic_data_structure.exceptions.list_exceptions import ListNegativeIndexError -def test_empty_list(): - lst = LinkedList() - lst.insert(0, 'a') - assert list(lst.values()) == ['a'] +@pytest.mark.parametrize('idx', (0, 1, 2, 10)) +def test_empty_list(empty_list: LinkedList, idx: int): + empty_list.insert(idx, 'a') + assert list(empty_list.values()) == ['a'] def test_insert_between(): @@ -34,28 +34,10 @@ def test_append(): assert list(lst.values()) == ['a', 'b', 'c', 'd'] -def test_negative_one_index(): - lst = LinkedList('a', 'b', 'd') - lst.insert(-1, 'c') - assert list(lst.values()) == ['a', 'b', 'c', 'd'] - - -def test_negative_two_index(): - lst = LinkedList('a', 'c', 'd') - lst.insert(-2, 'b') - assert list(lst.values()) == ['a', 'b', 'c', 'd'] - - -def test_negative_equals_zero(): - lst = LinkedList('b', 'c', 'd') - lst.insert(-3, 'a') - assert list(lst.values()) == ['a', 'b', 'c', 'd'] - - -@pytest.mark.parametrize('idx', (-4, -5, -10, -100)) -def test_too_low_index(int_list: LinkedList, idx: int): - int_list.insert(idx, 0) - assert list(int_list.values()) == [0, 1, 2, 3] +@pytest.mark.parametrize('idx', (-1, -2, -3, -10, -100)) +def test_negative_index(int_list: LinkedList, idx: int): + with pytest.raises(ListNegativeIndexError): + int_list.insert(idx, 'any value') @pytest.mark.parametrize('idx', (4, 5, 10, 100)) @@ -64,6 +46,6 @@ def test_too_high_index(int_list: LinkedList, idx: int): assert list(int_list.values()) == [1, 2, 3, 4] -def test_cycle(cycled_list: LinkedList): - with pytest.raises(ListHasCycleError): - cycled_list.insert(0, 0) +@pytest.mark.parametrize('idx', range(10)) +def test_cycle(cycled_list: LinkedList, idx: int): + cycled_list.insert(idx, 'new value') diff --git a/tests/test_linked_list/test_iter_nodes.py b/tests/test_linked_list/test_iter_nodes.py index fa34f57..892dd8b 100644 --- a/tests/test_linked_list/test_iter_nodes.py +++ b/tests/test_linked_list/test_iter_nodes.py @@ -1,8 +1,7 @@ import pytest from basic_data_structure import LinkedList, ListNode -from basic_data_structure.exceptions import ListHasCycleError -from basic_data_structure.list.iterators import ListNodeIterator +from basic_data_structure.iterators.list_node_iterator import ListNodeIterator def test_iterator(empty_list: LinkedList): diff --git a/tests/test_linked_list/test_iter_values.py b/tests/test_linked_list/test_iter_values.py index 5f6584b..a771df9 100644 --- a/tests/test_linked_list/test_iter_values.py +++ b/tests/test_linked_list/test_iter_values.py @@ -1,7 +1,7 @@ import pytest -from basic_data_structure import LinkedList, ListNode -from basic_data_structure.list.iterators import ListValueIterator +from basic_data_structure import LinkedList +from basic_data_structure.iterators.list_value_iterator import ListValueIterator def test_iterator(): diff --git a/tests/test_linked_list/test_len.py b/tests/test_linked_list/test_len.py index 159d59c..ce63eea 100644 --- a/tests/test_linked_list/test_len.py +++ b/tests/test_linked_list/test_len.py @@ -1,7 +1,7 @@ import pytest from basic_data_structure import LinkedList, ListNode -from basic_data_structure.exceptions import ListHasCycleError +from basic_data_structure.exceptions.list_exceptions import ListHasCycleError def test_empty_list(empty_list: LinkedList): @@ -26,9 +26,11 @@ def test_mix(int_list: LinkedList): def test_manual_add(int_list: LinkedList): assert len(int_list) == 3 - item = int_list[-1] - assert item.next is None - item.next = ListNode(4) + + node = int_list[2] + assert node.next is None + + node.next = ListNode(4) assert len(int_list) == 4 diff --git a/tests/test_linked_list/test_prepend.py b/tests/test_linked_list/test_prepend.py new file mode 100644 index 0000000..3dcc027 --- /dev/null +++ b/tests/test_linked_list/test_prepend.py @@ -0,0 +1,11 @@ +from basic_data_structure import LinkedList + + +def test_empty_list(empty_list: LinkedList): + empty_list.prepend('a') + assert list(empty_list.values()) == ['a'] + + +def test_not_empty(str_list: LinkedList): + str_list.prepend('zero') + assert list(str_list.values()) == ['zero', 'a', 'b', 'c'] diff --git a/tests/test_linked_list/test_reverse.py b/tests/test_linked_list/test_reverse.py index eadd21e..a2a7178 100644 --- a/tests/test_linked_list/test_reverse.py +++ b/tests/test_linked_list/test_reverse.py @@ -1,7 +1,7 @@ import pytest from basic_data_structure import LinkedList -from basic_data_structure.exceptions import ListHasCycleError +from basic_data_structure.exceptions.list_exceptions import ListHasCycleError def test_empty_list(): diff --git a/tests/test_linked_list/test_reversed.py b/tests/test_linked_list/test_reversed.py index 96292c3..6b51121 100644 --- a/tests/test_linked_list/test_reversed.py +++ b/tests/test_linked_list/test_reversed.py @@ -1,7 +1,7 @@ import pytest from basic_data_structure import LinkedList -from basic_data_structure.exceptions import ListHasCycleError +from basic_data_structure.exceptions.list_exceptions import ListHasCycleError def test_empty_list(): diff --git a/tests/test_linked_list/test_rotate.py b/tests/test_linked_list/test_rotate.py index d8aafb6..0ba5a66 100644 --- a/tests/test_linked_list/test_rotate.py +++ b/tests/test_linked_list/test_rotate.py @@ -1,7 +1,7 @@ import pytest from basic_data_structure import LinkedList -from basic_data_structure.exceptions import ListHasCycleError +from basic_data_structure.exceptions.list_exceptions import ListHasCycleError @pytest.mark.parametrize('count', (1, 2, 3, -1, -10, 100)) diff --git a/tests/test_stack/test_bool.py b/tests/test_stack/test_bool.py index 2a5882f..d967eb0 100644 --- a/tests/test_stack/test_bool.py +++ b/tests/test_stack/test_bool.py @@ -2,31 +2,31 @@ def test_empty_stack(): - s = Stack() - assert bool(s) is False + stack = Stack() + assert bool(stack) is False def test_inited_stack(): - s = Stack('a') - assert bool(s) is True + stack = Stack('a') + assert bool(stack) is True def test_pushed_stack(): - s = Stack() - s.push(0) - assert bool(s) is True + stack = Stack() + stack.push(0) + assert bool(stack) is True def test_mix(): - s = Stack(1, 2, 3) - s.push(4) - s.push(5) - assert bool(s) is True + stack = Stack(1, 2, 3) + stack.push(4) + stack.push(5) + assert bool(stack) is True def test_clear(): - s = Stack(1, 2, 3) - s.push(4) - s.push(5) - s.clear() - assert bool(s) is False + stack = Stack(1, 2, 3) + stack.push(4) + stack.push(5) + stack.clear() + assert bool(stack) is False diff --git a/tests/test_stack/test_clear.py b/tests/test_stack/test_clear.py index 00eded4..0934fb5 100644 --- a/tests/test_stack/test_clear.py +++ b/tests/test_stack/test_clear.py @@ -1,24 +1,24 @@ import pytest from basic_data_structure import Stack -from basic_data_structure.exceptions import EmptyStackError +from basic_data_structure.exceptions.stack_exceptions import StackIsEmptyError def test_with_init(): - s = Stack(1, 2, 3) - s.clear() - assert bool(s) is False - assert len(s) == 0 - with pytest.raises(EmptyStackError): - s.pop() + stack = Stack(1, 2, 3) + stack.clear() + assert bool(stack) is False + assert len(stack) == 0 + with pytest.raises(StackIsEmptyError): + stack.pop() def test_with_push(): - s = Stack() - s.push('a') - s.push('b') - s.clear() - assert bool(s) is False - assert len(s) == 0 - with pytest.raises(EmptyStackError): - s.pop() + stack = Stack() + stack.push('a') + stack.push('b') + stack.clear() + assert bool(stack) is False + assert len(stack) == 0 + with pytest.raises(StackIsEmptyError): + stack.pop() diff --git a/tests/test_stack/test_init.py b/tests/test_stack/test_init.py index 9f4666c..7cde2e4 100644 --- a/tests/test_stack/test_init.py +++ b/tests/test_stack/test_init.py @@ -4,11 +4,11 @@ def test_init_with_args(): - s = Stack(1, 2, 3) - assert len(s) == 3 + stack = Stack(1, 2, 3) + assert len(stack) == 3 @pytest.mark.parametrize('size', (0, 1, 3, 5, 1000)) def test_init_asterisk(size: int): - s = Stack(*range(size)) - assert len(s) == size + stack = Stack(*range(size)) + assert len(stack) == size diff --git a/tests/test_stack/test_len.py b/tests/test_stack/test_len.py index b0a725e..33fec69 100644 --- a/tests/test_stack/test_len.py +++ b/tests/test_stack/test_len.py @@ -4,8 +4,8 @@ def test_empty_stack(): - s = Stack() - assert len(s) == 0 + stack = Stack() + assert len(stack) == 0 @pytest.mark.parametrize('sequence', ( @@ -14,19 +14,19 @@ def test_empty_stack(): [object()], )) def test_inited_stack(sequence: list): - s = Stack(*sequence) - assert len(s) == len(sequence) + stack = Stack(*sequence) + assert len(stack) == len(sequence) def test_pushed_stack(): - s = Stack() - s.push(0) - s.push(1) - assert len(s) == 2 + stack = Stack() + stack.push(0) + stack.push(1) + assert len(stack) == 2 def test_mix(): - s = Stack(1, 2, 3) - s.push(4) - s.push(5) - assert len(s) == 5 + stack = Stack(1, 2, 3) + stack.push(4) + stack.push(5) + assert len(stack) == 5 diff --git a/tests/test_stack/test_pop.py b/tests/test_stack/test_pop.py index 0523400..102782c 100644 --- a/tests/test_stack/test_pop.py +++ b/tests/test_stack/test_pop.py @@ -1,60 +1,60 @@ import pytest from basic_data_structure import Stack -from basic_data_structure.exceptions import EmptyStackError +from basic_data_structure.exceptions.stack_exceptions import StackIsEmptyError def test_init_empty(): - s = Stack() - s.push(1) - assert s.pop() == 1 + stack = Stack() + stack.push(1) + assert stack.pop() == 1 def test_init_not_empty(): - s = Stack(1, 2, 3, 4) - assert s.pop() == 4 + stack = Stack(1, 2, 3, 4) + assert stack.pop() == 4 def test_loop(): - s = Stack(1, 2, 3, 4, 5) + stack = Stack(1, 2, 3, 4, 5) lst = [] - while s: - lst.append(s.pop()) + while stack: + lst.append(stack.pop()) assert lst == [5, 4, 3, 2, 1] - assert len(s) == 0 - assert bool(s) is False + assert len(stack) == 0 + assert bool(stack) is False def test_flow(): - s = Stack('a', 'b', 'c', 'd') - assert len(s) == 4 + stack = Stack('a', 'b', 'c', 'd') + assert len(stack) == 4 - s.push('e') - s.push('f') - s.push('g') - assert len(s) == 7 + stack.push('e') + stack.push('f') + stack.push('g') + assert len(stack) == 7 - assert s.pop() == 'g' - assert len(s) == 6 + assert stack.pop() == 'g' + assert len(stack) == 6 - assert s.pop() == 'f' - assert len(s) == 5 + assert stack.pop() == 'f' + assert len(stack) == 5 - assert s.pop() == 'e' - assert len(s) == 4 + assert stack.pop() == 'e' + assert len(stack) == 4 - assert s.pop() == 'd' - assert len(s) == 3 + assert stack.pop() == 'd' + assert len(stack) == 3 - assert s.pop() == 'c' - assert len(s) == 2 + assert stack.pop() == 'c' + assert len(stack) == 2 - assert s.pop() == 'b' - assert len(s) == 1 + assert stack.pop() == 'b' + assert len(stack) == 1 - assert s.pop() == 'a' - assert len(s) == 0 + assert stack.pop() == 'a' + assert len(stack) == 0 - with pytest.raises(EmptyStackError): - s.pop() - assert len(s) == 0 + with pytest.raises(StackIsEmptyError): + stack.pop() + assert len(stack) == 0 diff --git a/tests/test_stack/test_push.py b/tests/test_stack/test_push.py index 868d10c..20498e7 100644 --- a/tests/test_stack/test_push.py +++ b/tests/test_stack/test_push.py @@ -2,21 +2,21 @@ def test_init_empty(): - s = Stack() - s.push(1) - assert len(s) == 1 + stack = Stack() + stack.push(1) + assert len(stack) == 1 def test_init_not_empty(): - s = Stack(1, 2, 3, 4) - s.push(5) - assert len(s) == 5 - assert s.pop() == 5 + stack = Stack(1, 2, 3, 4) + stack.push(5) + assert len(stack) == 5 + assert stack.pop() == 5 def test_push_twice(): - s = Stack() - s.push('a') - s.push('b') - assert len(s) == 2 - assert s.pop() == 'b' + stack = Stack() + stack.push('a') + stack.push('b') + assert len(stack) == 2 + assert stack.pop() == 'b'