Skip to content

Commit

Permalink
python practice disguised as linked list problems
Browse files Browse the repository at this point in the history
  • Loading branch information
Anurag Saini committed Sep 7, 2024
1 parent 5d42da6 commit 627c4e4
Show file tree
Hide file tree
Showing 45 changed files with 5,929 additions and 11 deletions.
37 changes: 37 additions & 0 deletions docs/data-structures/linked-list/clone.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Clone linked lists

<style>
.md-logo img {
content: url('/data-structures/linked-list/polyline-light.svg');
}

:root [data-md-color-scheme=slate] .md-logo img {
content: url('/data-structures/linked-list/polyline-night.svg');
}
</style>

## Shallow copy

```python linenums="1"
def __copy__(self):
copy = LinkedList()
for e in self:
copy.append(e)
return copy
```

## Unit tests

```python linenums="1"
def test_clone():
l = LinkedList()
assert copy(l) == l
assert copy(l) is not l

l = LinkedList()
l.prepend(3)
l.prepend(2)
l.prepend(1)
assert copy(l) == l
assert copy(l) is not l
```
71 changes: 71 additions & 0 deletions docs/data-structures/linked-list/equality.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Compare linked lists

<style>
.md-logo img {
content: url('/data-structures/linked-list/polyline-light.svg');
}

:root [data-md-color-scheme=slate] .md-logo img {
content: url('/data-structures/linked-list/polyline-night.svg');
}
</style>

## Override `__eq__`

```python linenums="1"
def __eq__(self, other: 'LinkedList'):
if self is other:
return True

cursor = self._sentinel.next
for e in other:
if e != cursor.value:
return False
cursor = cursor.next

return cursor is self._sentinel
```

## Unit tests

```python linenums="1"
def test_equality_empty():
assert LinkedList() == LinkedList()
assert LinkedList() is not LinkedList()


def test_equality_simple():
p = LinkedList()
p.append(1)
p.append(2)
p.append(3)

q = LinkedList()
q.append(1)
q.append(2)
q.append(3)
assert p == q
assert p is not q


def test_equality_partial():
p = LinkedList()
p.append(1)
p.append(2)
p.append(3)
p.append(4)

q = LinkedList()
q.append(1)
q.append(2)
q.append(3)

assert p != q

q.append(4)
assert p == q

q.append(5)
assert p != q

```
73 changes: 73 additions & 0 deletions docs/data-structures/linked-list/iterate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Iterate over linked list

<style>
.md-logo img {
content: url('/data-structures/linked-list/polyline-light.svg');
}

:root [data-md-color-scheme=slate] .md-logo img {
content: url('/data-structures/linked-list/polyline-night.svg');
}
</style>

## Get item

=== "Implementation"

```python linenums="1" title="list.py"
def __getitem__(self, index) -> Optional[int]:
i, cursor = 0, self._sentinel.next
while i <= index:
if cursor is self._sentinel:
return None
if i == index:
return cursor.value
i, cursor = i+1, cursor.next
```

=== "Unit tests"

```python linenums="1"
def test_get():
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.append(4)
ll.append(5)

assert ll[0] == 1
assert ll[1] == 2
assert ll[2] == 3
assert ll[3] == 4
assert ll[4] == 5
assert ll[5] is None
```

## Iterator

=== "Implementation"

```python linenums="1" title="list.py"
def __iter__(self):
cursor = self._sentinel.next
while cursor is not self._sentinel:
yield cursor.value
cursor = cursor.next
```

=== "Unit tests"

```python linenums="1"
def test_iteration():
ll = LinkedList()
ll.append(1)
ll.append(2)
ll.append(3)
ll.append(4)
ll.append(5)

for i, v in enumerate(ll):
assert v == ll[i]
```
167 changes: 167 additions & 0 deletions docs/data-structures/linked-list/merge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Merge linked lists

<style>
.md-logo img {
content: url('/data-structures/linked-list/polyline-light.svg');
}

:root [data-md-color-scheme=slate] .md-logo img {
content: url('/data-structures/linked-list/polyline-night.svg');
}
</style>

## In-place merging of sorted linked lists

