Skip to content

Commit

Permalink
#1 Linked List is an Iterable
Browse files Browse the repository at this point in the history
  • Loading branch information
taciogt committed Sep 12, 2020
1 parent 72b99ce commit 1128e31
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 30 deletions.
47 changes: 23 additions & 24 deletions src/sequences/linked_list.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
from __future__ import annotations

# from typing import TypeVar, Generic, Sized, Iterable, Container, Tuple, Sequence, Collection,
# MutableSequence, Reversible
from typing import TypeVar, Sized
from typing import TypeVar, Generic, Sized, Iterable, Iterator, Sequence, Optional

T = TypeVar('T')


class _NodeIterator:
def __init__(self, current_node):
class _NodeIterator(Iterator[T]):
def __init__(self, current_node: Optional[_Node[T]]):
self.current_node = current_node

def __iter__(self):
return self

def __next__(self):
def __next__(self) -> T:
if self.current_node is not None:
_next = self.current_node.content
self.current_node = self.current_node.next
Expand All @@ -23,15 +21,15 @@ def __next__(self):
raise StopIteration


class _Node:
def __init__(self, content, *next_nodes):
class _Node(Generic[T]):
def __init__(self, content: T, *next_nodes: Sequence[T]):
self._content = content
try:
self._next = _Node(next_nodes[0], *next_nodes[1:])
self._next: Optional[_Node] = _Node(next_nodes[0], *next_nodes[1:])
except IndexError:
self._next = None

def __str__(self):
def __str__(self) -> str:
node_str = f'Node({self._content})'
if self._next is not None:
node_str += f'->{self._next}'
Expand All @@ -42,7 +40,7 @@ def __eq__(self, other):
return False
return self.content == other.content and self.next == other.next

def __getitem__(self, item):
def __getitem__(self, item) -> T:
if isinstance(item, slice):
raise NotImplementedError

Expand All @@ -65,13 +63,11 @@ def __iter__(self):
return _NodeIterator(current_node=self)


# https://docs.python.org/3/reference/datamodel.html#emulating-container-types

class LinkedList(Sized):
class LinkedList(Sized, Iterable[T]):

def __init__(self, *args):
try:
self.head = _Node(args[0], *args[1:])
self.head: Optional[_Node] = _Node(args[0], *args[1:])
except IndexError:
self.head = None

Expand All @@ -96,20 +92,23 @@ def __str__(self):
def __repr__(self):
return self.__str__()

def __getitem__(self, item):
def __getitem__(self, item) -> LinkedList:
if isinstance(item, slice):
def include_index(index):
match_step = item.step is None or index % item.step == 0
should_stop = item.stop is not None and index >= item.stop
return item.start <= index and match_step and not should_stop

return LinkedList(*[node for index, node in enumerate(self.head)
if include_index(index)])
if self.head is not None:
return LinkedList(*[node for index, node in enumerate(self.head)
if include_index(index)])
else:
return LinkedList()
if isinstance(item, int):
return self.head[item]
if self.head is not None:
return self.head[item]
else:
raise IndexError('LinkedList item is out of range')
raise TypeError(f'{self.__class__.__name__} indices must be integers or slices, not {item.__class__.__name__}')


# if __name__ == '__main__':
# for item in LinkedList(1, 2, 3):
# print(item)
def __iter__(self) -> Iterator[T]:
return _NodeIterator(self.head)
22 changes: 16 additions & 6 deletions src/sequences/test_linked_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from sequences.linked_list import LinkedList


class TestLinkedList(TestCase):
class TestLinkedListBasicMethods(TestCase):
def test_str(self):
linked_list = LinkedList(1, 2, 3)
self.assertEqual(str(linked_list), 'LinkedList[Node(1)->Node(2)->Node(3)]')
Expand All @@ -28,25 +28,28 @@ def test_equality__with_same_type(self):
self.assertNotEqual(LinkedList(1, 2, 3), LinkedList(1, 2))
self.assertNotEqual(LinkedList(1, 2), LinkedList(1, 2, 3))

def test_length(self): # LinkedList is a Sized type

class TestLinkedListIsCollection(TestCase):
def test_linked_list_is_sized(self):
self.assertEqual(0, len(LinkedList()))
self.assertEqual(1, len(LinkedList(1)))
self.assertEqual(2, len(LinkedList(1, 2)))
self.assertEqual(3, len(LinkedList(1, 2, 3)))

def test_sort(self):
pass
def test_linked_list_is_iterable(self):
self.assertEqual([], [item for item in LinkedList()])
self.assertEqual([1], [item for item in LinkedList(1)])
self.assertEqual([1, 2], [item for item in LinkedList(1, 2)])

def test_append(self):
pass

@skip('Implement after insert is available')
def test_reverse(self):

self.assertEqual(reversed(LinkedList(1)), LinkedList(1))


class TestLinkedListIndexing(TestCase):
class TestLinkedListIsSequence(TestCase):
def test_access_item_by_index(self):
self.assertEqual(LinkedList('a', 'b', 'c')[0], 'a')
self.assertEqual(LinkedList('a', 'b', 'c')[1], 'b')
Expand All @@ -59,7 +62,14 @@ def test_list_slicing(self):
self.assertEqual(linked_list[8:], LinkedList(8, 9, 10))
self.assertEqual(linked_list[0:8:3], LinkedList(0, 3, 6))

def test_list_slicing_empty_list(self):
self.assertEqual(LinkedList()[0:8:3], LinkedList())

def test_access_item_with_invalid_index(self):
with self.assertRaises(IndexError) as context_manager:
_ = LinkedList()[0]
self.assertEqual(str(context_manager.exception), 'LinkedList item is out of range')

with self.assertRaises(IndexError) as context_manager:
_ = LinkedList('a', 'b', 'c')[4]
self.assertEqual(str(context_manager.exception), 'LinkedList item is out of range')
Expand Down

0 comments on commit 1128e31

Please sign in to comment.