如何使用 Boost::multi_index_container 检查枚举是否已在特定位上设置

问题描述 投票:0回答:2

我想知道是否可以修改以下代码片段以删除 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";    
    }
}

有什么办法可以实现吗? 预先感谢!

c++ boost boost-multi-index
2个回答
1
投票

索引的运行原理是存在可以加快查找速度的排序。由于您的级别形成一个独立的集合,因此没有这样的顺序。

您最希望实现的目标就是拥有独立的索引:

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<>
    您可以使实际容器元素成为其他集合的一部分
  • 考虑间隔容器
  • 更高级的是,使用空间索引(例如rtree)可以让你查询多维正交空间;我认为这里不太有趣,因为所有轴都只是 [true,false],但我仍然认为我应该提到它。

过去答案中的一些鼓舞人心的例子:在interval_map的codomain中进行选择boost::multi_index_container,在容器内对std::set进行操作

  • 最后它让我想起了这个 使用 multi_index 索引多个向量字段,我建议将多对多索引设置为外部。这里有趣的一点是将字符串(如您的工作职位)动态映射到积分域的技术

0
投票

除了@sehe 的出色答案之外,如果您在范围内,您可以有这样的东西:

现场 Coliru 演示

#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";    
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.