Skip to content

Commit 83614e1

Browse files
committed
Add Parallel Letter Frequency solution
1 parent 383c649 commit 83614e1

File tree

5 files changed

+373
-0
lines changed

5 files changed

+373
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[package]
2+
name = "parallel-letter-frequency"
3+
version = "0.0.0"
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Parallel Letter Frequency
2+
3+
Count the frequency of letters in texts using parallel computation.
4+
5+
Parallelism is about doing things in parallel that can also be done
6+
sequentially. A common example is counting the frequency of letters.
7+
Create a function that returns the total frequency of each letter in a
8+
list of texts and that employs parallelism.
9+
10+
# Parallel Letter Frequency in Rust
11+
12+
Learn more about concurrency in Rust here:
13+
14+
- [Concurrency](https://doc.rust-lang.org/book/second-edition/ch16-00-concurrency.html)
15+
16+
## Bonus
17+
18+
This exercise also includes a benchmark, with a sequential implementation as a
19+
baseline. You can compare your solution to the benchmark. Observe the
20+
effect different size inputs have on the performance of each. Can you
21+
surpass the benchmark using concurrent programming techniques?
22+
23+
As of this writing, test::Bencher is unstable and only available on
24+
*nightly* Rust. Run the benchmarks with Cargo:
25+
26+
```
27+
cargo bench
28+
```
29+
30+
If you are using rustup.rs:
31+
32+
```
33+
rustup run nightly cargo bench
34+
```
35+
36+
- [Benchmark tests](https://doc.rust-lang.org/stable/unstable-book/library-features/test.html)
37+
38+
Learn more about nightly Rust:
39+
40+
- [Nightly Rust](https://doc.rust-lang.org/book/second-edition/ch01-03-how-rust-is-made-and-nightly-rust.html)
41+
- [Rustup: Working with nightly](https://github.com/rust-lang-nursery/rustup.rs#working-with-nightly-rust)
42+
43+
44+
## Rust Installation
45+
46+
Refer to the [exercism help page][help-page] for Rust installation and learning
47+
resources.
48+
49+
## Writing the Code
50+
51+
Execute the tests with:
52+
53+
```bash
54+
$ cargo test
55+
```
56+
57+
All but the first test have been ignored. After you get the first test to
58+
pass, open the tests source file which is located in the `tests` directory
59+
and remove the `#[ignore]` flag from the next test and get the tests to pass
60+
again. Each separate test is a function with `#[test]` flag above it.
61+
Continue, until you pass every test.
62+
63+
If you wish to run all tests without editing the tests source file, use:
64+
65+
```bash
66+
$ cargo test -- --ignored
67+
```
68+
69+
To run a specific test, for example `some_test`, you can use:
70+
71+
```bash
72+
$ cargo test some_test
73+
```
74+
75+
If the specific test is ignored use:
76+
77+
```bash
78+
$ cargo test some_test -- --ignored
79+
```
80+
81+
To learn more about Rust tests refer to the [online test documentation][rust-tests]
82+
83+
Make sure to read the [Modules](https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html) chapter if you
84+
haven't already, it will help you with organizing your files.
85+
86+
## Feedback, Issues, Pull Requests
87+
88+
The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help!
89+
90+
If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md).
91+
92+
[help-page]: http://exercism.io/languages/rust
93+
[modules]: https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html
94+
[cargo]: https://doc.rust-lang.org/book/second-edition/ch14-00-more-about-cargo.html
95+
[rust-tests]: https://doc.rust-lang.org/book/second-edition/ch11-02-running-tests.html
96+
97+
## Submitting Incomplete Solutions
98+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#![feature(test)]
2+
extern crate parallel_letter_frequency;
3+
extern crate test;
4+
5+
use std::collections::HashMap;
6+
use test::Bencher;
7+
8+
#[bench]
9+
fn bench_tiny_parallel(b: &mut Bencher) {
10+
let tiny = &["a"];
11+
b.iter(|| parallel_letter_frequency::frequency(tiny, 3));
12+
}
13+
14+
#[bench]
15+
fn bench_tiny_sequential(b: &mut Bencher) {
16+
let tiny = &["a"];
17+
b.iter(|| frequency(tiny));
18+
}
19+
20+
#[bench]
21+
fn bench_small_parallel(b: &mut Bencher) {
22+
let texts = all_texts(1);
23+
b.iter(|| parallel_letter_frequency::frequency(&texts, 3));
24+
}
25+
26+
#[bench]
27+
fn bench_small_sequential(b: &mut Bencher) {
28+
let texts = all_texts(1);
29+
b.iter(|| frequency(&texts));
30+
}
31+
32+
#[bench]
33+
fn bench_large_parallel(b: &mut Bencher) {
34+
let texts = all_texts(30);
35+
b.iter(|| parallel_letter_frequency::frequency(&texts, 3));
36+
}
37+
38+
#[bench]
39+
fn bench_large_sequential(b: &mut Bencher) {
40+
let texts = all_texts(30);
41+
b.iter(|| frequency(&texts));
42+
}
43+
44+
/// Simple sequential char frequency. Can it be beat?
45+
pub fn frequency(texts: &[&str]) -> HashMap<char, usize> {
46+
let mut map = HashMap::new();
47+
48+
for line in texts {
49+
for chr in line.chars().filter(|c| c.is_alphabetic()) {
50+
if let Some(c) = chr.to_lowercase().next() {
51+
(*map.entry(c).or_insert(0)) += 1;
52+
}
53+
}
54+
}
55+
56+
map
57+
}
58+
59+
fn all_texts(repeat: usize) -> Vec<&'static str> {
60+
[ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER]
61+
.iter()
62+
.cycle()
63+
.take(3 * repeat)
64+
.flat_map(|anthem| anthem.iter().cloned())
65+
.collect()
66+
}
67+
68+
// Poem by Friedrich Schiller. The corresponding music is the European Anthem.
69+
pub const ODE_AN_DIE_FREUDE: [&'static str; 8] = [
70+
"Freude schöner Götterfunken",
71+
"Tochter aus Elysium,",
72+
"Wir betreten feuertrunken,",
73+
"Himmlische, dein Heiligtum!",
74+
"Deine Zauber binden wieder",
75+
"Was die Mode streng geteilt;",
76+
"Alle Menschen werden Brüder,",
77+
"Wo dein sanfter Flügel weilt.",
78+
];
79+
80+
// Dutch national anthem
81+
pub const WILHELMUS: [&'static str; 8] = [
82+
"Wilhelmus van Nassouwe",
83+
"ben ik, van Duitsen bloed,",
84+
"den vaderland getrouwe",
85+
"blijf ik tot in den dood.",
86+
"Een Prinse van Oranje",
87+
"ben ik, vrij, onverveerd,",
88+
"den Koning van Hispanje",
89+
"heb ik altijd geëerd.",
90+
];
91+
92+
// American national anthem
93+
pub const STAR_SPANGLED_BANNER: [&'static str; 8] = [
94+
"O say can you see by the dawn's early light,",
95+
"What so proudly we hailed at the twilight's last gleaming,",
96+
"Whose broad stripes and bright stars through the perilous fight,",
97+
"O'er the ramparts we watched, were so gallantly streaming?",
98+
"And the rockets' red glare, the bombs bursting in air,",
99+
"Gave proof through the night that our flag was still there;",
100+
"O say does that star-spangled banner yet wave,",
101+
"O'er the land of the free and the home of the brave?",
102+
];
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use std::{collections::HashMap, thread};
2+
3+
pub type LetterCount = HashMap<char, u32>;
4+
5+
pub fn frequency(text: &[&str], num_workers: usize) -> LetterCount {
6+
let mut handles: Vec<thread::JoinHandle<LetterCount>> = Vec::new();
7+
8+
for chunk in build_chunks(text, num_workers) {
9+
handles.push(thread::spawn(move || frequency_helper(chunk)));
10+
}
11+
12+
handles
13+
.into_iter()
14+
.map(|handle| handle.join().unwrap())
15+
.fold(HashMap::new(), |acc, value| add(&acc, &value))
16+
}
17+
18+
fn build_chunks(text: &[&str], num_workers: usize) -> Vec<Vec<String>> {
19+
let mut chunks: Vec<Vec<String>> = Vec::new();
20+
for _ in 0..num_workers {
21+
chunks.push(Vec::new());
22+
}
23+
let mut index = 0;
24+
for line in text.iter() {
25+
chunks[index].push(line.to_string());
26+
index = (index + 1) % num_workers;
27+
}
28+
chunks
29+
}
30+
31+
fn add(list1: &LetterCount, list2: &LetterCount) -> LetterCount {
32+
let mut output = list1.clone();
33+
for (key, value) in list2 {
34+
let mut counter = output.entry(*key).or_insert(0);
35+
*counter += value;
36+
}
37+
output
38+
}
39+
40+
pub fn frequency_helper(texts: Vec<String>) -> LetterCount {
41+
let mut map = HashMap::new();
42+
43+
for line in texts {
44+
for chr in line.chars().filter(|c| c.is_alphabetic()) {
45+
if let Some(c) = chr.to_lowercase().next() {
46+
(*map.entry(c).or_insert(0)) += 1;
47+
}
48+
}
49+
}
50+
51+
map
52+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use std::collections::HashMap;
2+
3+
extern crate parallel_letter_frequency as frequency;
4+
5+
// Poem by Friedrich Schiller. The corresponding music is the European Anthem.
6+
const ODE_AN_DIE_FREUDE: [&'static str; 8] = [
7+
"Freude schöner Götterfunken",
8+
"Tochter aus Elysium,",
9+
"Wir betreten feuertrunken,",
10+
"Himmlische, dein Heiligtum!",
11+
"Deine Zauber binden wieder",
12+
"Was die Mode streng geteilt;",
13+
"Alle Menschen werden Brüder,",
14+
"Wo dein sanfter Flügel weilt.",
15+
];
16+
17+
// Dutch national anthem
18+
const WILHELMUS: [&'static str; 8] = [
19+
"Wilhelmus van Nassouwe",
20+
"ben ik, van Duitsen bloed,",
21+
"den vaderland getrouwe",
22+
"blijf ik tot in den dood.",
23+
"Een Prinse van Oranje",
24+
"ben ik, vrij, onverveerd,",
25+
"den Koning van Hispanje",
26+
"heb ik altijd geëerd.",
27+
];
28+
29+
// American national anthem
30+
const STAR_SPANGLED_BANNER: [&'static str; 8] = [
31+
"O say can you see by the dawn's early light,",
32+
"What so proudly we hailed at the twilight's last gleaming,",
33+
"Whose broad stripes and bright stars through the perilous fight,",
34+
"O'er the ramparts we watched, were so gallantly streaming?",
35+
"And the rockets' red glare, the bombs bursting in air,",
36+
"Gave proof through the night that our flag was still there;",
37+
"O say does that star-spangled banner yet wave,",
38+
"O'er the land of the free and the home of the brave?",
39+
];
40+
41+
#[test]
42+
fn test_no_texts() {
43+
assert_eq!(frequency::frequency(&[], 4), HashMap::new());
44+
}
45+
46+
#[test]
47+
fn test_one_letter() {
48+
let mut hm = HashMap::new();
49+
hm.insert('a', 1);
50+
assert_eq!(frequency::frequency(&["a"], 4), hm);
51+
}
52+
53+
#[test]
54+
fn test_case_insensitivity() {
55+
let mut hm = HashMap::new();
56+
hm.insert('a', 2);
57+
assert_eq!(frequency::frequency(&["aA"], 4), hm);
58+
}
59+
60+
#[test]
61+
fn test_many_empty_lines() {
62+
let mut v = Vec::with_capacity(1000);
63+
for _ in 0..1000 {
64+
v.push("");
65+
}
66+
assert_eq!(frequency::frequency(&v[..], 4), HashMap::new());
67+
}
68+
69+
#[test]
70+
fn test_many_times_same_text() {
71+
let mut v = Vec::with_capacity(1000);
72+
for _ in 0..1000 {
73+
v.push("abc");
74+
}
75+
let mut hm = HashMap::new();
76+
hm.insert('a', 1000);
77+
hm.insert('b', 1000);
78+
hm.insert('c', 1000);
79+
assert_eq!(frequency::frequency(&v[..], 4), hm);
80+
}
81+
82+
#[test]
83+
fn test_punctuation_doesnt_count() {
84+
assert!(!frequency::frequency(&WILHELMUS, 4).contains_key(&','));
85+
}
86+
87+
#[test]
88+
fn test_numbers_dont_count() {
89+
assert!(!frequency::frequency(&["Testing, 1, 2, 3"], 4).contains_key(&'1'));
90+
}
91+
92+
#[test]
93+
fn test_all_three_anthems_1_worker() {
94+
let mut v = Vec::new();
95+
for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() {
96+
for line in anthem.iter() {
97+
v.push(*line);
98+
}
99+
}
100+
let freqs = frequency::frequency(&v[..], 1);
101+
assert_eq!(freqs.get(&'a'), Some(&49));
102+
assert_eq!(freqs.get(&'t'), Some(&56));
103+
assert_eq!(freqs.get(&'ü'), Some(&2));
104+
}
105+
106+
#[test]
107+
fn test_all_three_anthems_3_workers() {
108+
let mut v = Vec::new();
109+
for anthem in [ODE_AN_DIE_FREUDE, WILHELMUS, STAR_SPANGLED_BANNER].iter() {
110+
for line in anthem.iter() {
111+
v.push(*line);
112+
}
113+
}
114+
let freqs = frequency::frequency(&v[..], 3);
115+
assert_eq!(freqs.get(&'a'), Some(&49));
116+
assert_eq!(freqs.get(&'t'), Some(&56));
117+
assert_eq!(freqs.get(&'ü'), Some(&2));
118+
}

0 commit comments

Comments
 (0)