Having an `Option<T>` as HashMap key is it possible?

Hello everyone! I'm back with another beginner question!
So, this time the error is inside the count_votes method in two different lines.
This method tries to count the the frequency of the different votes inside of total_votes using a HashMap.
The map is filled up with Option<T> votes as keys associated to usize counters values. The counters increments every time a vote repeats. It's a pretty standard algorithm.
Then count_votes returns the most frequent vote wrapped inside a Ok(Scrutiny()) instance.

Here is the code:

pub struct Election<T> {
    pub voters: HashMap<&'static str, Option<T>>,
    pub winner_threshold: u32,
}

impl<T> Election<T> {
    pub fn new(voters: &[&'static str], winner_threshold: u32) -> Election<T> {
        let voters: HashMap<&'static str, Option<T>> = (*voters).iter()
                                                                .map(|&v| (v, None))
                                                                .collect();
        Election { voters, winner_threshold, }
    }

    pub fn vote(&mut self, voter: &'static str, vote: T) {
        if let Some(voter) = self.voters.get_mut(voter) {
            voter.get_or_insert(vote);
        }
    }

    pub fn count_votes(self) -> Result<Scrutiny<T>, Election<T>> {
        let total_votes = self.voters.values();
        let unvoted = total_votes.filter(|v| !v.is_some())
                                 .count();
        if unvoted > 0 {
            Err(self)
        } else {
            let mut votes_counter: HashMap<Option<T>, usize> = new();
            for vote in total_votes {
                // this line has a compile error
                let vote_count = votes_counter.entry(vote).or_insert(0);
                *vote_count += 1;
            }
            // this line has a compile error
            let result = votes_counter.iter().max_by(|&(k, v)| *v);
            Ok(Scrutiny { result })
        }
    }
}

pub struct Scrutiny<T> {
    result: T,
}

impl<T> Scrutiny<T> {
    pub fn result(&self) -> &T {
        &self.result
    }
}

The first error that I encountered, and that I don't know if I'm not setting properly the pointer level of votes_counter or just HashMap can't use Some(T) as keys, is the following:

error[E0599]: no method named `entry` found for struct `std::collections::HashMap<std::option::Option<T>, usize>` in the current scope
   --> src/core/election.rs:37:48
    |
37  |                   let vote_count = votes_counter.entry(vote).or_insert(0);
    |                                                  ^^^^^ method not found in `std::collections::HashMap<std::option::Option<T>, usize>`
    |
    = note: the method `entry` exists but the following trait bounds were not satisfied:
            `std::option::Option<T>: std::cmp::Eq`
            `std::option::Option<T>: std::hash::Hash`

And the second one, I think it must be an iterator issue that I unable to solve due to my short experience using them. I've tried different approaches going back and forth but I haven't seen the light at the end of the tunnel yet.

error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument
  --> src/core/election.rs:41:40
   |
41 | ...                   .max_by(|&(k, v)| *v);
   |                        ^^^^^^ --------- takes 1 argument
   |                        |
   |                        expected closure that takes 2 arguments

Thank you very much for your time to review my code.

You need to specify that T can be hashed and compared to use it as the key.

pub fn count_votes(self) -> Result<Scrutiny<T>, Election<T>>
where
    T: Eq + Hash,
{
    ...
}

You can also put the where bound on the impl block.

Note that you should probably be using String instead of &'static str. The &'static str type can only be used with string literals that are hard-coded in your codebase.

2 Likes

Thanks @alice ! awesome fix! I've just tried it out and it enabled me to go further. However, I still have an issue and I think it might be related to how Option is referenced.

--> src/core/election.rs:42:54
   |
42 |                 let vote_count = votes_counter.entry(vote).or_insert(0);
   |                                                      ^^^^
   |                                                      |
   |                                                      expected enum `std::option::Option`, found reference
   |                                                      help: you can convert from `&Option<T>` to `Option<&T>` using `.as_ref()`: `vote.as_ref()`
   |
   = note:   expected enum `std::option::Option<T>`
           found reference `&std::option::Option<T>`

In fact, I've tried to replace vote for *vote and vote.as_ref() but both alternatives throw errors. I have the impression that I'm not deeply understanding the ownership rule here. Would you have any clue what the problem is here?

ps: Great catch on the String to replace &'static str. I've already put it in place. Thanks again.

You have to give entry an owned value to use it. To do this, you can clone it. The *vote did not work because T is not necessarily cheaply cloneable. It is a bit unfortunate to make a clone in this case, so I recommend that you instead use get_mut combined with insert.

if let Some(val) = votes_counter.get_mut(&vote) {
    *val += 1;
} else {
    votes_counter.insert(vote.clone(), 1);
}
2 Likes

Very accurate. Many thanks @alice :slight_smile:
I'll just add a small detail to your answer: T must implement Clone trait to copy vote.

Final implementation:

use std::collections::HashMap;
use std::hash::Hash;

pub struct Election<T> {
    pub voters: HashMap<String, Option<T>>,
}

impl<T> Election<T> {
    pub fn new(voters: &[String]) -> Election<T> {
        let voters: HashMap<String, Option<T>> = (*voters).iter()
                                                          .map(|v| (v.clone(), None))
                                                          .collect();
        Election { voters, }
    }

    pub fn vote(&mut self, voter: &String, vote: T) {
        if let Some(voter) = self.voters.get_mut(voter) {
            voter.get_or_insert(vote);
        }
    }

    pub fn count_votes(self) -> Result<Scrutiny<T>, Election<T>> 
    where T: 
        Eq + Hash + Clone,
    {
        let unvoted = self.voters.values()
                                 .filter(|v| !v.is_some())
                                 .count();
        if unvoted > 0 {
            Err(self)
        } else {
            let mut votes_counter = HashMap::<Option<T>, usize>::new();
            for vote in self.voters.values() {
                if let Some(val) = votes_counter.get_mut(&vote) {
                    *val += 1;
                } else {
                     votes_counter.insert(vote.clone(), 1);
                }
            }
            Ok(Scrutiny { result: votes_counter, })
        }
    }
}

pub struct Scrutiny<T> {
    result: HashMap::<Option<T>, usize>,
}

impl<T> Scrutiny<T> {
    pub fn result(&self) -> &HashMap::<Option<T>, usize> {
        &self.result
    }
}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.