Skip to content

Commit

Permalink
Add reversal support for all tree traversals
Browse files Browse the repository at this point in the history
  • Loading branch information
realshouzy committed May 14, 2024
1 parent 38814eb commit 0887f3e
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 59 deletions.
9 changes: 1 addition & 8 deletions nrw/algorithms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"quick_sort",
"preorder",
"inorder",
"reverse_inorder",
"postorder",
"levelorder",
)
Expand All @@ -32,10 +31,4 @@
quick_sort,
selection_sort,
)
from nrw.algorithms._traversal import (
inorder,
levelorder,
postorder,
preorder,
reverse_inorder,
)
from nrw.algorithms._traversal import inorder, levelorder, postorder, preorder
23 changes: 12 additions & 11 deletions nrw/algorithms/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ __all__: Final[tuple[str, ...]] = (
"quick_sort",
"preorder",
"inorder",
"reverse_inorder",
"postorder",
"levelorder",
)
Expand All @@ -36,32 +35,34 @@ def insertion_sort(lst: List[ComparableContentT]) -> List[ComparableContentT]: .
def merge_sort(lst: List[ComparableContentT]) -> List[ComparableContentT]: ...
def quick_sort(lst: List[ComparableContentT]) -> List[ComparableContentT]: ...
@overload
def preorder(tree: BinaryTree[_T]) -> List[_T]: ...
def preorder(tree: BinaryTree[_T], *, reverse: bool = False) -> List[_T]: ...
@overload
def preorder(
tree: BinarySearchTree[ComparableContentT],
*,
reverse: bool = False,
) -> List[ComparableContentT]: ...
@overload
def inorder(tree: BinaryTree[_T]) -> List[_T]: ...
def inorder(tree: BinaryTree[_T], *, reverse: bool = False) -> List[_T]: ...
@overload
def inorder(
tree: BinarySearchTree[ComparableContentT],
*,
reverse: bool = False,
) -> List[ComparableContentT]: ...
@overload
def reverse_inorder(tree: BinaryTree[_T]) -> List[_T]: ...
@overload
def reverse_inorder(
tree: BinarySearchTree[ComparableContentT],
) -> List[ComparableContentT]: ...
@overload
def postorder(tree: BinaryTree[_T]) -> List[_T]: ...
def postorder(tree: BinaryTree[_T], *, reverse: bool = False) -> List[_T]: ...
@overload
def postorder(
tree: BinarySearchTree[ComparableContentT],
*,
reverse: bool = False,
) -> List[ComparableContentT]: ...
@overload
def levelorder(tree: BinaryTree[_T]) -> List[_T]: ...
def levelorder(tree: BinaryTree[_T], *, reverse: bool = False) -> List[_T]: ...
@overload
def levelorder(
tree: BinarySearchTree[ComparableContentT],
*,
reverse: bool = False,
) -> List[ComparableContentT]: ...
72 changes: 43 additions & 29 deletions nrw/algorithms/_traversal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
__all__: Final[tuple[str, ...]] = (
"preorder",
"inorder",
"reverse_inorder",
"postorder",
"levelorder",
)
Expand All @@ -19,66 +18,74 @@

def preorder(
tree: BinaryTree[_T] | BinarySearchTree[ComparableContentT],
*,
reverse: bool = False,
) -> List[_T | ComparableContentT]:
result: List[_T | ComparableContentT] = List()

if tree.is_empty:
return result

result.append(tree.content)
result.concat(preorder(tree.left_tree))
result.concat(preorder(tree.right_tree))
if not reverse:
result.append(tree.content)
result.concat(preorder(tree.left_tree))
result.concat(preorder(tree.right_tree))
else:
result.append(tree.content)
result.concat(preorder(tree.right_tree))
result.concat(preorder(tree.left_tree))

return result


def inorder(
tree: BinaryTree[_T] | BinarySearchTree[ComparableContentT],
*,
reverse: bool = False,
) -> List[_T | ComparableContentT]:
result: List[_T | ComparableContentT] = List()

if tree.is_empty:
return result

result.concat(preorder(tree.left_tree))
result.append(tree.content)
result.concat(preorder(tree.right_tree))

return result


def reverse_inorder(
tree: BinaryTree[_T] | BinarySearchTree[ComparableContentT],
) -> List[_T | ComparableContentT]:
result: List[_T | ComparableContentT] = List()

if tree.is_empty:
return result

