Можно ли использовать один общий для ключа и значения HashMap? - PullRequest
0 голосов
/ 27 апреля 2018

В главе 13 книги Rust вы реализуете Cacher, чтобы использовать памятку для демонстрации функционального программирования и ускорения выполнения долгосрочных задач. В качестве дополнительной задачи они рекомендуют разрешить Cacher использование нескольких ключей с использованием HashMap, а также использовать универсальные шаблоны для обеспечения большей гибкости.

Попробуйте изменить Cacher, чтобы она содержала хеш-карту, а не одно значение. Ключами хэш-карты будут значения arg, которые передаются, и значения хэш-карты будут результатом вызова закрытие на этом ключе. Вместо того, чтобы смотреть self.value напрямую имеет значение Some или None, функция значения будет искать arg в хэш-карту и вернуть значение, если оно присутствует. Если это не в настоящее время Cacher вызовет замыкание и сохранит полученное значение в хэш-карте, связанной с его значением arg.

Вторая проблема текущей реализации Cacher заключается в том, что она принимает только замыкания, которые принимают один параметр типа u32 и возвращают u32. Мы могли бы хотеть кэшировать результаты замыканий, которые принимают строку например, нарезать и вернуть usize значения. Чтобы решить эту проблему, попробуйте введение более общих параметров для повышения гибкости Cacher функциональность.

Мне удалось реализовать HashMap, однако при попытке заменить определение замыкания u32 универсальным типом и использовать его в качестве сигнатуры HashMap я столкнулся с проблемой.

use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::thread;
use std::time::Duration;

struct Cacher<'a, T>
where
    T: Fn(&'a u32) -> &'a u32,
{
    calculation: T,
    values: HashMap<&'a u32, &'a u32>,
}

impl<'a, T> Cacher<'a, T>
where
    T: Fn(&'a u32) -> &'a u32,
{
    fn new(calculation: T) -> Cacher<'a, T> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }

    fn values(&mut self, arg: &'a u32) -> &'a u32 {
        match self.values.entry(arg) {
            Entry::Occupied(e) => &*e.into_mut(),
            Entry::Vacant(e) => &*e.insert(&(self.calculation)(&arg)),
        }
    }
}

fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        &num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result.values(&intensity));
        println!("Next, do {} situps!", expensive_result.values(&intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.values(&intensity)
            );
        }
    }
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

Я попробовал K, V генерики, как показано ниже, и они жалуются, когда Expected one of 7 possible values here указывает на определение первого типа.

use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::hash::Hash;
use std::thread;
use std::time::Duration;

struct Cacher<'a, T: 'a, K: 'a, V: 'a>
where
    T: Fn(&'a K) -> &'a V,
    K: Hash + Eq,
{
    calculation: T,
    values: HashMap<&'a K, &'a V>,
}

impl<'a, T: 'a, K: 'a, V: 'a> Cacher<'a, T: 'a, K: 'a, V: 'a>
where
    T: Fn(&'a K) -> &'a V,
    K: Hash + Eq,
{
    fn new(calculation: T) -> Cacher<'a, T: 'a, K: 'a, V: 'a> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }

    fn values(&mut self, arg: &'a K) -> &'a V {
        match self.values.entry(arg) {
            Entry::Occupied(e) => &*e.into_mut(),
            Entry::Vacant(e) => &*e.insert(&(self.calculation)(&arg)),
        }
    }
}

fn generate_workout(intensity: u32, random_number: u32) {
    let mut expensive_result = Cacher::new(|num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        &num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_result.values(&intensity));
        println!("Next, do {} situps!", expensive_result.values(&intensity));
    } else {
        if random_number == 3 {
            println!("Take a break today! Remember to stay hydrated!");
        } else {
            println!(
                "Today, run for {} minutes!",
                expensive_result.values(&intensity)
            );
        }
    }
}

fn main() {
    let simulated_user_specified_value = 10;
    let simulated_random_number = 7;

    generate_workout(simulated_user_specified_value, simulated_random_number);
}

Приводит к следующей ошибке:

error: expected one of `!`, `(`, `+`, `,`, `::`, `<`, or `>`, found `:`
  --> src/main.rs:16:39
   |
16 | impl<'a, T: 'a, K: 'a, V: 'a> Cacher<T: 'a, K: 'a, V: 'a>
   |                                       ^ expected one of 7 possible tokens here

Является ли единственный способ добавить еще 2 дженерика (т.е. K, V) или есть способ повторно использовать один дженерик? Если 2 требуется, что я пропускаю выше?

Есть ли более идиоматический подход к решению этой проблемы? К сожалению, книга Rust не предлагает решения.

1 Ответ

0 голосов
/ 27 апреля 2018

Ваша реализация не компилируется, поскольку границы времени жизни должны объявляться только после impl:

impl<'a, T: 'a, K: 'a, V: 'a> Cacher<'a, T, K, V>
where
    T: Fn(&'a K) -> &'a V,
    K: Hash + Eq,
{
    fn new(calculation: T) -> Cacher<'a, T, K, V> {
        Cacher {
            calculation,
            values: HashMap::new(),
        }
    }
}

Хранение ссылок в HashMap подразумевает, что вы должны управлять временем жизни и гарантировать, что значения, на которые ссылается HashMap, изживут Cacher.

Другим подходом, который можно рассмотреть, может быть кэширование по значениям:

struct Cacher<T, K, V>
where
    T: Fn(K) -> V,
{
    calculation: T,
    value: HashMap<K, V>,
}

impl<T, K, V> Cacher<T, K, V>
where
    T: Fn(K) -> V,
    K: Hash + Eq + Clone
{
    fn new(calculation: T) -> Cacher<T, K, V> {
        Cacher {
            calculation,
            value: HashMap::new(),
        }
    }

    fn value(& mut self, arg: K) -> &V {    
        match self.value.entry(arg.clone()) {
            Entry::Occupied(v) => v.into_mut(),
            Entry::Vacant(v) =>   v.insert((self.calculation)(arg)),
        }
    }
}

Обратите внимание, что в этом решении я добавил ограничение, что K равно Clone

...