【问题标题】:Groupping of data with std::map使用 std::map 对数据进行分组
【发布时间】:2022-08-05 20:52:53
【问题描述】:

问题:

需要对逻辑分组的值进行一些存储。

类似于以下简化表的内容:

因此,我们有一些类似数据库的表,其值可以通过组和值 id 进行标识。

要求:

  • 快速(或多或少)搜索;
  • 可以简单地删除整个组、检索或枚举组值和其他内容,例如:
storage.delete(group);
storage.has(group);
storage.get(group) -> array<value>; // It\'ll be probably 2 O(log n) operations + one O(m), where m number of elements in the group, because we\'ll

    标签: c++ stl


    【解决方案1】:

    可能的解决方案可以基于std::map自定义键有几个重载的运算符。

    Example on Compiler Explorer

    定义:

    #include <iostream>
    #include <map>
    #include <vector>
    
    template<typename GroupId, typename ValueId>
    struct GroupKey
    {
        GroupId groupId{};
        ValueId valueId{};
    
        bool operator<(const GroupId &groupId) const
        {
            return this->groupId < groupId;
        }
    
        friend bool operator<(GroupId groupId, const GroupKey &key)
        {
            return groupId < key.groupId;
        }
    
        bool operator<(const GroupKey &other) const
        {
            if (groupId == other.groupId)
                return valueId < other.valueId;
    
            return groupId < other.groupId;
        }
    };
    
    enum Group : uint16_t
    {
        GroupOne = 1,
        GroupTwo,
        GroupThree,
        GroupFour
    };
    
    enum Value : uint16_t
    {
        One = 1,
        Two,
        Three
    };
    
    // NOTE: here it's necessery to use "transparent comparator".
    // See notes below in the answer
    using Values = std::map<GroupKey<Group, Value>, int, std::less<>>;
    using Groups = std::vector<Group>;
    
    Values values {
        {{GroupOne, Two}, 1},
        {{GroupOne, Three}, 2},
        {{GroupTwo, One}, 3},
        {{GroupTwo, Two}, 4},
        {{GroupThree, Three}, 5},
        {{GroupThree, Two}, 6},
        {{GroupThree, One}, 7},
    };
    

    测试功能:

    bool hasGroup(Group group)
    {
        return values.find(group) != values.cend();
    }
    
    bool hasValue(Group group, Value value)
    {
        return values.find({group, value}) != values.end();
    }
    
    Groups getGroups()
    {
        Groups groups;
        if (values.empty())
            return groups;
    
        auto cit = values.cbegin();
        auto cend = values.cend();
        do {
            groups.push_back(cit->first.groupId);
        } while ((cit = values.upper_bound(cit->first.groupId)) != cend);
    
        return groups;
    }
    
    int get(Group groupId, Value id, bool *isPresent = nullptr)
    {
        auto it = values.find({groupId, id});
        bool present = it != values.end();
    
        if (isPresent)
            *isPresent = present;
    
        return present ? it->second : int{};
    }
    
    bool insert(Group groupId, Value id, int value)
    {
        if (!hasValue(groupId, id)) {
            values[{groupId, id}] = value;
    
            return true;
        }
    
        return false;
    }
    
    void update(Group groupId, Value id, Value value)
    {
        // As we know, if here key is not present,
        // it'll be inserted into the map
        values[{groupId, id}] = value;
    }
    
    // Remove group (and all values that are releated this group)
    void remove(Group groupId)
    {
        auto lower = values.lower_bound(groupId);
        if (lower != values.cend())
            values.erase(lower, values.upper_bound(groupId));
    }
    
    void remove(Group groupId, Value id)
    {
        values.erase({groupId, id});
    }
    

    测试:

    int main()
    {
    //  {{GroupOne, Two}, 1},
    //  {{GroupOne, Three}, 2},
    //  {{GroupTwo, One}, 3},
    //  {{GroupTwo, Two}, 4},
    //  {{GroupThree, Three}, 5},
    //  {{GroupThree, Two}, 6},
    //  {{GroupThree, One}, 7},
    
        // Tests
        std::cout << "Has GroupFour: " << hasGroup(GroupFour) << '\n'; // expected: false
        std::cout << "Has GroupOne: " << hasGroup(GroupOne) << '\n';   // expected: true
    
        std::cout << "Has One in GroupOne: " << hasValue(GroupOne, One) << '\n'; // expected: false
        std::cout << "Has Two in GroupTwo: " << hasValue(GroupTwo, Two) << '\n'; // expected: true
    
        auto groupsPrinter = [](const Groups &groups) {
            std::cout << "Groups in the storage: ";
            for (const auto &group : groups)
                std::cout << group << ' ';
    
            std::cout << std::endl;
        };
    
        groupsPrinter(getGroups()); // expected: 1(GroupOne), 2(GroupTwo), 3(GroupThree)
    
        std::cout << "Inster GroupFour, One: " << insert(GroupFour, One, 8) << '\n';
        std::cout << "Has One in GroupFour: " << hasValue(GroupFour, One) << '\n'; // expected: true
    
        groupsPrinter(getGroups()); // expected: 1(GroupOne), 2(GroupTwo), 3(GroupThree), 4(GroupFour)
    
        remove(GroupOne);
    
        std::cout << "Has GroupOne: " << hasGroup(GroupOne) << '\n';   // expected: false
        std::cout << "Has Two in GroupOne: " << hasValue(GroupOne, One) << '\n'; // expected: false
    
        groupsPrinter(getGroups()); // expected: 2(GroupTwo), 3(GroupThree), 4(GroupFour)
    
        remove(GroupFour, One);
    
        std::cout << "Has GroupFour: " << hasGroup(GroupFour) << '\n'; // expected: false
        std::cout << "Has One in GroupFour: " << hasValue(GroupOne, One) << '\n'; // expected: false
    
        groupsPrinter(getGroups()); // expected: 2(GroupTwo), 3(GroupThree)
    
        return 0;
    }
    

    输出:

    Has GroupFour: 0
    Has GroupOne: 1
    Has One in GroupOne: 0
    Has Two in GroupTwo: 1
    Groups in the storage: 1 2 3 
    Inster GroupFour, One: 1
    Has One in GroupFour: 1
    Groups in the storage: 1 2 3 4 
    Has GroupOne: 0
    Has Two in GroupOne: 0
    Groups in the storage: 2 3 4 
    Has GroupFour: 0
    Has One in GroupFour: 0
    Groups in the storage: 2 3 
    

    一些注意事项:

    有必要使用“透明比较器”,在我们的例子中,它是 std::less&lt;&gt; 作为值定义中的第三个模板参数。

    为了更好地理解,让我们看一下std::mapfind() 方法的定义(在我使用的STL 实现中):

    因此,为了能够使用不完全是Values::key_type 的类型调用find,或者换句话说,在我们的例子中,在以GroupId 作为参数的GroupKey 中调用operator&lt;() 重载。

    第二点是一组operator&lt;()

    1. map::lower_bound() 方法需要第一个bool operator&lt;(const GroupId &amp;groupId) const
    2. 第二个friend bool operator&lt;(GroupId groupId, const GroupKey &amp;key)map::upper_bound()
    3. 第三个bool operator&lt;(const GroupKey &amp;other) const by std::map 本身,因为map::key_type 必须支持&lt; 更少的操作。

      就我而言,这 3 个是使其工作所必需的。

      最后一点是关于storage.get(group) -&gt; array&lt;value&gt;; 的要求,上面的示例中没有实现它,但可以用与Groups getGroups() 相同的方式完成。

      我们可以使用upperlower 边界并在它们之间进行迭代以获取与组相关的值。它应该给我们两个 O(log n) 操作来获得边界 + 一个 O(m) 在它们之间进行迭代。

      P.S.:我尽量保持示例简单,代码肯定可以改进,希望这个想法对某人有所帮助。

    【讨论】:

      猜你喜欢
      • 2023-03-29
      • 1970-01-01
      • 2013-06-27
      • 2018-09-05
      • 1970-01-01
      • 1970-01-01
      • 2019-05-24
      相关资源
      最近更新 更多