Skip to content

Commit

Permalink
HashMap, keys, HashSet, SmallIntMap. Closes rust-lang#161
Browse files Browse the repository at this point in the history
  • Loading branch information
abonander committed Jul 31, 2014
1 parent cb94752 commit f23846c
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 2 deletions.
56 changes: 56 additions & 0 deletions examples/staging/hash/alt-key-types/alt-key-types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::collections::HashMap;

// Eq requires that you derive PartialEq on the type.
#[deriving(PartialEq, Eq, Hash)]
struct Account<'a>{
username: &'a str,
password: &'a str,
}

struct AccountInfo<'a>{
name: &'a str,
email: &'a str,
}

type Accounts<'a> = HashMap<Account<'a>, AccountInfo<'a>>;

fn try_logon<'a>(accounts: &Accounts<'a>,
username: &'a str, password: &'a str){
println!("Username: {}", username);
println!("Password: {}", password);
println!("Attempting logon...");

let logon = Account {
username: username,
password: password,
};

match accounts.find(&logon) {
Some(account_info) => {
println!("Successful logon!");
println!("Name: {}", account_info.name);
println!("Email: {}", account_info.email);
},
_ => println!("Login failed!"),
}
}

fn main(){
let mut accounts: Accounts = HashMap::new();

let account = Account {
username: "j.everyman",
password: "password123",
};

let account_info = AccountInfo {
name: "John Everyman",
email: "j.everyman@email.com",
};

accounts.insert(account, account_info);

try_logon(&accounts, "j.everyman", "psasword123");

try_logon(&accounts, "j.everyman", "password123");
}
35 changes: 35 additions & 0 deletions examples/staging/hash/alt-key-types/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Any type that implements the `Eq` and `Hash` traits can be a key in `HashMap`.
This includes:

* `bool` (though not very useful since there is only two possible keys)
* `int`, `uint`, and all variations thereof
(see `SmallIntMap` for a more streamlined map implementation keyed by `uint`)

Note that `f32` and `f64` do *not* implement `Hash`,
likely because floating-point precision errors
would make using them as hash map keys very frustrating.

All collection classes implement `Eq`, and implicitly implement `Hash`
if their contained type also implements `Hash`.
E.g. `Vec<T>` will implement `Hash` if `T` implements `Hash`.

You can easily implement `Eq` and `Hash` for a custom type with just one line:
`#[deriving(Eq,Hash)]`

The compiler will do the rest. If you want more control over the details,
you can implement `Eq` and `Hash` yourself.
This guide will not cover the specifics of implementing `Hash`.

Note that two instances of a type that are equal
(such that `a == b` returns `true`) should hash to the same value,
but two instances with the same hash don't necessarily have to be equal.
Read up on [hashes][hash] and [hash collisions][collision]
for more information.

To play around with using a `struct` in `HashMap`,
let's try making a very simple user logon system:

{alt-key-types.play}

[hash]: http://en.wikipedia.org/wiki/Hash_function
[collision]: http://en.wikipedia.org/wiki/Hash_collision
43 changes: 43 additions & 0 deletions examples/staging/hash/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::collections::HashMap;

fn call(number: &str) -> &str {
match number {
"798-1364" => "We're sorry, the call cannot be completed as dialed.
Please hang up and try again.",
"645-7689" => "Hello, this is Mr. Awesome's Pizza. My name is Fred.
What can I get for you today?",
_ => "Hi! Who is this again?"
}
}

fn main() {
let mut contacts = HashMap::new();

contacts.insert("Daniel", "798-1364");
contacts.insert("Ashley", "645-7689");
contacts.insert("Katie", "435-8291");
contacts.insert("Robert", "956-1745");

// Takes a reference and returns Option<&V>
match contacts.find(&("Daniel")) {
Some(&number) => println!("Calling Daniel: {}", call(number)),
_ => println!("Don't have Daniel's number."),
}

// `HashMap::insert()` returns true
// if the inserted value is new, false otherwise
contacts.insert("Daniel", "164-6743");

match contacts.find(&("Ashley")) {
Some(&number) => println!("Calling Ashley: {}", call(number)),
_ => println!("Don't have Ashley's number."),
}

contacts.remove(&("Ashley"));

// `HashMap::iter()` returns an iterator that yields
// (&'a key, &'a value) pairs in arbitrary order.
for (contact, &number) in contacts.iter() {
println!("Calling {}: {}", contact, call(number));
}
}
36 changes: 36 additions & 0 deletions examples/staging/hash/hashset/hashset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use std::collections::HashSet;

fn main() {
let mut a: HashSet<int> = vec!(1i, 2, 3).move_iter().collect();
let mut b: HashSet<int> = vec!(2i, 3, 4).move_iter().collect();

assert!(a.insert(4));
assert!(a.contains(&4));

// `HashSet::insert()` returns false if
// there was a value already present.
assert!(b.insert(4), "Value 4 is already in set B!");
// FIXME ^ Comment out this line

b.insert(5);

// If a collection's element type implements `Show`,
// then the collection implements `Show`.
// It usually prints its elements in the format `[elem1, elem2, ...]`
println!("A: {}", a);
println!("B: {}", b);

// Print [1, 2, 3, 4, 5] in arbitrary order
println!("Union: {}", a.union(&b).collect::<Vec<&int>>());

// This should print [1]
println!("Difference: {}", a.difference(&b).collect::<Vec<&int>>());

// Print [2, 3, 4] in arbitrary order.
println!("Intersection: {}", a.intersection(&b).collect::<Vec<&int>>());

// Print [1, 5]
println!("Symmetric Difference: {}",
a.symmetric_difference(&b).collect::<Vec<&int>>());
}

