Skip to content

Commit b71ba70

Browse files
pangram: add approaches (#1584)
* Create snippet.md * Create main.rs * Create content.md * Create config.json * Create config.json * Create snippet.txt * Update content.md * Create content.md * Create snippet.txt * Create content.md * Update snippet.txt * Update content.md * Create introduction.md * Create snippet.txt * Create content.md * Create snippet.txt * Create content.md * Update content.md * Update content.md * Update introduction.md * Update introduction.md * Update content.md * Update content.md * Update content.md * Update main.rs * Update content.md * Update content.md * Update snippet.txt * Update introduction.md * Update content.md * Update content.md * Update content.md * Update introduction.md * Update exercises/practice/pangram/.approaches/bitfield/snippet.txt Co-authored-by: Erik Schierboom <[email protected]> Co-authored-by: Erik Schierboom <[email protected]>
1 parent ea4a8b9 commit b71ba70

File tree

14 files changed

+419
-0
lines changed

14 files changed

+419
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `all` with `contains` on lowercased letters
2+
3+
```rust
4+
pub fn is_pangram(sentence: &str) -> bool {
5+
let sentence_lowered = sentence.to_lowercase();
6+
('a'..='z').all(|ltr| sentence_lowered.contains(ltr))
7+
}
8+
```
9+
10+
- This begins by lowercasing the input by using [to_lowercase][tolower].
11+
- It then checks if all letters in the alphabet are contained in the `sentence`,
12+
using the [`Iterator`][iterator] method [`all`][all] with the [`str`][str] method [`contains`][contains].
13+
If all of the letters in the alphabet are contained in the `sentence`, then the function will return `true`.
14+
15+
[tolower]: https://doc.rust-lang.org/std/string/struct.String.html#method.to_lowercase
16+
[iterator]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
17+
[all]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.all
18+
[str]: https://doc.rust-lang.org/std/primitive.str.html
19+
[contains]: https://doc.rust-lang.org/std/primitive.str.html#method.contains
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub fn is_pangram_all_contains(sentence: &str) -> bool {
2+
let sentence_lowered = sentence.to_lowercase();
3+
('a'..='z').all(|ltr| sentence_lowered.contains(ltr))
4+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Bit field
2+
3+
```rust
4+
const A_LCASE: u8 = 97;
5+
const A_UCASE: u8 = 65;
6+
const ALL_26_BITS_SET: u32 = 67108863;
7+
8+
pub fn is_pangram(sentence: &str) -> bool {
9+
let mut letter_flags = 0;
10+
11+
for letter in sentence.chars() {
12+
if letter >= 'a' && letter <= 'z' {
13+
letter_flags |= 1 << (letter as u8 - A_LCASE);
14+
} else if letter >= 'A' && letter <= 'Z' {
15+
letter_flags |= 1 << (letter as u8 - A_UCASE);
16+
}
17+
}
18+
letter_flags == ALL_26_BITS_SET
19+
}
20+
```
21+
22+
This solution uses the [ASCII][ascii] value of the letter to set the corresponding bit position.
23+
First, some [`const`][const] values are set.
24+
These values will be used for readability in the body of the `is_pangram` function.
25+
The ASCII value for `a` is `97`.
26+
The ASCII value for `A` is `65`.
27+
The value for all of the rightmost `26` bits being set in a [`u32`][u32] is `67108863`.
28+
29+
- The [`for` loop][for-loop] loops through the [chars][chars] of `sentence`.
30+
We don't iterate by bytes because, as of this writing, some tests may include multi-byte characters in `sentence`.
31+
- Each letter is tested for being `a` through `z` or `A` through `Z`.
32+
- If the lower-cased letter is subtracted by `a`, then `a` will result in `0`, because `97` minus `97` equals `0`.
33+
`z` would result in `25`, because `122` minus `97` equals `25`.
34+
So `a` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `z` would have `1` shifted left 25 places.
35+
- If the upper-cased letter is subtracted by `A`, then `A` will result in `0`, because `65` minus `65` equals `0`.
36+
`Z` would result in `25`, because `90` minus `65` equals `25`.
37+
So `A` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `Z` would have `1` shifted left 25 places.
38+
39+
In that way, both a lower-cased `z` and an upper-cased `Z` can share the same position in the bit field.
40+
41+
So, for an unsigned thirty-two bit integer, if the values for `a` and `Z` were both set, the bits would look like
42+
43+
```
44+
zyxwvutsrqponmlkjihgfedcba
45+
00000010000000000000000000000001
46+
```
47+
48+
We can use the [bitwise OR operator][or] to set the bit.
49+
After the loop completes, the function returns `true` if the `letter_flags` value is the same value as when all of the rightmost `26` bits are set,
50+
which is `67108863`.
51+
52+
This approach is relatively very fast compared with other approaches.
53+
54+
[ascii]: https://www.asciitable.com/
55+
[const]: https://doc.rust-lang.org/std/keyword.const.html
56+
[u32]: https://doc.rust-lang.org/std/primitive.u32.html
57+
[for-loop]: https://doc.rust-lang.org/reference/expressions/loop-expr.html#iterator-loops
58+
[chars]: https://doc.rust-lang.org/std/primitive.str.html#method.chars
59+
[shift-left]: https://doc.rust-lang.org/std/ops/trait.Shl.html
60+
[or]: https://doc.rust-lang.org/std/ops/trait.BitOr.html
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
for letter in sentence.chars() {
2+
if letter >= 'a' && letter <= 'z' {
3+
letter_flags |= 1 << (letter as u8 - A_LCASE);
4+
} else if letter >= 'A' && letter <= 'Z' {
5+
letter_flags |= 1 << (letter as u8 - A_UCASE);
6+
}
7+
}
8+
letter_flags == ALL_26_BITS_SET
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"introduction": {
3+
"authors": ["bobahop"]
4+
},
5+
"approaches": [
6+
{
7+
"uuid": "e2afccc3-5503-47a3-ae59-fd3f7ff48935",
8+
"slug": "all-contains",
9+
"title": "all with contains on lower case",
10+
"blurb": "Use all with contains on lowercased letters.",
11+
"authors": ["bobahop"]
12+
},
13+
{
14+
"uuid": "eb75431e-8295-40fb-8239-d207b45d4e8b",
15+
"slug": "hashset-is-subset",
16+
"title": "HashSet with is_subset",
17+
"blurb": "Use HashSet with is_subset.",
18+
"authors": ["bobahop"]
19+
},
20+
{
21+
"uuid": "159732d1-3a19-4e77-914d-a71e7e96439d",
22+
"slug": "hashset-len",
23+
"title": "HashSet with len",
24+
"blurb": "Use HashSet with len.",
25+
"authors": ["bobahop"]
26+
},
27+
{
28+
"uuid": "7907873a-10b8-4f9e-8c7d-52aa5d460392",
29+
"slug": "bitfield",
30+
"title": "Bit field",
31+
"blurb": "Use a bit field to keep track of used letters.",
32+
"authors": ["bobahop"]
33+
}
34+
]
35+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# `HashSet` with `is_subset`
2+
3+
```rust
4+
use std::collections::HashSet;
5+
6+
pub fn is_pangram(sentence: &str) -> bool {
7+
let all: HashSet<char> = HashSet::from_iter("abcdefghijklmnopqrstuvwxyz".chars());
8+
let used: HashSet<char> = HashSet::from_iter(sentence.to_lowercase().chars());
9+
all.is_subset(&used)
10+
}
11+
```
12+
13+
In this approach a [HashSet][hashset] is made of the lowercase alphabet [chars][chars] using the [from_iter][from-iter] method,
14+
and another `HashSet` is made from the [to_lowercase][to-lowercase] `sentence` `chars`.
15+
16+
The function returns if the alphabet `HashSet` [is_subset][is-subset] of the `sentence` `HashSet`.
17+
If all of the letters in the alphabet are a subset of the letters in the `sentence`,
18+
then `is_subset` will return `true`.
19+
20+
[hashset]: https://doc.rust-lang.org/std/collections/struct.HashSet.html
21+
[chars]: https://doc.rust-lang.org/std/primitive.str.html#method.chars
22+
[from-iter]: https://doc.rust-lang.org/std/iter/trait.FromIterator.html#tymethod.from_iter
23+
[to-lowercase]: https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase
24+
[is-subset]: https://doc.rust-lang.org/std/collections/hash_set/struct.HashSet.html#method.is_subset
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use std::collections::HashSet;
2+
3+
pub fn is_pangram(sentence: &str) -> bool {
4+
let all: HashSet<char> = HashSet::from_iter("abcdefghijklmnopqrstuvwxyz".chars());
5+
let used: HashSet<char> = HashSet::from_iter(sentence.to_lowercase().chars());
6+
all.is_subset(&used)
7+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# `HashSet` with `len`
2+
3+
```rust
4+
use std::collections::HashSet;
5+
6+
pub fn is_pangram(sentence: &str) -> bool {
7+
sentence
8+
.to_lowercase()
9+
.chars()
10+
.filter(|c| c.is_ascii_alphabetic())
11+
.collect::<HashSet<char>>()
12+
.len()
13+
== 26
14+
}
15+
```
16+
17+
This approach chains several functions together to determine the result.
18+
19+
- It first passes the `sentence` [to_lowercase][to-lowercase].
20+
- The lowercased `sentence` is then iterated by [chars][chars].
21+
- The `chars` are [filter][filter]ed in its [closure][closure] so that only a character that [is_ascii_alphabetic][is-ascii-alphabetic]
22+
makes it through to be [collect][collect]ed into a [HashSet][hashset].
23+
- The function returns if the [len][len] of the `HashSet` is `26`.
24+
If the number of unique letters in the `HashSet` is equal to the `26` letters in the alphabet, then the function will return `true`.
25+
26+
[to-lowercase]: https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase
27+
[chars]: https://doc.rust-lang.org/std/primitive.str.html#method.chars
28+
[filter]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter
29+
[closure]: https://doc.rust-lang.org/rust-by-example/fn/closures.html
30+
[is-ascii-alphabetic]: https://doc.rust-lang.org/std/primitive.u8.html#method.is_ascii_alphabetic
31+
[collect]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect
32+
[hashset]: https://doc.rust-lang.org/std/collections/struct.HashSet.html
33+
[len]: https://doc.rust-lang.org/std/collections/struct.HashSet.html#method.len
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
sentence
2+
.to_lowercase()
3+
.chars()
4+
.filter(|c| c.is_ascii_alphabetic())
5+
.collect::<HashSet<char>>()
6+
.len()
7+
== 26
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Introduction
2+
3+
There are various idomatic approaches to Pangram.
4+
You can use the `all` method on the alphabet letters with the `contains` method on the lowercased letters of sentence.
5+
You can see if the `HashSet` of the alphabet `is_substring` of a `HashSet` of the lowercased `sentence`.
6+
Or you can see if the `HashSet` `len` of the lowercased `sentence` filtered to just ASCII letters is `26`.
7+
Or you can use a bit field to keep track of used letters.
8+
9+
10+
## General guidance
11+
12+
The key to solving Pangram is determining if all of the letters in the alphabet are in the `&str` being tested.
13+
The occurrence of either the letter `a` or the letter `A` would count as the same letter.
14+
15+
## Approach: `all` with `contains` on lowercased letters
16+
17+
```rust
18+
pub fn is_pangram(sentence: &str) -> bool {
19+
let sentence_lowered = sentence.to_lowercase();
20+
('a'..='z').all(|ltr| sentence_lowered.contains(ltr))
21+
}
22+
```
23+
24+
For more information, check the [`all` with `contains` approach][approach-all-contains].
25+
26+
## Approach: `HashSet` with `is_subset` on lowercased characters
27+
28+
```rust
29+
use std::collections::HashSet;
30+
31+
pub fn is_pangram(sentence: &str) -> bool {
32+
let all: HashSet<char> = HashSet::from_iter("abcdefghijklmnopqrstuvwxyz".chars());
33+
let used: HashSet<char> = HashSet::from_iter(sentence.to_lowercase().chars());
34+
all.is_subset(&used)
35+
}
36+
```
37+
38+
For more information, check the [`HashSet` with `is_subset` approach][approach-hashset-is-subset].
39+
40+
## Approach: `HashSet` with `len` on lowercased characters
41+
42+
```rust
43+
use std::collections::HashSet;
44+
45+
pub fn is_pangram(sentence: &str) -> bool {
46+
sentence
47+
.to_lowercase()
48+
.chars()
49+
.filter(|c| c.is_ascii_alphabetic())
50+
.collect::<HashSet<char>>()
51+
.len()
52+
== 26
53+
}
54+
```
55+
56+
For more information, check the [`HashSet` with `len` approach][approach-hashset-len].
57+
58+
## Bit field
59+
60+
```rust
61+
const A_LCASE: u8 = 97;
62+
const A_UCASE: u8 = 65;
63+
const ALL_26_BITS_SET: u32 = 67108863;
64+
65+
pub fn is_pangram(sentence: &str) -> bool {
66+
let mut letter_flags = 0;
67+
68+
for letter in sentence.chars() {
69+
if letter >= 'a' && letter <= 'z' {
70+
letter_flags |= 1 << (letter as u8 - A_LCASE);
71+
} else if letter >= 'A' && letter <= 'Z' {
72+
letter_flags |= 1 << (letter as u8 - A_UCASE);
73+
}
74+
}
75+
letter_flags == ALL_26_BITS_SET
76+
}
77+
```
78+
79+
For more information, check the [Bit field approach][approach-bitfield].
80+
81+
## Which approach to use?
82+
83+
The fastest is the `Bit field` approach.
84+
85+
To compare performance of the approaches, check the [Performance article][article-performance].
86+
87+
[approach-all-contains]: https://exercism.org/tracks/rust/exercises/pangram/approaches/all-contains
88+
[approach-hashset-is-subset]: https://exercism.org/tracks/rust/exercises/pangram/approaches/hashset-is-subset
89+
[approach-hashset-len]: https://exercism.org/tracks/rust/exercises/pangram/approaches/hashset-len
90+
[approach-bitfield]: https://exercism.org/tracks/rust/exercises/pangram/approaches/bitfield
91+
[article-performance]: https://exercism.org/tracks/rust/exercises/pangram/articles/performance

0 commit comments

Comments
 (0)