我想知道是否可以修改以下代码片段以删除 rv::filter 并使用 boost::multi_index 代替。
我有一个结构体 Person,其中包含定义为枚举的名字、姓氏、工作职位和访问级别。一个人可以访问“Level1”、“Level2”、Level3”的任意组合,在代码中表示为 AccessLevel::Level1 | AccessLevel::Level2 等。
我想找到所有工作职位等于“开发人员”并且有权访问Level1的人。 我通过先找到开发人员,然后按访问级别过滤它来解决这个问题。
但我想知道是否可以以某种方式修改 boost::multi_index_key 以获得相同的结果。
当我使用 std::tuple("developer", AccessLevel::Level1) 调用 equal_range 时,它仅返回具有 AccessLevel1 的开发人员(这是预期结果,因为 AccessLevel::Level1 | AccessLevel::Level2 是不同的值)。
#include <string>
#include <ranges>
#include <iostream>
#include <boost/multi_index/key.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
namespace bmi = boost::multi_index;
namespace rv = std::ranges::views;
enum AccessLevel
{
Level1 = 1 << 0,
Level2 = 1 << 1,
Level3 = 1 << 2,
};
AccessLevel operator|(AccessLevel lhs, AccessLevel rhs)
{
return static_cast<AccessLevel>(static_cast<int>(lhs) | static_cast<int>(rhs));
}
AccessLevel operator&(AccessLevel& lhs, AccessLevel rhs)
{
return static_cast<AccessLevel>(static_cast<int>(lhs) & static_cast<int>(rhs));
}
struct Person
{
std::string firstName;
std::string lastName;
std::string jobPosition;
AccessLevel accessLevel;
};
struct JobPositionTag {};
using Persons = bmi::multi_index_container<
Person,
bmi::indexed_by<
bmi::ordered_non_unique<
bmi::tag<JobPositionTag>,
bmi::key<&Person::jobPosition>
>
>
>;
int main()
{
Persons m_persons;
m_persons.insert({"John", "Johnovsky", "developer", AccessLevel::Level1 | AccessLevel::Level2 | AccessLevel::Level3 });
m_persons.insert({"Shrek", "Ogr", "developer", AccessLevel::Level2 });
m_persons.insert({"Foo", "Bar", "PO", AccessLevel::Level1});
m_persons.insert({"Hackerman", "", "developer", AccessLevel::Level1 });
auto& byJobPosition = m_persons.get<JobPositionTag>();
auto [beginIt, endIt] = byJobPosition.equal_range("developer");
auto filtered = boost::make_iterator_range(beginIt, endIt)
| rv::filter([](auto&& person) { return person.accessLevel & AccessLevel::Level1; });
for (auto&& dev : filtered)
{
std::cout << dev.firstName << " " << dev.lastName << " : " << dev.jobPosition << "\n";
}
}
有什么办法可以实现吗? 预先感谢!
索引的运行原理是存在可以加快查找速度的排序。由于您的级别形成一个独立的集合,因此没有这样的顺序。
您最希望实现的目标就是拥有独立的索引:
template <AccessLevel l> constexpr bool isLevel(Person const& p) { return p.accessLevel & l; }
using Persons = bmi::multi_index_container<
Person,
bmi::indexed_by< //
bmi::ordered_non_unique<bmi::tag<struct ByPos>, bmi::key<&Person::jobPosition>>,
bmi::ordered_non_unique<bmi::tag<struct ByLevel1>, bmi::key<isLevel<Level1>>>, //
bmi::ordered_non_unique<bmi::tag<struct ByLevel2>, bmi::key<isLevel<Level2>>>, //
bmi::ordered_non_unique<bmi::tag<struct ByLevel3>, bmi::key<isLevel<Level3>>> //
>>;
但是,这就留下了先按哪个顺序查询的问题:
Persons m_persons{
{"John", "Johnovsky", "developer", Level1 | Level2 | Level3},
{"Shrek", "Ogr", "developer", Level2},
{"Foo", "Bar", "PO", Level1},
{"Hackerman", "", "developer", Level1},
};
auto& byPos = m_persons.get<ByPos>();
auto& byL1 = m_persons.get<ByLevel1>();
fmt::print("by pos first {}\n",
make_iterator_range(byPos.equal_range("developer")) | rv::filter(isLevel<Level1>));
fmt::print("by lvl first {}\n",
make_iterator_range(byL1.equal_range(true)) |
rv::filter([](auto&& person) { return person.jobPosition == "developer"; }));
印刷
by pos first [{Hackerman '': developer}, {John 'Johnovsky': developer}]
by lvl first [{Hackerman '': developer}, {John 'Johnovsky': developer}]
可能有大量关于如何解决此类优化问题的科学知识(因为它是范式数据库表示的核心),但我并不完全了解。我主要建议根据您的实际需求来衡量性能。
名称“级别”意味着“等级”的取代,名称
Level1
/Level2
/Level3
也暗示潜在的顺序。如果其中一个级别实际上更重要,那么您可以按它们的整数值对它们进行排序,或者考虑使用复合键:
using Persons = bmi::multi_index_container<
Person,
bmi::indexed_by<bmi::ordered_non_unique<bmi::tag<struct ByPos>, bmi::key<&Person::jobPosition>>,
bmi::ordered_non_unique<bmi::tag<struct ByLevel>,
bmi::key<isLevel<Level1>, isLevel<Level2>, isLevel<Level3>>>>>;
导致Live:
fmt::print("by lvl first {}\n",
make_iterator_range(byLev.equal_range(true)) |
rv::filter([](auto&& person) { return person.jobPosition == "developer"; }));
当然,这仍然留下了需要后置过滤的情况。此外,如果排名重要性顺序实际上是颠倒的,则对于此特定查询将无济于事。
这种问题经常出现。您可能需要考虑不同的方法:
boost::intrusive::set<>
您可以使实际容器元素成为其他集合的一部分过去答案中的一些鼓舞人心的例子:在interval_map的codomain中进行选择,boost::multi_index_container,在容器内对std::set进行操作
除了@sehe 的出色答案之外,如果您在范围内,您可以有这样的东西:
#include <array>
#include <string>
#include <ranges>
#include <iostream>
#include <boost/multi_index/key.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
namespace bmi = boost::multi_index;
namespace rv = std::ranges::views;
enum AccessLevel
{
Level1 = 1 << 0,
Level2 = 1 << 1,
Level3 = 1 << 2,
};
AccessLevel operator|(AccessLevel lhs, AccessLevel rhs)
{
return static_cast<AccessLevel>(static_cast<int>(lhs) | static_cast<int>(rhs));
}
AccessLevel operator&(AccessLevel& lhs, AccessLevel rhs)
{
return static_cast<AccessLevel>(static_cast<int>(lhs) & static_cast<int>(rhs));
}
struct Person
{
std::string firstName;
std::string lastName;
std::string jobPosition;
AccessLevel accessLevel;
int getAccessLevel() const { return accessLevel; }
};
struct JobPositionTag {};
using Persons = bmi::multi_index_container<
Person,
bmi::indexed_by<
bmi::ordered_non_unique<
bmi::tag<JobPositionTag>,
bmi::key<&Person::jobPosition, &Person::getAccessLevel>
>
>
>;
auto JobPositionsWithLevelAccess(Persons& persons, const std::string& position, AccessLevel level)
{
static constexpr auto combinedLevels = [] {
std::array<int, 8> res;
std::size_t i = 0;
for(auto b2: {false, true}) {
for(auto b1: {false, true}) {
for(auto b0: {false, true}) {
res[i++] =
(b0 * AccessLevel::Level1) |
(b1 * AccessLevel::Level2) |
(b2 * AccessLevel::Level3);
}
}
}
return res;
}();
auto& byJobPosition = persons.get<JobPositionTag>();
return
combinedLevels |
rv::transform([&, pos=position, lv=level](int combinedLevel) {
if (lv & combinedLevel){
auto [beginIt, endIt] = byJobPosition.equal_range(std::make_tuple(pos, combinedLevel));
return boost::make_iterator_range(beginIt, endIt);
}
else {
return boost::make_iterator_range(byJobPosition.end(), byJobPosition.end());
}
}) |
rv::join;
}
int main()
{
Persons m_persons;
m_persons.insert({"John", "Johnovsky", "developer", AccessLevel::Level1 | AccessLevel::Level2 | AccessLevel::Level3 });
m_persons.insert({"Shrek", "Ogr", "developer", AccessLevel::Level2 });
m_persons.insert({"Foo", "Bar", "PO", AccessLevel::Level1});
m_persons.insert({"Hackerman", "", "developer", AccessLevel::Level1 });
auto filtered = JobPositionsWithLevelAccess(m_persons, "developer", AccessLevel::Level1);
for (auto&& dev : filtered)
{
std::cout << dev.firstName << " " << dev.lastName << " : " << dev.jobPosition << "\n";
}
}