我得到this example关于在C ++中实现泛型memoization。但是,正如有人在此评论中发出通知,原始代码进行了2次查找,而下面的代码只有一次。
template <typename ReturnType, typename... Args>
std::function<ReturnType (Args...)> memoize(std::function<ReturnType (Args...)> func)
{
std::map<std::tuple<Args...>, ReturnType> cache;
return ([=](Args... args) mutable {
std::tuple<Args...> t(args...);
auto range = cache.equal_range(t);
if (range.first != range.second) return (*range.first).second;
return (*cache.insert(range.first, func(args...))).second;
});
}
有人注意到使用unordered_map可能会有更好的表现。但是我read:
对于通过子集的范围迭代,它们通常效率较低 他们的元素。我不明白为什么范围操作会更少 有效的,如果上述情况是这些情况之一(因为我们使用 范围)?
答案 0 :(得分:1)
当有人在此评论中发出通知时,原始代码会进行2次查找,而下面的代码只会进行一次
好的,所以你试图使用提示的insert
来避免插入点的冗余对数复杂性搜索。您仍然遇到与其他问题相同的错误,您的代码可能应该如下所示:
cache.insert(range.first, std::make_pair(t, func(args...)));
// ^hint ^value_type
(这是链接文档中的重载#4)。
你可以完全没有第一次查找:如果密钥存在,insert
只返回现有元素的迭代器,所以你可以用乐观插入来写它 - 这是否更好取决于成本default-construct然后分配你的ReturnType:
auto result = cache.insert(make_pair(t, ReturnType{}));
if (result.second) {
// insertion succeeded so the value wasn't cached already
result.first->second = func(args...);
}
return result.first->second;
...使用unordered_map可能会有更好的表现......
std::map
查找/插入具有对数复杂度的比例,std::unordered_map
以恒定复杂度进行比例缩放。实际上哪个更好取决于你有多少条目,以及其他因素,你需要进行分析以确定。
... [unordered_map]对范围迭代的效率较低......
好吧,如果你避免equal_range
,你根本不需要范围迭代。
查看您实际使用的操作,并了解哪种数据结构非常适合。对于简单的查找和插入,这些都是你真正需要的,unordered_map
可能很好。如果您关心密钥的相对排序,那么std::map
会更好。
两种结构对密钥的要求也不同:map
需要排序(operator<
或自定义比较器),而unordered_map
需要定义良好的散列函数{{1或者自定义谓词。
首先,我不确定operator==
是否支持std::hash
,因此您可能需要提供自定义哈希来使用std::tuple
。