Skip to content

Commit

Permalink
exercises(linked-list): implement (#496)
Browse files Browse the repository at this point in the history
Refs: #490
  • Loading branch information
ee7 authored Mar 6, 2023
1 parent 41ab5be commit 155cf46
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 0 deletions.
14 changes: 14 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,20 @@
"integers"
]
},
{
"slug": "linked-list",
"name": "Linked List",
"uuid": "de2be158-c9af-4890-985b-43b0b7c8d8ff",
"practices": [],
"prerequisites": [],
"difficulty": 4,
"topics": [
"data_structures",
"generics",
"objects",
"pointers"
]
},
{
"slug": "react",
"name": "React",
Expand Down
26 changes: 26 additions & 0 deletions exercises/practice/linked-list/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Instructions

Implement a doubly linked list.

Like an array, a linked list is a simple linear data structure.
Several common data types can be implemented using linked lists, like queues, stacks, and associative arrays.

A linked list is a collection of data elements called *nodes*.
In a *singly linked list* each node holds a value and a link to the next node.
In a *doubly linked list* each node also holds a link to the previous node.

You will write an implementation of a doubly linked list.
Implement a Node to hold a value and pointers to the next and previous nodes.
Then implement a List which holds references to the first and last node and offers an array-like interface for adding and removing items:

- `push` (*insert value at back*);
- `pop` (*remove value at back*);
- `shift` (*remove value at front*).
- `unshift` (*insert value at front*);

To keep your implementation simple, the tests will not cover error conditions.
Specifically: `pop` or `shift` will never be called on an empty list.

Read more about [linked lists on Wikipedia][linked-lists].

[linked-lists]: https://en.wikipedia.org/wiki/Linked_list
18 changes: 18 additions & 0 deletions exercises/practice/linked-list/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"authors": [
"ee7"
],
"files": {
"solution": [
"linked_list.nim"
],
"test": [
"test_linked_list.nim"
],
"example": [
".meta/example.nim"
]
},
"blurb": "Implement a doubly linked list.",
"source": "Classic computer science topic"
}
71 changes: 71 additions & 0 deletions exercises/practice/linked-list/.meta/example.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
type
Node[T] = ref object ## A node of a doubly linked list.
prev: Node[T]
next: Node[T]
val: T

LinkedList*[T] = object ## A doubly linked list.
head: Node[T]
tail: Node[T]
count: int

func len*[T](list: LinkedList[T]): int =
## Returns the number of nodes in `list`.
list.count

proc push*[T](list: var LinkedList[T], val: T) =
## Appends a node with the given `val` to `list`.
let node = Node[T](prev: list.tail, next: nil, val: val)
if list.tail != nil:
list.tail.next = node
list.tail = node
if list.head == nil:
list.head = node
inc list.count

proc unshift*[T](list: var LinkedList[T], val: T) =
## Prepends a node with the given `val` to `list`.
let node = Node[T](prev: nil, next: list.head, val: val)
if list.head != nil:
list.head.prev = node
list.head = node
if list.tail == nil:
list.tail = node
inc list.count

proc pop*[T](list: var LinkedList[T]): T =
## Removes the final node of `list` and returns its value.
if list.tail != nil:
result = list.tail.val
list.tail = list.tail.prev
dec list.count

proc shift*[T](list: var LinkedList[T]): T =
## Removes the first node of `list` and returns its value.
if list.head != nil:
result = list.head.val
list.head = list.head.next
dec list.count

iterator nodes[T](list: LinkedList[T]): Node[T] =
## Yields every node of `list`. Supports removing a node during traversal.
var node = list.head
while node != nil:
let tmp = node.next
yield node
node = tmp

proc delete*[T](list: var LinkedList[T], val: T) =
## Removes a node with value `val` from `list`.
for node in list.nodes():
if node.val == val:
if node.prev == nil:
list.head = node.next
else:
node.prev.next = node.next
if node.next == nil:
list.tail = node.prev
else:
node.next.prev = node.prev
dec list.count
break
67 changes: 67 additions & 0 deletions exercises/practice/linked-list/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[7f7e3987-b954-41b8-8084-99beca08752c]
description = "pop gets element from the list"

[c3f67e5d-cfa2-4c3e-a18f-7ce999c3c885]
description = "push/pop respectively add/remove at the end of the list"

[00ea24ce-4f5c-4432-abb4-cc6e85462657]
description = "shift gets an element from the list"

[37962ee0-3324-4a29-b588-5a4c861e6564]
description = "shift gets first element from the list"

[30a3586b-e9dc-43fb-9a73-2770cec2c718]
description = "unshift adds element at start of the list"

[042f71e4-a8a7-4cf0-8953-7e4f3a21c42d]
description = "pop, push, shift, and unshift can be used in any order"

[88f65c0c-4532-4093-8295-2384fb2f37df]
description = "count an empty list"

[fc055689-5cbe-4cd9-b994-02e2abbb40a5]
description = "count a list with items"

[8272cef5-130d-40ea-b7f6-5ffd0790d650]
description = "count is correct after mutation"

[229b8f7a-bd8a-4798-b64f-0dc0bb356d95]
description = "popping to empty doesn't break the list"

[4e1948b4-514e-424b-a3cf-a1ebbfa2d1ad]
description = "shifting to empty doesn't break the list"