result.concat(preorder(tree.right_tree))
result.append(tree.content)
result.concat(preorder(tree.left_tree))
if not reverse:
result.concat(preorder(tree.left_tree))
result.append(tree.content)
result.concat(preorder(tree.right_tree))
else:
result.concat(preorder(tree.right_tree))
result.append(tree.content)
result.concat(preorder(tree.left_tree))

return result


def postorder(
tree: BinaryTree[_T] | BinarySearchTree[ComparableContentT],
*,
reverse: bool = False,
) -> List[_T | ComparableContentT]:
result: List[_T | ComparableContentT] = List()

if tree.is_empty:
return result

result.concat(preorder(tree.left_tree))
result.concat(preorder(tree.right_tree))
result.append(tree.content)
if not reverse:
result.concat(preorder(tree.left_tree))
result.concat(preorder(tree.right_tree))
result.append(tree.content)
else:
result.concat(preorder(tree.right_tree))
result.concat(preorder(tree.left_tree))
result.append(tree.content)

return result


def levelorder(
tree: BinaryTree[_T] | BinarySearchTree[ComparableContentT],
*,
reverse: bool = False,
) -> List[_T | ComparableContentT]:
if tree.is_empty:
return List()
Expand All @@ -91,10 +98,17 @@ def levelorder(
trees.content
)
assert current_tree is not None
if not current_tree.left_tree.is_empty:
trees.append(current_tree.left_tree)
if not current_tree.right_tree.is_empty:
trees.append(current_tree.right_tree)

if not reverse:
if not current_tree.left_tree.is_empty:
trees.append(current_tree.left_tree)
if not current_tree.right_tree.is_empty:
trees.append(current_tree.right_tree)
else:
if not current_tree.right_tree.is_empty:
trees.append(current_tree.right_tree)
if not current_tree.left_tree.is_empty:
trees.append(current_tree.left_tree)
trees.next()

result: List[_T | ComparableContentT] = List()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ target-version = "py312"
line-length = 88

[tool.ruff.lint.extend-per-file-ignores]
"./tests/*_test.py" = ["SLF001", "D100", "D103", "PLR2004"]
"./tests/*_test.py" = ["SLF001", "D100", "D103", "PLR2004", "D102"]
"./tests/*.py" = ["D104"]
"./nrw/database/*.py" = ["BLE001", "PLR0913", "ARG002"]
"./nrw/database/*.pyi" = ["PLR0913"]
Expand Down
104 changes: 94 additions & 10 deletions tests/algorithms/traversal_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"""Tests for `datastructures._traversal`."""
from __future__ import annotations

from typing import TYPE_CHECKING, Callable, Iterator
from typing import TYPE_CHECKING, Iterator, Protocol

import pytest

from nrw.algorithms import inorder, levelorder, postorder, preorder, reverse_inorder
from nrw.algorithms import inorder, levelorder, postorder, preorder
from nrw.datastructures import BinarySearchTree, BinaryTree

if TYPE_CHECKING:
Expand All @@ -31,16 +31,28 @@ def binary_tree() -> BinaryTree[int]:
return tree


class Traverser(Protocol):
"""Unnecessary protocol because `Callable` cannot express complex signatures."""

def __call__(
self,
tree: BinaryTree[int] | BinarySearchTree[int],
*,
reverse: bool = False,
) -> List[int]: ...


@pytest.mark.parametrize("empty_tree", [BinaryTree[int](), BinarySearchTree[int]()])
@pytest.mark.parametrize(
"traverse",
[inorder, postorder, preorder, reverse_inorder, levelorder],
[inorder, postorder, preorder, levelorder],
)
def test_traversal_on_empty_tree(
empty_tree: BinaryTree[int] | BinarySearchTree[int],
traverse: Callable[[BinaryTree[int] | BinarySearchTree[int]], List[int]],
traverse: Traverser,
) -> None:
assert traverse(empty_tree).is_empty
assert traverse(empty_tree, reverse=True).is_empty


def test_inorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> None:
Expand All @@ -53,6 +65,16 @@ def test_inorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> None:
result.next()


def test_reverse_inorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> None:
expected_result: Iterator[int] = iter((2, 1, 0))
result: List[int] = inorder(binary_tree, reverse=True)

result.to_first()
while result.has_access:
assert result.content == next(expected_result)
result.next()


