How can I add RegEx to HashMap

Trying to build a HashMap of Command and Function where the command is a RegEx and once the user post a phrase matching this a given Regex (hashmap key) the programe will execute the function associated with this command (hashmap value).

I drafted the below, but has 2 issues with it:

  1. Can not add RegEx to the HashMap
error[E0599]: no method named `insert` found for struct `std::collections::HashMap<regex::re_unicode::Regex, fn(i32, i32) -> i32>` in the current scope
   --> src\main.rs:124:14
    |
124 |     commands.insert(
    |              ^^^^^^ method not found in `std::collections::HashMap<regex::re_unicode::Regex, fn(i32, i32) -> i32>`
    | 
   ::: C:\Users\Yara Yousef\.cargo\registry\src\github.com-1ecc6299db9ec823\regex-1.3.7\src\re_unicode.rs:136:1
    |
136 | pub struct Regex(Exec);
    | -----------------------
    | |
    | doesn't satisfy `regex::re_unicode::Regex: std::cmp::Eq`
    | doesn't satisfy `regex::re_unicode::Regex: std::hash::Hash`
    |
    = note: the method `insert` exists but the following trait bounds were not satisfied:
            `regex::re_unicode::Regex: std::cmp::Eq`
            `regex::re_unicode::Regex: std::hash::Hash`
  1. Casting the RegEx capture as number:
error[E0605]: non-primitive cast: `regex::re_unicode::Match<'_>` as `i32`
   --> src\main.rs:140:44
    |
140 |                     let sum_num = function(caps.get(2).unwrap() as i32, caps.get(4).unwrap() as i32);
    |                                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

My code is:

use regex::Regex;
use std::collections::HashMap;

fn sum(x: i32, y: i32) -> i32 {
    x + y
}

fn main() {
    let mut commands: HashMap<Regex, fn(x: i32, y: i32) -> i32> = HashMap::new();
    commands.insert(
        Regex::new(r#"(?xi)
                (What\sis|What's|Calculate|How much is)
                \s(\d)
                \s(\+|and|plus|)
                \s(\d)
            "#).unwrap(), 
        sum
    );

    let phrase = "what is 1 + 2";

    for (command, function) in &commands {
        match command.is_match(phrase) {
            true => {
                for caps in command.captures(phrase) {
                    let sum_num = function(caps.get(2).unwrap() as i32, caps.get(4).unwrap() as i32);
                    println!("Sum of {} and {} is {}",
                    caps.get(2).unwrap(), caps.get(4).unwrap(), sum_num);
                }
            },
            false => {},
        }
    }

The problem is that HashMap requires keys to be hashable (implement the Hash trait), and comparable (Eq trait).
I would suggest using a Vec instead, where the value is a tuple of a Regex instance and a callback.
It wouldn't make much sense to use a HashMap if you're going to iterate over their keys...


use regex::Regex; // 1.3.7
use std::str::FromStr;

fn sum(a: i32, b: i32) -> i32 {
    a + b
}

fn main() {
    let mut commands: Vec<(Regex, fn(x: i32, y: i32) -> i32)> = Vec::new();
    commands.push(
        (Regex::new(r#"(?i)(What\Wis|What's|Calculate|How much is)\W*(\d+)\W*(\+|and|plus)\W*(\d+)"#).unwrap(), sum)
    );

    let phrase = "what is 1 + 2";

    for (command, function) in &commands {
        match command.is_match(phrase) {
            true => {
                for caps in command.captures(phrase) {
                    let a = i32::from_str(caps.get(2).unwrap().as_str()).unwrap();
                    let b = i32::from_str(caps.get(4).unwrap().as_str()).unwrap();
                    let sum_num = function(a, b);
                    println!("Sum of {} and {} is {}", a, b, sum_num);
                }
            },
            false => {},
        }   
    }
}
2 Likes

The compiler tells you just what exactly the problem is. Regex isn't Hash or Eq (i.e. can't be hashed or compared for equality), so you can't use it as a key in a hashmap.

As to converting a match to an integer: I don't know why you expect a primitive cast to work from a library-defined type like Match; you should probably get the text captured by the match using its as_str() method, then .parse::<i32>() it into an integer.

1 Like

I answered this here, with an example of how to make it work: Adding RegEx to HashMap · Issue #670 · rust-lang/regex · GitHub

Can you pls elaborate more about this statement.

I assume @naim is referring to the tradeoffs between BTreeMap and HashMap.

In general, iterating over every member of a hash table (HashMap or HashSet) is both slower than in other containers and doesn't have a reliable order. This is the cost of hash tables providing mostly (amortized) O(1) operations instead of O(log n) operations.

Or as std::collections - Rust puts it:

Use a HashMap when:

  • You want to associate arbitrary keys with an arbitrary value.
  • You want a cache.
  • You want a map, with no extra functionality.

Use a BTreeMap when:

  • You want a map sorted by its keys.
  • You want to be able to get a range of entries on-demand.
  • You're interested in what the smallest or largest key-value pair is.
  • You want to find the largest or smallest key that is smaller or larger than something.
3 Likes

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