Permalink
Browse files

double linked list implementation

  • Loading branch information...
Kartones committed Jul 3, 2017
1 parent db723ea commit 2b29094e145cda86abc05c84960020581fc9d6d3
Showing with 352 additions and 0 deletions.
  1. +2 −0 .gitignore
  2. +1 −0 README.md
  3. +4 −0 double-liked-list/.flake8
  4. +104 −0 double-liked-list/linked_list.py
  5. +221 −0 double-liked-list/test_linked_list.py
  6. +20 −0 double-liked-list/test_linters.py
@@ -3,3 +3,5 @@ urls.txt
*__pycache__*
*/config.py
twitter-purge/run.sh
*.cache*
*.mypy_cache*
@@ -15,6 +15,7 @@ Miscellaneous Python code snippets and experiments.
```

### Folders / Projects
* `\double-linked-list`: A basic Python implementation of a double linked list. Based on a blog post but improved a bit, main purpose was to have a working example with mypy & flake8 linter tests running.
* `\game-of-life-kata`: A coding kata. Inside-out TDD approach + PyGame "visualizer".
* `\pelican\plugins`: Plugins for the Pelican static site generator tool.
* `\pelican\blogengine_to_pelican.py`: Tool to migrate from BlogEngine.Net to Pelican (posts and pages).
@@ -0,0 +1,4 @@
[flake8]
max-line-length = 120
inline-quotes = "
ignore = E701
@@ -0,0 +1,104 @@
from typing import Dict, Optional, Any


# Based on https://dbader.org/blog/python-linked-list

class LinkedListNode:

def __init__(self,
data: Dict=None,
previous_node: Optional["LinkedListNode"]=None,
next_node: Optional["LinkedListNode"]=None) -> None:
if data is None:
data = dict()
self.data = data
self.previous_node = previous_node
self.next_node = next_node


class LinkedList:
"""
Double linked list implementation
"""

def __init__(self) -> None:
self.head = None # type: Optional["LinkedListNode"]
self.tail = None # type: Optional["LinkedListNode"]

def prepend(self, data: Dict) -> None:
"""
O(1) Insert new element at the list head
"""
new_head_node = LinkedListNode(data=data, next_node=self.head)
if self.head is not None:
self.head.previous_node = new_head_node
new_head_node.next_node = self.head
else:
self.tail = new_head_node
self.head = new_head_node

def append(self, data: Dict) -> None:
"""
O(1) Insert new element at list tail
"""
new_tail_node = LinkedListNode(data=data, previous_node=self.tail)
if self.tail is not None:
self.tail.next_node = new_tail_node
new_tail_node.previous_node = self.tail
else:
self.head = new_tail_node
self.tail = new_tail_node

def find(self, target_key: str, target_value: Any) -> Optional["LinkedListNode"]:
"""
O(N) Search for first occurrence of desired data
"""
current_node = self.head
while (current_node is not None and (target_key not in current_node.data.keys() or
(target_key in current_node.data.keys() and current_node.data[target_key] != target_value))):
current_node = current_node.next_node
return current_node # Or None if not found

def remove(self, target_key: str, target_value: Any) -> None:
"""
O(N) Delete first occurrence of desired data
"""
target_item = self.find(target_key=target_key, target_value=target_value)
if target_item is None:
return

previous_node = target_item.previous_node
next_node = target_item.next_node

if previous_node is not None:
previous_node.next_node = next_node
if next_node is not None:
next_node.previous_node = previous_node

if self.head == target_item:
self.head = next_node
if self.tail == target_item:
self.tail = previous_node

target_item.previous_node = None
target_item.next_node = None

def reverse(self) -> None:
"""
O(N) Reverse list in-place
"""
current_node = self.head
new_tail_node = self.head
previous_node = None
next_node = None
while current_node is not None:
next_node = current_node.next_node
current_node.next_node = previous_node
previous_node = current_node
current_node = next_node
self.head = previous_node
if self.head is not None:
self.head.previous_node = None
self.tail = new_tail_node
if self.tail is not None:
self.tail.next_node = None
@@ -0,0 +1,221 @@
from linked_list import LinkedList


def test_initial_list_state() -> None:
linked_list = LinkedList()
assert linked_list.head is None
assert linked_list.tail is None


def test_prepending_elements() -> None:
node_1_data = {"id": 1}
node_2_data = {"id": 2}
node_3_data = {"id": 3}
linked_list = LinkedList()

linked_list.prepend(data=node_1_data)
assert linked_list.head is not None
assert linked_list.head.data == node_1_data
assert linked_list.tail is not None
assert linked_list.tail.data == node_1_data

linked_list.prepend(data=node_2_data)
assert linked_list.head.data == node_2_data
assert linked_list.head.next_node is not None
assert linked_list.head.next_node.data == node_1_data
assert linked_list.head.previous_node is None
assert linked_list.tail.data == node_1_data
assert linked_list.tail.next_node is None
assert linked_list.tail.previous_node is not None
assert linked_list.tail.previous_node.data == node_2_data

linked_list.prepend(data=node_3_data)
assert linked_list.head.data == node_3_data
assert linked_list.head.next_node is not None
assert linked_list.head.next_node.data == node_2_data
assert linked_list.head.previous_node is None
assert linked_list.tail.data == node_1_data
assert linked_list.tail.next_node is None
assert linked_list.tail.previous_node is not None
assert linked_list.tail.previous_node.data == node_2_data

middle_node = linked_list.head.next_node
assert middle_node.data == node_2_data
assert middle_node.next_node is not None
assert middle_node.next_node.data is not None
assert middle_node.next_node.data == node_1_data
assert middle_node.previous_node is not None
assert middle_node.previous_node.data is not None
assert middle_node.previous_node.data == node_3_data


def test_appending_elements() -> None:
node_1_data = {"id": 1}
node_2_data = {"id": 2}
node_3_data = {"id": 3}
linked_list = LinkedList()

linked_list.append(data=node_1_data)
assert linked_list.head is not None
assert linked_list.head.data == node_1_data
assert linked_list.tail is not None
assert linked_list.tail.data == node_1_data

linked_list.append(data=node_2_data)
assert linked_list.head.data == node_1_data
assert linked_list.head.next_node is not None
assert linked_list.head.next_node.data == node_2_data
assert linked_list.head.previous_node is None
assert linked_list.tail.data == node_2_data
assert linked_list.tail.next_node is None
assert linked_list.tail.previous_node is not None
assert linked_list.tail.previous_node.data == node_1_data

linked_list.append(data=node_3_data)
assert linked_list.head.data == node_1_data
assert linked_list.head.next_node is not None
assert linked_list.head.next_node.data == node_2_data
assert linked_list.head.previous_node is None
assert linked_list.tail.data == node_3_data
assert linked_list.tail.next_node is None
assert linked_list.tail.previous_node is not None
assert linked_list.tail.previous_node.data == node_2_data

middle_node = linked_list.head.next_node
assert middle_node.data == node_2_data
assert middle_node.next_node is not None
assert middle_node.next_node.data is not None
assert middle_node.next_node.data == node_3_data
assert middle_node.previous_node is not None
assert middle_node.previous_node.data is not None
assert middle_node.previous_node.data == node_1_data


def test_finding_elements() -> None:
node_1_data = {"id": 1}
node_2_data = {"id": 2}
node_3_data = {"id": 3}
other_data = {"id": 4}
linked_list = LinkedList()

linked_list.append(node_1_data)
linked_list.append(node_2_data)
linked_list.append(node_3_data)

node = linked_list.find(target_key="id", target_value=node_1_data["id"])
assert node is not None
assert node.data == node_1_data

node = linked_list.find(target_key="id", target_value=node_2_data["id"])
assert node is not None
assert node.data == node_2_data

node = linked_list.find(target_key="id", target_value=node_3_data["id"])
assert node is not None
assert node.data == node_3_data

assert linked_list.find(target_key="id", target_value=other_data["id"]) is None
assert linked_list.find(target_key="inexistant_key", target_value="an_irrelevant_value") is None


def test_finding_elements_on_empty_list() -> None:
linked_list = LinkedList()

linked_list.find(target_key="id", target_value="an_irrelevant_value")

assert linked_list.head is None
assert linked_list.tail is None


def test_removing_elements() -> None:
node_1_data = {"id": 1}
node_2_data = {"id": 2}
node_3_data = {"id": 3}
node_4_data = {"id": 4}

linked_list = LinkedList()

linked_list.append(node_1_data)
linked_list.append(node_2_data)
linked_list.append(node_3_data)
linked_list.append(node_4_data)

linked_list.remove(target_key="id", target_value=node_2_data["id"])
assert linked_list.head is not None
assert linked_list.head.data == node_1_data
assert linked_list.head.next_node is not None
assert linked_list.head.next_node.data == node_3_data
assert linked_list.tail is not None
assert linked_list.tail.data == node_4_data
assert linked_list.tail.previous_node is not None
assert linked_list.tail.previous_node.data == node_3_data

linked_list.remove(target_key="id", target_value=node_1_data["id"])
assert linked_list.head is not None
assert linked_list.head.data == node_3_data
assert linked_list.head.next_node is not None
assert linked_list.head.next_node.data == node_4_data
assert linked_list.tail is not None
assert linked_list.tail.data == node_4_data
assert linked_list.tail.previous_node is not None
assert linked_list.tail.previous_node.data == node_3_data

linked_list.remove(target_key="id", target_value=node_4_data["id"])
assert linked_list.head is not None
assert linked_list.head.data == node_3_data
assert linked_list.head.next_node is None
assert linked_list.tail is not None
assert linked_list.tail.data == node_3_data
assert linked_list.tail.previous_node is None

linked_list.remove(target_key="id", target_value=node_3_data["id"])
assert linked_list.head is None
assert linked_list.tail is None


def test_removing_from_empty_list() -> None:
linked_list = LinkedList()

linked_list.remove(target_key="id", target_value="an_irrelevant_value")

assert linked_list.head is None
assert linked_list.tail is None


def test_reversing_list() -> None:
node_1_data = {"id": 1}
node_2_data = {"id": 2}
node_3_data = {"id": 3}
node_4_data = {"id": 4}
linked_list = LinkedList()

linked_list.append(node_1_data)
linked_list.append(node_2_data)
linked_list.append(node_3_data)
linked_list.append(node_4_data)

linked_list.reverse()

assert linked_list.head is not None
assert linked_list.head.data == node_4_data
assert linked_list.head.previous_node is None
second_node = linked_list.head.next_node
assert second_node is not None
assert second_node.data == node_3_data
third_node = second_node.next_node
assert third_node is not None
assert third_node.data == node_2_data
fourth_node = third_node.next_node
assert fourth_node is not None
assert fourth_node.data == node_1_data
assert fourth_node.next_node is None
assert linked_list.tail is not None
assert fourth_node.data == linked_list.tail.data


def test_reversing_empty_list() -> None:
linked_list = LinkedList()
linked_list.reverse()

assert linked_list.head is None
assert linked_list.tail is None
@@ -0,0 +1,20 @@
import subprocess
import sys

from flake8.api import legacy as flake8


def test_flake8_compliance() -> None:
pep8style = flake8.get_style_guide(config_file=".flake8")
result = pep8style.check_files(["."])
assert result.total_errors == 0


def test_mypy_compliance() -> None:
result = subprocess.call("/usr/local/bin/mypy --ignore-missing-imports --disallow-untyped-defs --check-untyped-defs"
" --warn-redundant-casts --warn-return-any"
" --warn-unused-ignores --strict-optional .",
shell=True,
stdout=sys.stdout,
stderr=sys.stderr)
assert result == 0

0 comments on commit 2b29094

Please sign in to comment.