Skip to content

Commit 58bc309

Browse files
ellnixsenekor
andauthored
list-ops: add exercise (#2023)
Co-Authored-By: Remo Senekowitsch <[email protected]>
1 parent 9105fe1 commit 58bc309

File tree

12 files changed

+782
-0
lines changed

12 files changed

+782
-0
lines changed

config.json

+13
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,19 @@
11941194
"traits"
11951195
]
11961196
},
1197+
{
1198+
"slug": "list-ops",
1199+
"name": "List Ops",
1200+
"uuid": "4b411941-d272-4b95-8c40-5816fecf27a2",
1201+
"practices": [],
1202+
"prerequisites": [],
1203+
"difficulty": 4,
1204+
"topics": [
1205+
"generics",
1206+
"iterators",
1207+
"traits"
1208+
]
1209+
},
11971210
{
11981211
"slug": "phone-number",
11991212
"name": "Phone Number",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Instructions append
2+
3+
## Implementing your own list operations
4+
5+
Rust has a rich iterator system, using them to solve this exercise is trivial.
6+
Please attempt to solve the exercise without reaching for iterator methods like
7+
`map()`, `flatten()`, `filter()`, `count()`, `fold()`, `chain()`, `rev()` and similar.
8+
The `next()` and `next_back()` methods do not count.
9+
10+
## Avoiding allocations
11+
12+
The exercise is solvable without introducing any additional memory allocations in the solution.
13+
See if you can create your own iterator type instead of creating an iterator from a collection.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Instructions
2+
3+
Implement basic list operations.
4+
5+
In functional languages list operations like `length`, `map`, and `reduce` are very common.
6+
Implement a series of basic list operations, without using existing functions.
7+
8+
The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include:
9+
10+
- `append` (_given two lists, add all items in the second list to the end of the first list_);
11+
- `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_);
12+
- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_);
13+
- `length` (_given a list, return the total number of items within it_);
14+
- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_);
15+
- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_);
16+
- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_);
17+
- `reverse` (_given a list, return a list with all the original items, but in reversed order_).
18+
19+
Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant.
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
/target/
4+
**/*.rs.bk
5+
6+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7+
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
8+
Cargo.lock
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"authors": ["ellnix"],
3+
"files": {
4+
"solution": [
5+
"src/lib.rs",
6+
"Cargo.toml"
7+
],
8+
"test": [
9+
"tests/list-ops.rs"
10+
],
11+
"example": [
12+
".meta/example.rs"
13+
]
14+
},
15+
"blurb": "Implement basic list operations."
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
pub fn append<T, I>(a: I, b: I) -> impl Iterator<Item = T>
2+
where
3+
I: Iterator<Item = T>,
4+
{
5+
struct Append<T, I: Iterator<Item = T>> {
6+
a: I,
7+
b: I,
8+
}
9+
10+
impl<T, I: Iterator<Item = T>> Iterator for Append<T, I> {
11+
type Item = T;
12+
13+
fn next(&mut self) -> Option<Self::Item> {
14+
self.a.next().or_else(|| self.b.next())
15+
}
16+
}
17+
18+
Append { a, b }
19+
}
20+
21+
pub fn concat<I, NI, T>(list: I) -> impl Iterator<Item = T>
22+
where
23+
NI: Iterator<Item = T>,
24+
I: Iterator<Item = NI>,
25+
{
26+
struct Concat<I: Iterator<Item = NI>, NI: Iterator<Item = T>, T> {
27+
nested_list: I,
28+
cur: Option<NI>,
29+
}
30+
31+
impl<I: Iterator<Item = NI>, NI: Iterator<Item = T>, T> Iterator for Concat<I, NI, T> {
32+
type Item = T;
33+
34+
fn next(&mut self) -> Option<Self::Item> {
35+
if let Some(nested_iterator) = self.cur.as_mut() {
36+
if let Some(val) = nested_iterator.next() {
37+
return Some(val);
38+
}
39+
}
40+
41+
if let Some(next_nested) = self.nested_list.next() {
42+
self.cur = Some(next_nested);
43+
self.next()
44+
} else {
45+
None
46+
}
47+
}
48+
}
49+
50+
Concat {
51+
nested_list: list,
52+
cur: None,
53+
}
54+
}
55+
56+
pub fn filter<I, T, F>(list: I, predicate: F) -> impl Iterator<Item = T>
57+
where
58+
I: Iterator<Item = T>,
59+
F: Fn(&T) -> bool,
60+
{
61+
struct Filter<I: Iterator<Item = T>, T, F: Fn(&T) -> bool> {
62+
list: I,
63+
predicate: F,
64+
}
65+
66+
impl<I, T, F> Iterator for Filter<I, T, F>
67+
where
68+
I: Iterator<Item = T>,
69+
F: Fn(&T) -> bool,
70+
{
71+
type Item = T;
72+
73+
fn next(&mut self) -> Option<Self::Item> {
74+
self.list.find(|val| (self.predicate)(val))
75+
}
76+
}
77+
78+
Filter { list, predicate }
79+
}
80+
81+
pub fn length<I: Iterator<Item = T>, T>(list: I) -> usize {
82+
let mut len = 0;
83+
84+
for _ in list {
85+
len += 1;
86+
}
87+
88+
len
89+
}
90+
91+
pub fn map<I, F, T, U>(list: I, function: F) -> impl Iterator<Item = U>
92+
where
93+
I: Iterator<Item = T>,
94+
F: Fn(T) -> U,
95+
{
96+
struct Map<I: Iterator<Item = T>, F: Fn(T) -> U, T, U> {
97+
list: I,
98+
function: F,
99+
}
100+
101+
impl<I: Iterator<Item = T>, F: Fn(T) -> U, T, U> Iterator for Map<I, F, T, U> {
102+
type Item = U;
103+
104+
fn next(&mut self) -> Option<Self::Item> {
105+
self.list.next().map(&self.function)
106+
}
107+
}
108+
109+
Map { list, function }
110+
}
111+
112+
pub fn foldl<I, F, T, U>(list: I, initial: U, function: F) -> U
113+
where
114+
I: Iterator<Item = T>,
115+
F: Fn(U, T) -> U,
116+
{
117+
let mut result = initial;
118+
119+
for item in list {
120+
result = (function)(result, item)
121+
}
122+
123+
result
124+
}
125+
126+
pub fn foldr<I, F, T, U>(mut list: I, initial: U, function: F) -> U
127+
where
128+
I: DoubleEndedIterator<Item = T>,
129+
F: Fn(U, T) -> U,
130+
{
131+
let mut result = initial;
132+
133+
while let Some(item) = list.next_back() {
134+
result = (function)(result, item)
135+
}
136+
137+
result
138+
}
139+
140+
pub fn reverse<I: DoubleEndedIterator<Item = T>, T>(list: I) -> impl Iterator<Item = T> {
141+
struct Reverse<I: DoubleEndedIterator<Item = T>, T> {
142+
list: I,
143+
}
144+
145+
impl<I: DoubleEndedIterator<Item = T>, T> Iterator for Reverse<I, T> {
146+
type Item = T;
147+
148+
fn next(&mut self) -> Option<Self::Item> {
149+
self.list.next_back()
150+
}
151+
}
152+
153+
Reverse { list }
154+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use list_ops::*;
2+
3+
{% for group in cases %}
4+
5+
{% set first_exercise = group.cases | first %}
6+
{% if first_exercise.property == "concat" %}
7+
#[allow(clippy::zero_repeat_side_effects)]
8+
{%- endif %}
9+
mod {{ first_exercise.property | make_ident }} {
10+
use super::*;
11+
12+
{% for test in group.cases %}
13+
14+
#[test]
15+
#[ignore]
16+
fn {{ test.description | make_ident }}() {
17+
{% filter replace(from="[", to="vec![") %}
18+
19+
{%- for arg, value in test.input -%}
20+
{% if arg == "function" %}{% continue %}{% endif %}
21+
{%- set is_empty = value is iterable and value | length == 0 -%}
22+
23+
let {{arg}} =
24+
{% if is_empty %}
25+
{% if test.property is starting_with("fold") %}
26+
[0.0f64; 0]
27+
{% elif test.property == "concat" %}
28+
[[0i32; 0]; 0]
29+
{% else %}
30+
[0i32; 0]
31+
{% endif %}
32+
{% elif test.property is starting_with("fold") %}
33+
{% if value is number %}
34+
{{ test.input[arg] }}.0
35+
{% else %}
36+
{{ test.input[arg] | as_str | replace(from=",", to=".0,") | replace(from="]", to=".0]") | replace(from="[.0]", to="[]") }}
37+
{% endif %}
38+
{% else %}
39+
{{ test.input[arg] }}
40+
{% endif -%}
41+
{% if value is iterable %}
42+
.into_iter()
43+
{% set first_item = value | first %}
44+
{% if test.property == "concat" or first_item is iterable %}
45+
.map(Vec::into_iter)
46+
{% endif %}
47+
{% endif %};
48+
{%- endfor -%}
49+
50+
let output = {{ test.property }}(
51+
{% for arg, value in test.input %}
52+
{% if arg == "function" %}
53+
{{ value | replace(from="(", to="|") | replace(from=")", to="|") | replace(from=" ->", to="") | replace(from="modulo", to="%")}},
54+
{% else %}
55+
{{arg}},
56+
{% endif %}
57+
{% endfor %}
58+
);
59+
60+
let expected = {{ test.expected }}{% if test.property is starting_with("fold") %}.0{% endif %};
61+
62+
assert_eq!(
63+
output
64+
{% if test.expected is iterable %}
65+
{% set first_item = test.expected | first %}
66+
{% if test.property != "concat" and first_item is iterable %}
67+
.map(|subiter| subiter.collect::<Vec<_>>())
68+
{% endif %}
69+
.collect::<Vec<_>>()
70+
{% endif %},
71+
expected
72+
);
73+
74+
{% endfilter %}
75+
}
76+
{% endfor -%}
77+
}
78+
{% endfor -%}

0 commit comments

Comments
 (0)