Skip to content

Commit e8c8982

Browse files
authored
allergies: add approaches (#1629)
Add approaches for the `allergies` exercise
1 parent 5b3b770 commit e8c8982

File tree

6 files changed

+381
-0
lines changed

6 files changed

+381
-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": "78997b92-fed7-484d-a6bf-aece9df9ab59",
9+
"slug": "vec-on-new",
10+
"title": "Create Vec on new",
11+
"blurb": "Create and store Vec on new.",
12+
"authors": ["bobahop"]
13+
},
14+
{
15+
"uuid": "019c4a1a-e690-480c-96a2-e1221d48baff",
16+
"slug": "vec-when-requested",
17+
"title": "Create Vec when requested",
18+
"blurb": "Store as u32 and create Vec when requested.",
19+
"authors": ["bobahop"]
20+
}
21+
]
22+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Introduction
2+
3+
There is more than one way to solve Allergies.
4+
One approach can be to create a [`Vec`][vec] of `Allergen` values when the `Allergies` struct is created.
5+
Another approach can be to store the `Allergen` values as a [`u32`][u32] and only output a `Vec` of `Allergen` values when requested.
6+
7+
## General guidance
8+
9+
Something to keep in mind is to leverage [bitwise][bitwise] operations to implement the logic.
10+
11+
12+
## Approach: Create `Vec` on `new()`
13+
14+
```rust
15+
pub struct Allergies {
16+
allergens: Vec<Allergen>,
17+
}
18+
19+
#[derive(Clone, Copy, Debug, PartialEq)]
20+
pub enum Allergen {
21+
Eggs = 1,
22+
Peanuts = 2,
23+
Shellfish = 4,
24+
Strawberries = 8,
25+
Tomatoes = 16,
26+
Chocolate = 32,
27+
Pollen = 64,
28+
Cats = 128,
29+
}
30+
31+
use self::Allergen::*;
32+
const ALLERGENS: [Allergen; 8] = [
33+
Eggs,
34+
Peanuts,
35+
Shellfish,
36+
Strawberries,
37+
Tomatoes,
38+
Chocolate,
39+
Pollen,
40+
Cats,
41+
];
42+
43+
impl Allergies {
44+
pub fn new(score: u32) -> Self {
45+
Self {
46+
allergens: Self::calculate_allergens(score),
47+
}
48+
}
49+
50+
pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
51+
self.allergens.iter().any(|allergy| allergy == allergen)
52+
}
53+
54+
pub fn allergies(&self) -> Vec<Allergen> {
55+
self.allergens.clone()
56+
}
57+
58+
fn calculate_allergens(score: u32) -> Vec<Allergen> {
59+
let mut allergens = Vec::<Allergen>::new();
60+
61+
for flag in 0..=7 {
62+
if score & 1 << flag != 0 {
63+
allergens.push(ALLERGENS[flag as usize]);
64+
}
65+
}
66+
allergens
67+
}
68+
}
69+
```
70+
71+
For more information, check the [create `Vec` on `new()` approach][approach-vec-on-new].
72+
73+
## Approach: Create `Vec` when requested
74+
75+
```rust
76+
#[derive(Debug, PartialEq, Copy, Clone)]
77+
pub enum Allergen {
78+
Eggs = 1 << 0,
79+
Peanuts = 1 << 1,
80+
Shellfish = 1 << 2,
81+
Strawberries = 1 << 3,
82+
Tomatoes = 1 << 4,
83+
Chocolate = 1 << 5,
84+
Pollen = 1 << 6,
85+
Cats = 1 << 7,
86+
}
87+
88+
use self::Allergen::*;
89+
const ALLERGENS: [Allergen; 8] = [
90+
Eggs,
91+
Peanuts,
92+
Shellfish,
93+
Strawberries,
94+
Tomatoes,
95+
Chocolate,
96+
Pollen,
97+
Cats,
98+
];
99+
pub struct Allergies {
100+
allergens: u32,
101+
}
102+
103+
impl Allergies {
104+
pub fn new(n: u32) -> Allergies {
105+
Allergies { allergens: n }
106+
}
107+
108+
pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
109+
self.allergens & *allergen as u32 != 0
110+
}
111+
112+
pub fn allergies(&self) -> Vec<Allergen> {
113+
ALLERGENS
114+
.iter()
115+
.filter(|a| self.is_allergic_to(a))
116+
.cloned()
117+
.collect()
118+
}
119+
}
120+
```
121+
122+
For more information, check the [create `Vec` when requested approach][approach-vec-when-requested].
123+
124+
## Which approach to use?
125+
126+
Since benchmarking is currently outside the scope of this document, which to use is pretty much a matter of personal preference.
127+
To create the `Vec` on `new()` may be considered wasteful if the `allergies()` method is never called.
128+
On the other hand, if the `allergies()` method is called more than once on the same struct, then it may be considered wasteful
129+
to create the `Vec` multiple times.
130+
131+
[vec]: https://doc.rust-lang.org/std/vec/struct.Vec.html
132+
[u32]: https://doc.rust-lang.org/std/primitive.u32.html
133+
[bitwise]: https://www.tutorialspoint.com/rust/rust_bitwise_operators.htm
134+
[approach-vec-on-new]: https://exercism.org/tracks/rust/exercises/allergies/approaches/vec-on-new
135+
[approach-vec-when-requested]: https://exercism.org/tracks/rust/exercises/allergies/approaches/vec-when-requested
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Create `Vec` on `new()`
2+
3+
```rust
4+
pub struct Allergies {
5+
allergens: Vec<Allergen>,
6+
}
7+
8+
#[derive(Clone, Copy, Debug, PartialEq)]
9+
pub enum Allergen {
10+
Eggs = 1,
11+
Peanuts = 2,
12+
Shellfish = 4,
13+
Strawberries = 8,
14+
Tomatoes = 16,
15+
Chocolate = 32,
16+
Pollen = 64,
17+
Cats = 128,
18+
}
19+
20+
use self::Allergen::*;
21+
const ALLERGENS: [Allergen; 8] = [
22+
Eggs,
23+
Peanuts,
24+
Shellfish,
25+
Strawberries,
26+
Tomatoes,
27+
Chocolate,
28+
Pollen,
29+
Cats,
30+
];
31+
32+
impl Allergies {
33+
pub fn new(score: u32) -> Self {
34+
Self {
35+
allergens: Self::calculate_allergens(score),
36+
}
37+
}
38+
39+
pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
40+
self.allergens.iter().any(|allergy| allergy == allergen)
41+
}
42+
43+
pub fn allergies(&self) -> Vec<Allergen> {
44+
self.allergens.clone()
45+
}
46+
47+
fn calculate_allergens(score: u32) -> Vec<Allergen> {
48+
let mut allergens = Vec::<Allergen>::new();
49+
50+
for flag in 0..=7 {
51+
if score & 1 << flag != 0 {
52+
allergens.push(ALLERGENS[flag as usize]);
53+
}
54+
}
55+
allergens
56+
}
57+
}
58+
```
59+
60+
This approach starts by defining the `Allergies` struct with an `allergens` field of `Vec<Allergens>` type.
61+
62+
Since the values of the `Allergen` enum are primitive numeric types, the [`Copy`][copy] trait is derived when defining the enum.
63+
[`Clone`][clone] is a supertrait of `Copy`, so everything which is `Copy` must also implement `Clone`, so `Clone` is derived as well.
64+
65+
The `use self::Allergen::*;` line is so that the `Allergen` values can be referred to directly in the file,
66+
instead of needing to prefix every value with `Allergen`, e.g. 'Allergen::Eggs`.
67+
68+
A fixed-size [array][array] is defined to hold the `Allergen` values in order, as enum values are not ordered to be acccessible by index.
69+
70+
The `[Allergen; 8]` is used to give the type and length of the array.
71+
To be a [`const`][const], the size of the array must be known at compile time, so setting the type and length must be done explicitly,
72+
so the size in bytes of the fixed array can be deduced by the compiler from that and not by inspecting the element types and counting
73+
the elements itself.
74+
75+
Next, the methods for the `Allergies` struct are implemented.
76+
77+
The `new()` method uses the `calculate_allergens()` method to set the `allergens` field for a new `Allergies` struct.
78+
79+
The `is_allergic_to()` method uses the [`any()`][any] method to see if the passed-in `Allergen` is contained in the `allergens`
80+
field, which is a `Vec` of `Allergen` values.
81+
82+
The `allergies()` method uses the [`clone()`][clone-method] method to return a copy of the `allergens` `Vec`.
83+
Since the `Allergen` values derive from `Copy`, they are inexpensively copied.
84+
85+
The `calculate_allergens()` method uses [`for` and `range`][for-and-range] to iterate through the indexes of the `ALLERGENS` array.
86+
The [bitwise AND operator][bitand] (`&`) is used to compare the score with `1` [shifted left][shl] (`<<`) for the value of the `flag`.
87+
88+
So, if `flag` is `0`, then `1` is shifted left `0` places, and the score is compared with bitwise `1`, which is also decimal `1`.
89+
If the comparison is not `0`, then the score contains the `Allergen`, and the `ALLERGEN` at the index of the `flag` value (`Eggs`) is pushed
90+
to the output `Vec`.
91+
92+
if `flag` is `7`, then `1` is shifted left `7` places, and the score is compared with bitwise `10000000`, which is decimal `128`.
93+
If the comparison is not `0`, then the score contains the `Allergen`, and the `ALLERGEN` at the index of the `flag` value (`Cats`) is pushed
94+
to the output `Vec`.
95+
96+
[copy]: https://doc.rust-lang.org/std/marker/trait.Copy.html
97+
[clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html
98+
[array]: https://doc.rust-lang.org/std/primitive.array.html
99+
[const]: https://doc.rust-lang.org/std/keyword.const.html
100+
[any]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any
101+
[clone-method]: https://doc.rust-lang.org/std/clone/trait.Clone.html#tymethod.clone
102+
[for-and-range]: https://doc.rust-lang.org/rust-by-example/flow_control/for.html
103+
[bitand]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html
104+
[shl]: https://doc.rust-lang.org/std/ops/trait.Shl.html
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn calculate_allergens(score: u32) -> Vec<Allergen> {
2+
let mut allergens = Vec::<Allergen>::new();
3+
for flag in 0..=7 {
4+
if score & 1 << flag != 0 {
5+
allergens.push(ALLERGENS[flag as usize]);
6+
}
7+
}
8+
allergens
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Create `Vec` when requested
2+
3+
```rust
4+
#[derive(Debug, PartialEq, Copy, Clone)]
5+
pub enum Allergen {
6+
Eggs = 1 << 0,
7+
Peanuts = 1 << 1,
8+
Shellfish = 1 << 2,
9+
Strawberries = 1 << 3,
10+
Tomatoes = 1 << 4,
11+
Chocolate = 1 << 5,
12+
Pollen = 1 << 6,
13+
Cats = 1 << 7,
14+
}
15+
16+
use self::Allergen::*;
17+
const ALLERGENS: [Allergen; 8] = [
18+
Eggs,
19+
Peanuts,
20+
Shellfish,
21+
Strawberries,
22+
Tomatoes,
23+
Chocolate,
24+
Pollen,
25+
Cats,
26+
];
27+
pub struct Allergies {
28+
allergens: u32,
29+
}
30+
31+
impl Allergies {
32+
pub fn new(n: u32) -> Allergies {
33+
Allergies { allergens: n }
34+
}
35+
36+
pub fn is_allergic_to(&self, allergen: &Allergen) -> bool {
37+
self.allergens & *allergen as u32 != 0
38+
}
39+
40+
pub fn allergies(&self) -> Vec<Allergen> {
41+
ALLERGENS
42+
.iter()
43+
.filter(|a| self.is_allergic_to(a))
44+
.cloned()
45+
.collect()
46+
}
47+
}
48+
```
49+
50+
This approach starts by defining the `Allergen` enum.
51+
Since the values of the `Allergen` enum are primitive numeric types, the [`Copy`][copy] trait is derived when defining the enum.
52+
[`Clone`][clone] is a supertrait of `Copy`, so everything which is `Copy` must also implement `Clone`, so `Clone` is derived as well.
53+
54+
The `use self::Allergen::*;` line is so that the `Allergen` values can be referred to directly in the file,
55+
instead of needing to prefix every value with `Allergen`, e.g. 'Allergen::Eggs`.
56+
57+
A fixed-size [array][array] is defined to hold the `Allergen` values as a way to iterate the enum values without requiring an external crate.
58+
59+
The `[Allergen; 8]` is used to give the type and length of the array.
60+
To be a [`const`][const], the size of the array must be known at compile time, so setting the type and length must be done explicitly,
61+
so the size in bytes of the fixed array can be deduced by the compiler from that and not by inspecting the element types and counting
62+
the elements itself.
63+
64+
The `Allergies` struct is defined with its `allergens` field given the type of [`u32`][u32].
65+
66+
Next, the methods for the `Allergies` struct are implemented.
67+
68+
The `new()` method sets its `allergens` field to the `u232` value passed in.
69+
70+
The `is_allergic_to()` method uses the [bitwise AND operator][bitand] (`&`) to compare the `Allergen` passed in with the `allergens` `u32` field.
71+
The dereferenced `Allergen` passed in is [cast][cast] to a `u32` for the purpose of comparison with the `allergens` `u32` value.
72+
The method returns if the comparison is not `0`.
73+
If the comparison is not `0`, then the `allergens` field contains the value of the `Allergen`, and `true` is returned.
74+
75+
For example, if the `allergens` field is decimal `3`, it is binary `11`.
76+
If the passed-in `Allergen` is `Peanuts`, which is the value `2` decimal, `10` binary, then `10` ANDed with `11` is `10`, not `0`,
77+
and so the method would return `true`.
78+
79+
If the passed-in `Allergen` is `Shellfish`, which is the value `4` decimal, `100` binary, then `100` ANDed with `011` is `0`,
80+
and so the method would return `false`.
81+
82+
The `allergies()` method iterates through the `ALLERGENS` array, passing each element of the array to the [`filter()`][filter] method.
83+
The [closure][closure] (also known as a lambda), returns the result of passing the `Allergen` to the `is_allergic_to()` method.
84+
The elements that survive the test of being present in the `allergens` field are passed to the [`cloned()`][cloned] method. which
85+
converts the iterator of references to `Allergen` values to an iterator of copies of the `Allergen` values.
86+
Since the `Allergen` values derive from `Copy`, they are inexpensively copied.
87+
The cloned elements are chained to the [`collect()`][collect] method, which uses [type inference][type-inference] to collect
88+
the elements into a `Vec<Allergen>` as defined in the method signature for the return value.
89+
90+
[copy]: https://doc.rust-lang.org/std/marker/trait.Copy.html
91+
[clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html
92+
[array]: https://doc.rust-lang.org/std/primitive.array.html
93+
[const]: https://doc.rust-lang.org/std/keyword.const.html
94+
[u32]: https://doc.rust-lang.org/std/primitive.u32.html
95+
[any]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.any
96+
[clone-method]: https://doc.rust-lang.org/std/clone/trait.Clone.html#tymethod.clone
97+
[for-and-range]: https://doc.rust-lang.org/rust-by-example/flow_control/for.html
98+
[bitand]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html
99+
[shl]: https://doc.rust-lang.org/std/ops/trait.Shl.html
100+
[cast]: https://doc.rust-lang.org/1.8.0/book/casting-between-types.html
101+
[filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter
102+
[closure]: https://doc.rust-lang.org/rust-by-example/fn/closures.html
103+
[cloned]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.cloned
104+
[collect]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect
105+
[type-inference]: https://doc.rust-lang.org/rust-by-example/types/inference.html
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub fn allergies(&self) -> Vec<Allergen> {
2+
ALLERGENS
3+
.iter()
4+
.filter(|a| self.is_allergic_to(a))
5+
.cloned()
6+
.collect()
7+
}

0 commit comments

Comments
 (0)