Learning Programming Concepts by Jumping in at the Deep End
There are many different ways to learn something; one that I enjoy from time to time is what I’d call the “jump in at the deep end”: Look at the solution to a problem and study each of its details until you understand all the concepts.
This is in contrast to a tutorial-style learning where you learn one thing after another and gradually build more complex things. I like the “deep end” approach because it’s a very personal learning style. It often gives me a way to discover interesting aspects of things myself, try to find out how parts work together, and play around with the material until I think I’ve got how it works. And while the road to understanding might be full of little frustrations and missing knowledge, I find it a very rewarding exercise. (Or maybe I’m just one of those people who like to geek out about neat concepts.)
Recently, I came across this task: Count the individual words in a given text. The solution I found seemed like a good “deep end” problem to me. It’s short and yet full of different concepts.
Here’s what I’d write (in Rust):
use std::collections::HashMap;
fn count_words(text: &str) -> HashMap<&str, usize> {
text.split(' ').fold(
HashMap::new(),
|mut map, word| { *map.entry(word).or_insert(0) += 1; map }
)
}
It’s just 8 lines! (You can play with the code here.)
And here are the concepts you should study to fully understand what’s going on (in no particular order; I tried to add a lot of links):
- Functions:
fn count_words
- Modules and imports:
use std::…
- Strings and characters:
&str
and' '
are guaranteed to be UTF-8 - Slices: views into/references to memory and, in Rust, their lifetimes: the
&str
s in the hash map are slices of the inputtext
- Iterators:
split
returns a type that implementsIterator
fold
(a.k.a.reduce
): A core part of functional programming (more information)Iterator
: a trait
- Map data type:
HashMap
- Hashing
- Generic data types:
Split<'a, P>
,HashMap<K, V>
- Rust’s entry API: A view into a map with the ability to transform/add items
- closures:
|x, y| { … }
- Implicit
return
, last expression in a block gets returned:{ …; map }
returnsmap
- Mutable vs. immutable variable bindings:
mut map
- References, pointers, dereferencing:
*thingy
and&thingy
- Integer sizes:
usize
- Built-in operators:
+=
Note that most of these concepts are not special to Rust. I’ve just written this in Rust because it’s the language I currently use the most in my free time, and also because it’s a language that exposes you to a lot of interesting concepts.
Just for comparison, here’s the same in JavaScript (play with it on JSBin):
function count_words(text) {
return text.split(' ')
.reduce((map, word) => {
map[word] = map[word] ? map[word] + 1 : 1;
return map;
}, {});
}