键值映射中的部分查找,其中键本身是键值映射
partial lookup in key-value map where key itself is a key-value map
假设我们有一个键值映射的数据结构,其中键本身又是一个键值映射。例如:
map<map<string,string>>, string>
现在,假设我们要查询此映射中与键的键值的特定子集匹配的所有顶级键/值。例:
map = { { "k1" : "v1", "k2 : "v2" } : "value1",
{ "k1" : "v3", "k2 : "v4" } : "value2",
{ "k1" : "v1", "k2 : "v5" } : "value3"
}
我们的查询是"给我所有键值,其中键包含{ "k1" : "v1" }
,它将返回第一个和第三个值。类似地,查询{ "k1" : "v3", "k2" : "v4" }
将返回所有同时具有k1=v3
和k2=v4
的键值,从而产生第二个值。显然,我们可以在每个查询上搜索完整的地图,但我正在寻找比这更有效的东西。
我环顾四周,但找不到高效、易于使用C++的解决方案。Boost multi_index 在查询键值对子集时似乎没有这种灵活性。
某些数据库有办法创建可以准确回答此类查询的索引。例如,Postgres具有GIN指数(广义倒排指数),可让您询问
SELECT * FROM table WHERE some_json_column @> '{"k1":"v1","k2":"v2"}'
-- returns all rows that have both k1=v1 and k2=v2
但是,我正在寻找一种没有数据库的解决方案,只是在C++。是否有任何库或数据结构可以完成这样的事情?如果没有,自定义实现上的一些指针?
我会继续使用数据库索引类比。在该类比中,索引搜索不使用通用的 k=v 类型搜索,而只是一个元组,其中包含构成索引的元素(通常是列)的值。然后,数据库将恢复为扫描不在索引中的其他 k=v 参数。
在这个类比中,您将有一个固定数量的键,这些键可以表示为数组或字符串(固定大小)。好消息是,在键上设置全局顺序是微不足道的,并且由于std::map::upper_bound
方法,在部分键之后立即找到迭代器也是微不足道的。
因此,获得完整的密钥是立竿见影的:只需使用find
、at
或operator []
提取它。获取部分键的所有元素仍然很简单:
- 查找从部分键上方开始的迭代器,并带有
upper_bound
- 在元素与部分键匹配时向前迭代
但这需要您将初始类型更改为std::map<std::array<string, N>, string>
您可以使用std::map<string, string>
作为输入值在此容器上构建 API,从中提取实际的完整或部分键,并如上所述进行迭代,仅保留与索引中不存在的 k,v 对匹配的元素。
您可以使用std::includes
来检查键映射是否包含另一个查询键值对映射。 我不确定如何避免检查每个键映射。也许其他答案有更好的主意。
template <typename MapOfMapsIt, typename QueryMapIt>
std::vector<MapOfMapsIt> query_keymap_contains(
MapOfMapsIt mom_fst,
MapOfMapsIt mom_lst,
QueryMapIt q_fst,
QueryMapIt q_lst)
{
std::vector<MapOfMapsIt> out;
for(; mom_fst != mom_lst; ++mom_fst)
{
const auto key_map = mom_fst->first;
if(std::includes(key_map.begin(), key_map.end(), q_fst, q_lst))
out.push_back(mom_fst);
}
return out;
}
用法:
typedef std::map<std::string, std::string> StrMap;
typedef std::map<StrMap, std::string> MapKeyMaps;
MapKeyMaps m = {{{{"k1", "v1"}, {"k2", "v2"}}, "value1"},
{{{"k1", "v3"}, {"k2", "v4"}}, "value2"},
{{{"k1", "v1"}, {"k2", "v5"}}, "value3"}};
StrMap q1 = {{"k1", "v1"}};
StrMap q2 = {{"k1", "v3"}, {"k2", "v4"}};
auto res1 = query_keymap_contains(m.begin(), m.end(), q1.begin(), q1.end());
auto res2 = query_keymap_contains(m.begin(), m.end(), q2.begin(), q2.end());
std::cout << "Query1: ";
for(auto i : res1) std::cout << i->second << " ";
std::cout << "nQuery2: ";
for(auto i : res2) std::cout << i->second << " ";
输出:
Query1: value1 value3
Query2: value2
现场示例
我相信不同方法的效率将取决于实际数据。但是,我会考虑为特定"kX","vY"
对的外部映射元素制作迭代器的"缓存",如下所示:
using M = std::map<std::map<std::string, std::string>, std::string>;
M m = {
{ { { "k1", "v1" }, { "k2", "v2" } }, "value1" },
{ { { "k1", "v3" }, { "k2", "v4" } }, "value2" },
{ { { "k1", "v1" }, { "k2", "v5" } }, "value3" }
};
std::map<M::key_type::value_type, std::vector<M::iterator>> cache;
for (auto it = m.begin(); it != m.end(); ++it)
for (const auto& kv : it->first)
cache[kv].push_back(it);
现在,您基本上需要获取所有搜索到的"kX","vY"
对,并为它们找到缓存迭代器的交集:
std::vector<M::key_type::value_type> find_list = { { "k1", "v1" }, { "k2", "v5" } };
std::vector<M::iterator> found;
if (find_list.size() > 0) {
auto it = find_list.begin();
std::copy(cache[*it].begin(), cache[*it].end(), std::back_inserter(found));
while (++it != find_list.end()) {
const auto& temp = cache[*it];
found.erase(std::remove_if(found.begin(), found.end(),
[&temp](const auto& e){ return std::find(temp.begin(), temp.end(), e) == temp.end(); } ),
found.end());
}
}
最终输出:
for (const auto& it : found)
std::cout << it->second << std::endl;
在这种情况下给出value3
。
现场演示:https://wandbox.org/permlink/S9Zp8yofSvjfLokc。
请注意,交集步骤的复杂性非常大,因为缓存的迭代器是未排序的。如果您改用指针,则可以对向量进行排序或将指针存储在地图中,这将使您能够更快地找到交叉点,例如,通过使用std::set_intersection
。
您可以使用有序查询对每个元素进行单次(部分)传递,并尽可能早地返回。从std::set_difference
中汲取灵感,我们想知道query
是否是data
的子集,这让我们可以选择外部映射的条目。
// Is the sorted range [first1, last1) a subset of the sorted range [first2, last2)
template<class InputIt1, class InputIt2>
bool is_subset(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
{
while (first1 != last1) {
if (first2 == last2) return false; // Reached the end of data with query still remaing
if (*first1 < *first2) {
return false; // didn't find this query element
} else {
if (! (*first2 < *first1)) {
++first1; // found this query element
}
++first2;
}
}
return true; // reached the end of query
}
// find every element of "map-of-maps" [first2, last2) for which the sorted range [first1, last1) is a subset of it's key
template<class InputIt1, class InputIt2, class OutputIt>
OutputIt query_data(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, OutputIt d_first)
{
auto item_matches = [=](auto & inner){ return is_subset(first1, last1, inner.first.begin(), inner.first.end()); };
return std::copy_if(first2, last2, d_first, item_matches);
}
std::map
被实现为具有O(nlgn)查找的平衡二叉树。相反,你需要的是作为哈希表实现的std::unordered_map
,即 O(1) 查找。
现在让我改写一下你的措辞,你想:
我们的查询是"给我所有键值,其中键包含 { "k1" : "v1" },它将返回第一个和第三个值。
翻译过来就是:
如果给出的键值对在内部映射中,请将其值交还给我。 本质上,您需要的是 std::unordered_map 擅长的双重查找。
这是一个代码尖刺,可以解决标准库的问题(不需要花哨的代码)
#include <iostream>
#include <unordered_map>
#include <string>
int main() {
using elemType = std::pair<std::string, std::string>;
using innerMap = std::unordered_map<std::string, std::string>;
using myMap = std::unordered_map<std::string, innerMap>;
auto table = myMap{ { "value1", { {"k1", "v1"}, {"k2", "v2"} } },
{ "value2", { {"k1", "v3"}, {"k2", "v4"} } },
{ "value3", { {"k1", "v1"}, {"k2", "v5"} } } };
//First we set-up a predicate lambda
auto printIfKeyValueFound = [](const myMap& tab, const elemType& query) {
// O(n) for the first table and O(1) lookup for each, O(n) total
for(const auto& el : tab) {
auto it = el.second.find(query.first);
if(it != el.second.end()) {
if(it->second == query.second) {
std::cout << "Element found: " << el.first << "n";
}
}
}
};
auto query = elemType{"k1", "v1"};
printIfKeyValueFound(table, query);
输出:值 3,值 1
对于任意大小的查询,您可以:
//First we set-up a predicate lambda
auto printIfKeyValueFound = [](const myMap& tab, const std::vector<elemType>& query) {
// O(n) for the first table and O(n) for the query O(1) search
// O(n^2) total
for(const auto& el : tab) {
bool found = true;
for(const auto& queryEl : query) {
auto it = el.second.find(queryEl.first);
if(it != el.second.end() && it->second != queryEl.second) {
found = false;
break;
}
}
if(found)
std::cout << el.first << "n";
}
};
auto query = std::vector<elemType>{ {"k1", "v1"}, {"k2", "v2"} };
输出值1
- 当键值是 std 向量时,为什么使用 at in C++ 访问映射值如此缓慢?
- C++ 映射的键/值的用户自定义名称?
- 更改多重映射容器中所有元素的键值
- 使用无序映射在STL中存储键值对
- 当值是地址C 时,如何使用键使用映射值
- 键值映射中的部分查找,其中键本身是键值映射
- 为什么要对 map::find 应用不存在的键将返回一个C++中第一个值映射大小的迭代器
- 在映射中插入键值,其中映射的值是向量对
- 打印出映射键值,其中键是结构变量 c++
- 将浮点值映射到一对键
- 如何在映射中找到上一个和下一个键/值
- 为什么用Spirit解析一个空行会在映射中产生一个空的键值对
- C++使用没有值,只有键的映射的字典
- 如何命名同时充当其他集合容器的B+树键/值映射集合
- 排序值C++映射具有多个键值的数据结构
- 如何将键值对引用从一个映射复制到同一类型的另一个映射
- 如何创建以字符作为键值并将字符串数组作为映射值的映射 - 数组必须具有不同的长度
- 存储桶键值映射
- 将std::string就地标记为键值映射
- 数据映射:从"any"类到泛型键值映射(C++)