44 changes: 44 additions & 0 deletions examples/staging/hash/hashset/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Consider a `HashSet` as a `HashMap` where the key and value are the same.
Or, to be more precise, a `HashMap` where we just care about the keys.

"What's the point of that?" you ask. "I could just store the keys in a `Vec`."

A `HashSet`'s unique feature is that
it is guaranteed to not have duplicate elements.
That's the contract that any [`Set`][set] implementation fulfills.
`HashSet` is just one implementation. (see also: [`MutableSet`][mutableset])

If you insert a value that is already present in the `HashSet`,
(i.e. the new value is equal to the existing and they both have the same hash),
then the new value will replace the old.

This is great for when you never want more than one of something,
or when you want to know if you've already got something.

But sets can do more than that.
If they weren't important, then why would there be a
[branch of mathematics][set-theory] dedicated to them?

Sets have 4 primary operations (all of the following calls return an iterator):

* `union`: get all the elements in one set or the other.

* `difference`: get all the elements that are in the first set but not the second.

* `intersection`: get all the elements that are only in *both* sets.

* `symmetric_difference`:
get all the elements that are in one set or the other, but *not* both.

Try all of these in the following example.

{hashset.play}

Sets don't seem so pointless now, do they?

(Examples adapted from the [documentation.][hash-set])

[set]: http://doc.rust-lang.org/std/collections/trait.Set.html
[mutableset]: http://doc.rust-lang.org/std/collections/trait.MutableSet.html
[set-theory]: http://en.wikipedia.org/wiki/Set_theory
[hash-set]: http://doc.rust-lang.org/std/collections/hashmap/struct.HashSet.html#method.difference
18 changes: 18 additions & 0 deletions examples/staging/hash/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Where vectors store values by an integer index, `HashMap`s store values by key.
`HashMap` keys can be booleans, integers, strings,
or any other type that implements the `Eq` and `Hash` traits.
More on this in the next section.

Like vectors, `HashMap`s are growable, but HashMaps can also shrink themselves
when they have excess space.
You can create a HashMap with a certain starting capacity using
`HashMap::with_capacity(uint)`, or use `HashMap::new()` to get a HashMap
with a default initial capacity (recommended).

{hash.play}

For more information on how hashing and hash maps
(sometimes called hash tables) work, have a look at
[Wikipedia][wiki-hash]

[wiki-hash]: (http://en.wikipedia.org/wiki/Hash_table)
22 changes: 22 additions & 0 deletions examples/staging/hash/smallintmap/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
`SmallIntMap` can be very useful in certain cases.
You can use it anywhere you would use a `HashMap<uint, _>`.
Internally, it's just a wrapper for `Vec`
that handles growth and inconsistent indexes.

Because it uses a `Vec` for storage,
`SmallIntMap` will need to grow to the size of its largest key.
So if your largest key is 50,
a `SmallIntMap` will take up as much space as 50 elements.
Thus the emphasis on *small*.

Here's a good example:
a program for managing a small apartment complex.
Some apartments might be occupied, some might be empty.
Maybe you'll add more later,
and you want the program to be able to adapt without any extra work.

{smallintmap.play}

You might not normally use `SmallIntMap`,
but maybe, one day, you'll have a need for it.
And when you do need it, it will be there. And you'll know how to use it.
42 changes: 42 additions & 0 deletions examples/staging/hash/smallintmap/smallintmap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use std::collections::SmallIntMap;

struct Tenant<'a> {
name: &'a str,
phone: &'a str,
}

fn main() {
// Start with 5 apartments
let mut apartments = SmallIntMap::with_capacity(5);

// The compiler infers 1 as uint
apartments.insert(1, Tenant {
name: "John Smith",
phone: "555-1234",
});

apartments.insert(3, Tenant {
name: "Henrietta George",
phone: "555-2314",
});

apartments.insert(5, Tenant {
name: "David Rogers",
phone: "555-5467",
});

apartments.pop(&1);
match apartments.find_mut(&3) {
Some(henrietta) => henrietta.name = "David and Henrietta Smith",
_ => println!("Oh no! Where did David and Henrietta go?"),
}

apartments.insert(0, Tenant {
name: "Phillip Davis",
phone: "5555-7869",
});

for (key, tenant) in apartments.iter(){
println!("{}: {} ({})", key, tenant.name, tenant.phone);
}
}
8 changes: 6 additions & 2 deletions examples/structure.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,18 @@
{ "id": "decodable", "title": "`Decodable`", "children": null },
{ "id": "encodable", "title": "`Encodable`", "children": null }
] },
{ "id": "fmt", "title": "Formatting", "children": null }
{ "id": "fmt", "title": "Formatting", "children": null },
{ "id": "hash", "title": "HashMap", "children": [
{ "id": "alt-key-types", "title": "Alternate/custom key types", "children": null},
{ "id": "hashset", "title": "HashSet", "children": null },
{ "id": "smallintmap", "title": "SmallIntMap", "children": null }
] }
] },
{ "id": "todo", "title": "TODO", "children": [
{ "id": "arg", "title": "Program arguments", "children": null },
{ "id": "assert", "title": "assert! and debug_assert!", "children": null },
{ "id": "comment", "title": "Comments", "children": null },
{ "id": "green", "title": "Green threads", "children": null },
{ "id": "hash", "title": "Hasher and Hashmap", "children": null },
{ "id": "log", "title": "Logging", "children": null },
{ "id": "rc", "title": "Reference counting", "children": null },
{ "id": "regex", "title": "Regex", "children": null },
Expand Down

0 comments on commit f23846c

Please sign in to comment.