Как вставить новый объект на карту, только если объект не существует на карте с помощью ржавчины?

Перевожу код C ++ в ржавчину. Вот исходный код C ++.

#include <map>
#include <string>
#include <cassert>
#include <iostream>

int main() {
    std::map<std::string, int> m {
        { "A", 1 },
        { "B", 2 },
        { "D", 4 },
    };
    // *1
    auto r = m.equal_range("C"); // *2
    if (r.first == r.second) {
        auto const& it = r.first;
        assert(it->first == "D");
        assert(it->second == 4);
        // Let's say creating the object to insert is high cost
        // so it should be created only if the element doesn't exist.
        // Creating the object at *1 is not acceptable because if the element exists,
        // then the created object isn't userd.
        //
        // `it` is hint iterator that point to insertion position.
        // If the object to isnert has the same key as the argument of equal_range (*2)
        // the time complexity is O(1).
        m.emplace_hint(it, "C", 3); 
    }
    for (auto const& kv : m) {
        std::cout << kv.first << ":" << kv.second << std::endl;
    }
}

Запускаемая демонстрация: https://wandbox.org/permlink/4eEZ2jY9kaOK9ru0

Это вставка шаблона, если он не существует.

Я хочу заархивировать две цели.

Один из них эффективно вставляет объект. Поиск объекта занимает O (logN) временную сложность. Я хочу вставить новый объект только в том случае, если объект не существует на карте. Если новый объект вставляется с самого начала, то для поиска позиции вставки требуется дополнительная стоимость O (logN). Исходный код C ++ использует it в качестве подсказки для вставки нового объекта.

Другой - создание нового объекта только в том случае, если объект с таким же ключом не существует на карте. Ведь создание объекта в реальности требует больших затрат. (Мой пример кода для пользователей std :: string и int value. Это просто пример.) Итак, я не хочу предварительно создавать объект для вставки в * 1.

Я прочитал документ BTreeMap. Но я не мог найти пути.

https://doc.rust-lang.org/std/collections/struct.BTreeMap.html

Есть ли хороший способ сделать это? Или есть какой-нибудь нестандартный контейнер (карта) для поддержки операции, которую я хочу сделать?


person Takatoshi Kondo    schedule 02.07.2020    source источник
comment
Вы рассматривали std::unordered_map вместо std::map? Тогда вопрос будет в том, насколько хороша хеш-функция, а не в O(n log n) времени поиска.   -  person PaulMcKenzie    schedule 02.07.2020
comment
std :: map :: try_emplace?   -  person Equod    schedule 02.07.2020
comment
@PaulMcKenzie, я бы хотел узнать способ ржавчины. Не C ++. Код - это исходный код, который я хочу перенести на ржавчину. Кстати, я думаю, время поиска - O (долгое время).   -  person Takatoshi Kondo    schedule 02.07.2020
comment
@Equod, полезно знать std :: map :: try_emplace. Исходный код C ++ такой же, как std :: map :: try_emplace. Спасибо. Но я хочу знать способ ржавчины.   -  person Takatoshi Kondo    schedule 02.07.2020
comment
@TakatoshiKondo Значит, в Rust нет встроенного класса хеш-таблицы? Вот почему не стоит делать построчные переводы с одного языка на другой. Более быстрый поиск выполняется с использованием хеш-таблиц с хорошей хеш-функцией. std::map не использует хеширование.   -  person PaulMcKenzie    schedule 02.07.2020
comment
@PaulMcKenzie, хэш или упорядоченный (на основе дерева) контейнер не является важной темой в этом вопросе. Если порядок важен, я не могу использовать хеш.   -  person Takatoshi Kondo    schedule 02.07.2020


Ответы (1)


Похоже, вам нужен Entry API?

В русификации вашего примера m.entry("C") вернет Entry перечисление, которое содержит информацию о том, присутствует запись или нет. Затем вы можете либо отправить явно, либо использовать один из высокоуровневых методов, например. BTreeMap::or_insert_with, который принимает функцию (и, таким образом, создает объект для ленивой вставки)

Таким образом, версия Rust будет примерно такой:

let mut m = BTreeMap::new();
m.insert("A", 1);
m.insert("B", 2);
m.insert("D", 4);

m.entry("C").or_insert_with(|| {
    3 // create expensive object here
});

for (k, v) in &m {
    println!("{}:{}", k, v);
}
person Masklinn    schedule 02.07.2020
comment
Спасибо ! Я нашел player_stats.entry("defence").or_insert_with(random_stat_buff); пример в doc.rust-lang.org/std/collections /struct.BTreeMap.html. Кажется, что or_insert_with () может принимать параметр функции. Пример random_stat_buff - это функция. Вызывается random_stat_buff, только если элемент не существует? Полагаю, что так. - person Takatoshi Kondo; 02.07.2020
comment
да. В этом вся суть, иначе вы можете просто использовать .or_insert, который берет реальный объект для вставки (и, таким образом, лучше для очень дешевого объекта) (или .or_default(), который автоматически создает и вставляет значение по умолчанию - при условии, что тип значения реализует Default) . - person Masklinn; 02.07.2020
comment
Также обратите внимание, если вам это нужно: все они возвращают изменяемую ссылку на значение записи, либо ту, что там была, либо ту, которую вы только что вставили. Это полезно, когда вы также хотите безоговорочно обновить запись (независимо от того, является ли она существующей или новой), например. m.entry(k).or_insert(0) += 1 вставит 0, если записи не было, тогда значение будет увеличиваться независимо. - person Masklinn; 02.07.2020
comment
Спасибо. Ваше решение идеальное. or_insert_with может выполнять своего рода ленивую инстанциацию. Мило. - person Takatoshi Kondo; 02.07.2020
comment
@TakatoshiKondo: Обратной стороной является то, что теперь, когда вы протестировали Entry API, вы отчаиваетесь, используя любую карту, на которой его нет ... - person Matthieu M.; 02.07.2020