Skip to content

Commit 5b3b770

Browse files
authored
simple-linked-list: add approaches (#1628)
Add approaches for the `simple-linked-list` exercise.
1 parent bfe46a5 commit 5b3b770

File tree

6 files changed

+459
-0
lines changed

6 files changed

+459
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"introduction": {
3+
"authors": ["bobahop"],
4+
"contributors": []
5+
},
6+
"approaches": [
7+
{
8+
"uuid": "648219f8-b4a6-4131-860c-c93ea80042b1",
9+
"slug": "keep-track-of-length",
10+
"title": "Keep track of length",
11+
"blurb": "Keep track of length as you go.",
12+
"authors": ["bobahop"]
13+
},
14+
{
15+
"uuid": "aba9f9ae-f7a5-4620-9a28-6a2bb0b240a9",
16+
"slug": "do-not-keep-track-of-length",
17+
"title": "Do not keep track of length",
18+
"blurb": "Do not keep track of length as you go.",
19+
"authors": ["bobahop"]
20+
}
21+
]
22+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Do not keep track of length
2+
3+
```rust
4+
use std::iter::FromIterator;
5+
6+
type Link<T> = Option<Box<Node<T>>>;
7+
8+
pub struct SimpleLinkedList<T> {
9+
head: Link<T>,
10+
}
11+
struct Node<T> {
12+
data: T,
13+
next: Link<T>,
14+
}
15+
16+
impl<T> Node<T> {
17+
fn new(data: T, next: Option<Box<Node<T>>>) -> Self {
18+
Self { data, next }
19+
}
20+
}
21+
22+
impl<T> SimpleLinkedList<T> {
23+
pub fn new() -> Self {
24+
Self { head: None }
25+
}
26+
27+
pub fn is_empty(&self) -> bool {
28+
self.head.is_none()
29+
}
30+
31+
pub fn len(&self) -> usize {
32+
let mut current_node = &self.head;
33+
let mut size = 0;
34+
while let Some(x) = current_node {
35+
size += 1;
36+
current_node = &x.next;
37+
}
38+
size
39+
}
40+
41+
pub fn push(&mut self, element: T) {
42+
let node = Box::new(Node::new(element, self.head.take()));
43+
self.head = Some(node);
44+
}
45+
46+
pub fn pop(&mut self) -> Option<T> {
47+
if self.head.is_some() {
48+
let head_node = self.head.take().unwrap();
49+
self.head = head_node.next;
50+
Some(head_node.data)
51+
} else {
52+
None
53+
}
54+
}
55+
56+
pub fn peek(&self) -> Option<&T> {
57+
self.head.as_ref().map(|head| &(head.data))
58+
}
59+
60+
pub fn rev(self) -> SimpleLinkedList<T> {
61+
let mut list = SimpleLinkedList::new();
62+
let mut cur_node = self.head;
63+
while let Some(node) = cur_node {
64+
list.push(node.data);
65+
cur_node = node.next;
66+
}
67+
list
68+
}
69+
}
70+
71+
impl<T> FromIterator<T> for SimpleLinkedList<T> {
72+
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
73+
let mut list = SimpleLinkedList::new();
74+
for item in iter {
75+
list.push(item);
76+
}
77+
list
78+
}
79+
}
80+
81+
impl<T> Into<Vec<T>> for SimpleLinkedList<T> {
82+
fn into(self) -> Vec<T> {
83+
let mut the_vec: Vec<T> = vec![];
84+
let mut cur_node = self.rev().head;
85+
while let Some(node) = cur_node {
86+
the_vec.push(node.data);
87+
cur_node = node.next;
88+
}
89+
the_vec
90+
}
91+
}
92+
```
93+
94+
This approach starts by defining a `Link` type which will be used for the `head` field of `SimpleLinkedList` and the `next` field of `Node`.
95+
Defining `Link<T>` as an `Option<Box<Node<T>>>` in one place helps to keep the code [DRY][dry].
96+
97+
The `is_empty()` method is implemented by returning the result of the [is_none()][is-none] method on the `head`.
98+
99+
The `len()` method is implemented by iterating all of the nodes while mutating a counter variable.
100+
When the iteration is done, the value of the counter variable is returned.
101+
102+
[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
103+
[is-none]: https://doc.rust-lang.org/std/option/enum.Option.html#method.is_none
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pub fn len(&self) -> usize {
2+
let mut current_node = &self.head;
3+
let mut size = 0;
4+
while let Some(x) = current_node {
5+
size += 1;
6+
current_node = &x.next;
7+
}
8+
size
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# Introduction
2+
3+
There is more than one way to solve Simple Linked List.
4+
One general approach is to keep track of the length as nodes are pushed and popped.
5+
Another approach is to calculate the length every time it is asked for.
6+
7+
## General guidance
8+
9+
One thing to keep in mind is to not mutate the list when it is not necessary.
10+
For instance, if you find yourself using `mut self` for `rev()` or `into()`, that is an indication that the list is being mutated when it is not necessary.
11+
12+
A well-known treatment of writing linked lists in Rust is [`Learn Rust With Entirely Too Many Linked Lists`][too-many-lists].
13+
14+
## Approach: Keep track of length
15+
16+
```rust
17+
use std::iter::FromIterator;
18+
19+
type Link<T> = Option<Box<Node<T>>>;
20+
21+
pub struct SimpleLinkedList<T> {
22+
head: Link<T>,
23+
len: usize,
24+
}
25+
struct Node<T> {
26+
data: T,
27+
next: Link<T>,
28+
}
29+
30+
impl<T> SimpleLinkedList<T> {
31+
pub fn new() -> Self {
32+
Self { head: None, len: 0 }
33+
}
34+
35+
pub fn is_empty(&self) -> bool {
36+
self.len == 0
37+
}
38+
39+
pub fn len(&self) -> usize {
40+
self.len
41+
}
42+
43+
pub fn push(&mut self, _element: T) {
44+
let new_node = Box::new(Node {
45+
data: _element,
46+
next: self.head.take(),
47+
});
48+
49+
self.head = Some(new_node);
50+
self.len += 1;
51+
}
52+
53+
pub fn pop(&mut self) -> Option<T> {
54+
if self.len == 0 {
55+
return None;
56+
}
57+
self.len -= 1;
58+
self.head.take().map(|node| {
59+
self.head = node.next;
60+
node.data
61+
})
62+
}
63+
64+
pub fn peek(&self) -> Option<&T> {
65+
self.head.as_ref().map(|node| &node.data)
66+
}
67+
68+
pub fn rev(self) -> SimpleLinkedList<T> {
69+
let mut list = Self::new();
70+
if self.len == 0 {
71+
return list;
72+
}
73+
let mut cur_node = self.head;
74+
while let Some(node) = cur_node {
75+
list.push(node.data);
76+
cur_node = node.next;
77+
}
78+
list
79+
}
80+
}
81+
82+
impl<T> FromIterator<T> for SimpleLinkedList<T> {
83+
fn from_iter<I: IntoIterator<Item = T>>(_iter: I) -> Self {
84+
let mut list = Self::new();
85+
for val in _iter {
86+
list.push(val);
87+
}
88+
list
89+
}
90+
}
91+
92+
impl<T> Into<Vec<T>> for SimpleLinkedList<T> {
93+
fn into(self) -> Vec<T> {
94+
let mut the_vec: Vec<T> = vec![];
95+
if self.len == 0 {
96+
return the_vec;
97+
}
98+
let mut cur_node = self.rev().head;
99+
while let Some(node) = cur_node {
100+
the_vec.push(node.data);
101+
cur_node = node.next;
102+
}
103+
the_vec
104+
}
105+
}
106+
```
107+
108+
For more information, check the [keep track of length appproach][approach-keep-track-of-length].
109+
110+
## Approach: Do not keep track of length
111+
112+
```rust
113+
use std::iter::FromIterator;
114+
115+
type Link<T> = Option<Box<Node<T>>>;
116+
117+
pub struct SimpleLinkedList<T> {
118+
head: Link<T>,
119+
}
120+
struct Node<T> {
121+
data: T,
122+
next: Link<T>,
123+
}
124+
125+
impl<T> Node<T> {
126+
fn new(data: T, next: Option<Box<Node<T>>>) -> Self {
127+
Self { data, next }
128+
}
129+
}
130+
131+
impl<T> SimpleLinkedList<T> {
132+
pub fn new() -> Self {
133+
Self { head: None }
134+
}
135+
136+
pub fn is_empty(&self) -> bool {
137+
self.head.is_none()
138+
}
139+
140+
pub fn len(&self) -> usize {
141+
let mut current_node = &self.head;
142+
let mut size = 0;
143+
while let Some(x) = current_node {
144+
size += 1;
145+
current_node = &x.next;
146+
}
147+
size
148+
}
149+
150+
pub fn push(&mut self, element: T) {
151+
let node = Box::new(Node::new(element, self.head.take()));
152+
self.head = Some(node);
153+
}
154+
155+
pub fn pop(&mut self) -> Option<T> {
156+
if self.head.is_some() {
157+
let head_node = self.head.take().unwrap();
158+
self.head = head_node.next;
159+
Some(head_node.data)
160+
} else {
161+
None
162+
}
163+
}
164+
165+
pub fn peek(&self) -> Option<&T> {
166+
self.head.as_ref().map(|head| &(head.data))
167+
}
168+
169+
pub fn rev(self) -> SimpleLinkedList<T> {
170+
let mut list = SimpleLinkedList::new();
171+
let mut cur_node = self.head;
172+
while let Some(node) = cur_node {
173+
list.push(node.data);
174+
cur_node = node.next;
175+
}
176+
list
177+
}
178+
}
179+
180+
impl<T> FromIterator<T> for SimpleLinkedList<T> {
181+
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
182+
let mut list = SimpleLinkedList::new();
183+
for item in iter {
184+
list.push(item);
185+
}
186+
list
187+
}
188+
}
189+
190+
impl<T> Into<Vec<T>> for SimpleLinkedList<T> {
191+
fn into(self) -> Vec<T> {
192+
let mut the_vec: Vec<T> = vec![];
193+
let mut cur_node = self.rev().head;
194+
while let Some(node) = cur_node {
195+
the_vec.push(node.data);
196+
cur_node = node.next;
197+
}
198+
the_vec
199+
}
200+
}
201+
```
202+
203+
For more information, check the [do not keep track of length appproach][approach-do-not-keep-track-of-length].
204+
205+
## Which approach to use?
206+
207+
Since benchmarking is currently outside the scope of this document, which to use is pretty much a matter of personal preference.
208+
To keep track of the length as you go may seem wasteful if the length is never requested.
209+
On the other hand, if the the length is requested more than once on an unchanged list, it may seem wasteful to calculate the same length
210+
multiple times.
211+
212+
[too-many-lists]: https://rust-unofficial.github.io/too-many-lists/
213+
[approach-keep-track-of-length]: https://exercism.org/tracks/rust/exercises/simple-linked-list/approaches/keep-track-of-length
214+
[approach-do-not-keep-track-of-length]: https://exercism.org/tracks/rust/exercises/simple-linked-list/approaches/do-not-keep-track-of-length

0 commit comments

Comments
 (0)