[e8f7c600-d597-4f79-949d-8ad8bae895a6]
description = "deletes the only element"

[fd65e422-51f3-45c0-9fd0-c33da638f89b]
description = "deletes the element with the specified value from the list"

[59db191a-b17f-4ab7-9c5c-60711ec1d013]
description = "deletes the element with the specified value from the list, re-assigns tail"

[58242222-5d39-415b-951d-8128247f8993]
description = "deletes the element with the specified value from the list, re-assigns head"

[ee3729ee-3405-4bd2-9bad-de0d4aa5d647]
description = "deletes the first of two elements"

[47e3b3b4-b82c-4c23-8c1a-ceb9b17cb9fb]
description = "deletes the second of two elements"

[7b420958-f285-4922-b8f9-10d9dcab5179]
description = "delete does not modify the list if the element is not found"

[7e04828f-6082-44e3-a059-201c63252a76]
description = "deletes only the first occurrence"
26 changes: 26 additions & 0 deletions exercises/practice/linked-list/linked_list.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
type
LinkedList*[T] = object ## A doubly linked list.

proc len*[T](list: LinkedList[T]): int =
## Returns the number of nodes in `list`.
discard

proc push*[T](list: var LinkedList[T], val: T) =
## Appends a node with the given `val` to `list`.
discard

proc pop*[T](list: var LinkedList[T]): T =
## Removes the final node of `list` and returns its value.
discard

proc shift*[T](list: var LinkedList[T]): T =
## Removes the first node of `list` and returns its value.
discard

proc unshift*[T](list: var LinkedList[T], val: T) =
## Prepends a node with the given `val` to `list`.
discard

proc delete*[T](list: var LinkedList[T], val: T) =
## Removes a node with value `val` from `list`.
discard
158 changes: 158 additions & 0 deletions exercises/practice/linked-list/test_linked_list.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import std/unittest
import linked_list

suite "Linked List":
test "pop gets element from the list":
var list = LinkedList[int]()
list.push(7)
check list.pop() == 7

test "push/pop respectively add/remove at the end of the list":
var list = LinkedList[int]()
list.push(11)
list.push(13)
check list.pop() == 13
check list.pop() == 11

test "shift gets an element from the list":
var list = LinkedList[int]()
list.push(17)
check list.shift() == 17

test "shift gets first element from the list":
var list = LinkedList[int]()
list.push(23)
list.push(5)
check list.shift() == 23
check list.shift() == 5

test "unshift adds element at start of the list":
var list = LinkedList[int]()
list.unshift(23)
list.unshift(5)
check list.shift() == 5
check list.shift() == 23

test "pop, push, shift, and unshift can be used in any order":
var list = LinkedList[int]()
list.push(1)
list.push(2)
check list.pop() == 2
list.push(3)
check list.shift() == 1
list.unshift(4)
list.push(5)
check list.shift() == 4
check list.pop() == 5
check list.shift() == 3

test "count an empty list":
var list = LinkedList[int]()
check list.len == 0

test "count a list with items":
var list = LinkedList[int]()
list.push(37)
list.push(1)
check list.len == 2

test "count is correct after mutation":
var list = LinkedList[int]()
list.push(31)
check list.len == 1
list.unshift(43)
check list.len == 2
discard list.shift()
check list.len == 1
discard list.pop()
check list.len == 0

test "popping to empty doesn't break the list":
var list = LinkedList[int]()
list.push(41)
list.push(59)
discard list.pop()
discard list.pop()
list.push(47)
check list.len == 1
check list.pop() == 47

test "shifting to empty doesn't break the list":
var list = LinkedList[int]()
list.push(41)
list.push(59)
discard list.shift()
discard list.shift()
list.push(47)
check list.len == 1
check list.shift() == 47

test "deletes the only element":
var list = LinkedList[int]()
list.push(61)
list.delete(61)
check list.len == 0

test "deletes the element with the specified value from the list":
var list = LinkedList[int]()
list.push(71)
list.push(83)
list.push(79)
list.delete(83)
check list.len == 2
check list.pop() == 79
check list.shift() == 71

test "deletes the element with the specified value from the list, re-assigns tail":
var list = LinkedList[int]()
list.push(71)
list.push(83)
list.push(79)
list.delete(83)
check list.len == 2
check list.pop() == 79
check list.pop() == 71

test "deletes the element with the specified value from the list, re-assigns head":
var list = LinkedList[int]()
list.push(71)
list.push(83)
list.push(79)
list.delete(83)
check list.len == 2
check list.shift() == 71
check list.shift() == 79

test "deletes the first of two elements":
var list = LinkedList[int]()
list.push(97)
list.push(101)
list.delete(97)
check list.len == 1
check list.pop() == 101

test "deletes the second of two elements":
var list = LinkedList[int]()
list.push(97)
list.push(101)
list.delete(101)
check list.len == 1
check list.pop() == 97

test "delete does not modify the list if the element is not found":
var list = LinkedList[int]()
list.push(89)
list.delete(103)
check list.len == 1

test "deletes only the first occurrence":
var list = LinkedList[int]()
list.push(73)
list.push(9)
list.push(9)
list.push(107)
list.delete(9)
check list.len == 3
check list.pop() == 107
check list.pop() == 9
check list.pop() == 73

0 comments on commit 155cf46

Please sign in to comment.