```python linenums="1"
def sorted_ingest(self, other: 'LinkedList'):
if self is other:
return

new_sentinel = Node(0) # (1)!
cursor = new_sentinel

cursor_self = self._sentinel.next
cursor_other = other._sentinel.next

while cursor_self is not self._sentinel and cursor_other is not other._sentinel: # (2)!
if cursor_self.value <= cursor_other.value:
cursor.next = cursor_self
cursor_self = cursor_self.next
else:
cursor.next = cursor_other
cursor_other = cursor_other.next
cursor = cursor.next

if cursor_self is not self._sentinel:
cursor.next = cursor_self # (3)!
new_sentinel.prev = self._sentinel.prev # (4)!
elif cursor_other is not other._sentinel:
cursor.next = cursor_other
new_sentinel.prev = other._sentinel.prev
else:
new_sentinel.prev = cursor # (5)!

self._sentinel = new_sentinel # (6)!
other._sentinel.next = other._sentinel # (7)!

```

1. Set up head for merged list
2. Merge until either list exhausts.
3. No need to loop here, we can just link the remaining list.
4. We don't need to find the `tail` of the remaining list. It'll be the `prev` of original sentinel.
5. Doing the same work as line 22 and 25.
6. Stitch the two ends of new list.
7. Leave the ingested list empty.

## Unit tests

```python linenums="1"
def test_merge_empty():
a = LinkedList()
b = LinkedList()
a.sorted_ingest(b)

assert str(a) == "[]"
assert b.empty


def test_merge_empty_in_single():
a = LinkedList()
a.append(1)
b = LinkedList()
a.sorted_ingest(b)

assert str(a) == "[1]"
assert b.empty


def test_merge_single_in_empty():
a = LinkedList()
b = LinkedList()
b.append(1)
a.sorted_ingest(b)

assert str(a) == "[1]"
assert b.empty


def test_merge_is_append():
a = LinkedList()
a.append(1)
a.append(2)
a.append(3)
b = LinkedList()
b.append(4)
b.append(5)
b.append(6)
a.sorted_ingest(b)

assert str(a) == "[1, 2, 3, 4, 5, 6]"
assert b.empty


def test_merge_is_prepend():
a = LinkedList()
a.append(1)
a.append(2)
a.append(3)
b = LinkedList()
b.append(4)
b.append(5)
b.append(6)
b.sorted_ingest(a)

assert str(b) == "[1, 2, 3, 4, 5, 6]"
assert a.empty


def test_merge_is_interleave():
a = LinkedList()
a.append(1)
a.append(3)
a.append(5)
b = LinkedList()
b.append(2)
b.append(4)
b.append(6)
b.sorted_ingest(a)

assert str(b) == "[1, 2, 3, 4, 5, 6]"
assert a.empty


def test_merge_is_interleave_uneven():
a = LinkedList()
a.append(1)
a.append(3)
a.append(5)
a.append(10)
b = LinkedList()
b.append(2)
b.append(4)
b.append(6)
b.sorted_ingest(a)

assert str(b) == "[1, 2, 3, 4, 5, 6, 10]"
assert a.empty


def test_merge_random():
a = sorted([random.randint(0, 100) for x in range(0, 10)])
b = sorted([random.randint(0, 100) for x in range(0, 10)])
c = sorted(a + b)

p = LinkedList()
for e in a:
p.append(e)

q = LinkedList()
for e in b:
q.append(e)

p.sorted_ingest(q)

for i, e in enumerate(p):
assert e == c[i]
```
4 changes: 4 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ nav:
- Sentinel: 'data-structures/linked-list/sentinel.md'
- Linked list as Stack: 'data-structures/linked-list/stack-as-linked-list.md'
- Linked list as Queue: 'data-structures/linked-list/queue-as-linked-list.md'
- Iterate over list: 'data-structures/linked-list/iterate.md'
- Compare linked lists: 'data-structures/linked-list/equality.md'
- Clone linked lists: 'data-structures/linked-list/clone.md'
- Merge linked lists: 'data-structures/linked-list/merge.md'
- Heap: 'data-structures/heap/index.md'
- Circular list: 'data-structures/circular-list.md'
- Graph:
Expand Down
Loading

0 comments on commit 627c4e4

Please sign in to comment.