def test_postorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> None:
expected_result: Iterator[int] = iter((0, 2, 1))
result: List[int] = postorder(binary_tree)
Expand All @@ -63,6 +85,18 @@ def test_postorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> Non
result.next()


def test_reverse_postorder_traversal_on_binary_tree(
binary_tree: BinaryTree[int],
) -> None:
expected_result: Iterator[int] = iter((2, 0, 1))
result: List[int] = postorder(binary_tree, reverse=True)

result.to_first()
while result.has_access:
assert result.content == next(expected_result)
result.next()


def test_preorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> None:
expected_result: Iterator[int] = iter((1, 0, 2))
result: List[int] = preorder(binary_tree)
Expand All @@ -73,9 +107,11 @@ def test_preorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> None
result.next()


def test_reverse_inorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> None:
expected_result: Iterator[int] = iter((2, 1, 0))
result: List[int] = reverse_inorder(binary_tree)
def test_reverse_preorder_traversal_on_binary_tree(
binary_tree: BinaryTree[int],
) -> None:
expected_result: Iterator[int] = iter((1, 2, 0))
result: List[int] = preorder(binary_tree, reverse=True)

result.to_first()
while result.has_access:
Expand All @@ -93,6 +129,18 @@ def test_levelorder_traversal_on_binary_tree(binary_tree: BinaryTree[int]) -> No
result.next()


def test_reverse_levelorder_traversal_on_binary_tree(
binary_tree: BinaryTree[int],
) -> None:
expected_result: Iterator[int] = iter((1, 2, 0))
result: List[int] = levelorder(binary_tree, reverse=True)

result.to_first()
while result.has_access:
assert result.content == next(expected_result)
result.next()


def test_inorder_traversal_on_binary_search_tree(bst: BinarySearchTree[int]) -> None:
expected_result: Iterator[int] = iter((0, 1, 2))
result: List[int] = inorder(bst)
Expand All @@ -103,6 +151,18 @@ def test_inorder_traversal_on_binary_search_tree(bst: BinarySearchTree[int]) ->
result.next()


def test_reverse_inorder_traversal_on_binary_search_tree(
bst: BinarySearchTree[int],
) -> None:
expected_result: Iterator[int] = iter((2, 1, 0))
result: List[int] = inorder(bst, reverse=True)

result.to_first()
while result.has_access:
assert result.content == next(expected_result)
result.next()


def test_postorder_traversal_on_binary_search_tree(bst: BinarySearchTree[int]) -> None:
expected_result: Iterator[int] = iter((0, 2, 1))
result: List[int] = postorder(bst)
Expand All @@ -113,6 +173,18 @@ def test_postorder_traversal_on_binary_search_tree(bst: BinarySearchTree[int]) -
result.next()


def test_reverse_postorder_traversal_on_binary_search_tree(
bst: BinarySearchTree[int],
) -> None:
expected_result: Iterator[int] = iter((2, 0, 1))
result: List[int] = postorder(bst, reverse=True)

result.to_first()
while result.has_access:
assert result.content == next(expected_result)
result.next()


def test_preorder_traversal_on_binary_search_tree(bst: BinarySearchTree[int]) -> None:
expected_result: Iterator[int] = iter((1, 0, 2))
result: List[int] = preorder(bst)
Expand All @@ -123,11 +195,11 @@ def test_preorder_traversal_on_binary_search_tree(bst: BinarySearchTree[int]) ->
result.next()


def test_reverse_inorder_traversal_on_binary_search_tree(
def test_reverse_preorder_traversal_on_binary_search_tree(
bst: BinarySearchTree[int],
) -> None:
expected_result: Iterator[int] = iter((2, 1, 0))
result: List[int] = reverse_inorder(bst)
expected_result: Iterator[int] = iter((1, 2, 0))
result: List[int] = preorder(bst, reverse=True)

result.to_first()
while result.has_access:
Expand All @@ -147,5 +219,17 @@ def test_levelorder_traversal_on_binary_search_tree(
result.next()


def test_reverse_levelorder_traversal_on_binary_search_tree(
bst: BinarySearchTree[int],
) -> None:
expected_result: Iterator[int] = iter((1, 2, 0))
result: List[int] = levelorder(bst, reverse=True)

result.to_first()
while result.has_access:
assert result.content == next(expected_result)
result.next()


if __name__ == "__main__":
raise SystemExit(pytest.main())

0 comments on commit 0887f3e

Please sign in to comment.