We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Performance and Data Layout step 16 of 20
Implement a custom hasher for integer keys
Implement a small custom hasher, use it in a HashMap, and count pairs of equal integer keys. This exercise defines exact hash values so you can verify the byte and integer paths separately.
pub fn hash_of(v: u64) -> u64
pub fn count_pairs(keys: Vec<u64>) -> u64
Required hashing rules
Keep the supplied constants and public types:
OFFSET = 0xcbf2_9ce4_8422_2325PRIME = 0x0000_0100_0000_01b3FastHasher(pub u64)stores the current state.FastBuildconstructs a fresh FastHasher with state OFFSET.
Implement Hasher for FastHasher with these operations:
| Operation | Effect |
|---|---|
write(bytes) |
For each byte b in order, set state to (state ^ u64::from(b)).wrapping_mul(PRIME). |
write_u64(v) |
Set state to (state ^ v).wrapping_mul(PRIME) once. |
finish() |
Return state ^ (state >> 32) without changing state. |
Writes continue from the current state. An empty byte slice changes nothing. Calling finish does not reset or consume the hasher, so repeated calls without a write return the same value. Use wrapping multiplication regardless of build settings: arithmetic is modulo 2 to the power of 64.
The byte recurrence resembles FNV-1a, but the whole-u64 update and final mixing make this a task-specific algorithm. Do not substitute a standard FNV hash.
Implement BuildHasher for FastBuild with type Hasher = FastHasher.
Its build_hasher method returns FastHasher(OFFSET). Implement hash_of with
FastBuild.hash_one(v) so it hashes one value through the builder's API.
Why override write_u64?
Hashing a u64 through its Hash implementation uses the integer-writing path. The required operation handles the whole integer in one update. Feeding its eight bytes into write would apply eight updates and produce a different hash, even on a little-endian machine.
Rust 1.98.1's default write_u64 uses native-endian bytes. The explicit override avoids depending on that representation and implements the exact rule above. In general, Hasher does not promise equivalence between different write methods.
The starter omits write, one of Hasher's two required methods, so it fails with E0046. Providing write fixes that trait error; the integer override is still needed to match the specified hashes.
Count unordered equal-key pairs
Use HashMap<u64, u64, FastBuild> to count occurrences. For each distinct key
with count c, contribute c * (c - 1) / 2. Sum those contributions.
For example, [7, 7, 9, 7, 9] has three pairs of sevens and one pair of nines,
so return four. Empty input and all-distinct keys return zero. All u64 key
values are valid, including zero and u64::MAX. Assume counts, intermediate
products and the total fit in u64; allocation failure is outside the task.
HashMap::with_capacity_and_hasher accepts a capacity and FastBuild. Reserving space can avoid growth, but input length can greatly exceed the number of distinct keys. Treat that reservation as a memory/performance choice rather than a universal optimization rule.
The harness generates n keys with
i.wrapping_mul(2_654_435_761) % modulus for i starting at zero. It treats a
modulus of zero as one. Its largest fixture uses two million keys and modulus
500,000. It also checks hash_of at zero, one, 42 and u64::MAX. These output
checks do not independently prove the map uses FastBuild or exercise every
possible byte sequence.
Hashing and iteration order
HashMap iteration order is unspecified. A deterministic hasher does not turn it into a documented ordering guarantee. If output order matters, sort the entries or use an ordered collection such as BTreeMap.
Pair counting does not depend on iteration order: it sums contributions from all counts, within the arithmetic bounds above.
The default HashMap builder uses randomized hashing to help resist deliberate collision attacks. Its algorithm and seeding details are implementation choices; do not assume a single process-wide seed or a permanently fixed algorithm. This exercise's deterministic hasher has no such protection. Use it for the controlled exercise data, not as a general replacement for untrusted keys. Whether a different hasher improves a real workload requires measurement.
Keep equality and hashing consistent
Equal keys must hash equally when used with the same builder. Different keys may collide; the map still checks equality. Do not change a stored key in a way that changes its equality or hash behavior.
A handwritten equality implementation can be compatible with a derived Hash implementation, but it is easy to make them disagree about which fields matter. Review them together. Neither custom hashing nor Clippy substitutes for checking that contract.
Submissions must compile, pass tests and pass Clippy with warnings treated as errors. Use hash_one directly rather than manually assembling that call.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.