C ++比较器函数指定地图的特定键顺序?

Posted

技术标签:

【中文标题】C ++比较器函数指定地图的特定键顺序?【英文标题】:C++ Comparator function to specifiy specific key ordering for map? 【发布时间】:2015-10-26 03:55:07 【问题描述】:

问题:您将如何编写一个函数来为映射构造函数执行自定义比较器以创建键的特定顺序?

我有以下字符串键:

"a", "d", "i", "n", "ns", "ne", "vl", "rr"

我正在使用映射来为这些键写入值。但我需要按照上述确切顺序订购它们。 Map 通常会创建订单:

a d i n ne ns rr vl

如何编写一个比较器函数,将其发送到地图构造函数,以便保持这个顺序?

这就是我现在使用的方法

vector<pair<string, string>>(  "a","" , "d","" , "i","" , "n","" , "ns","" , "ne","" , "vl","" , "rr","" ,  "",""  );

...然后我做了一堆 find_if 调用。如果找到,我将值添加到该对中。如果没有,我创建一个新的对(在开始时)。我一开始就这样做,所以如果密钥不存在,我可以返回“”。好吧,除了我需要对上面没有列出的键进行排序之外,这一切都有效,因为可以添加任何键。此外,当我尝试添加一个不存在的密钥时,我的程序崩溃了。必须调试...但是这种方式令人困惑。使用带有一些特定排序的地图将非常适合我的需求(在运行时添加的任何键也应该通过默认比较进行排序以保持顺序。但是可以对这些“未知”(未来添加的)键进行排序) .

我“想要这个”的原因(在 cmets 中询问):我需要在程序的最后一步输出如下字符串:

",a=legato,d=dn,i=12,n=A3" 

等等。它们需要按特定顺序排列,因为我稍后需要使用正则表达式(在单独的程序中)来操作这个字符串。顺序对于指定的键很重要。对于未指定的键,顺序也必须是固定的……因为正则表达式。

【问题讨论】:

为什么要这样做?通常您希望比较器功能简单快速。如果我是你,我会考虑保留vector&lt;pair&lt;string,string&gt;&gt; 结构,同时制作一个map&lt;string, unsigned int&gt; 将字符串映射到它们的索引并使用它进行快速查找。或者,如果您不想担心保持两个结构一致的细节,也可以使用boost::multiindex::container 【参考方案1】:

您将 map 用于排序键的比较器指定为模板参数。所以你的地图声明看起来像:

std::map<string, value_type, my_comparator> my_map;

比较器是您可以定义的,它接受两个key_type 参数ab,然后如果ab 之前返回true(否则返回false)。这方面的一个例子是:

struct my_comparator 
    bool operator()(const string &a, const string &b) 
        // Return true if a comes before b.
    

要实现您在问题中指定的顺序,您可以按照以下方式进行操作。我使用std::tuple 来确保满足严格的订购标准。

bool operator()(const string &a, const string &b) 
    auto a_tuple = std::make_tuple(a == "a", a == "d", a == "i", ..., a);
    auto b_tuple = std::make_tuple(b == "a", b == "d", a == "i", ..., a);

    return a_tuple < b_tuple;

由于在元组的最后一个元素中有 ab,如果它与您的预定义字符串之一不匹配,它将按这些进行排序。

【讨论】:

我刚刚意识到的另一件事...我不知道这是否可行,但是如果我将地图转换为矢量,然后使用此比较器进行排序,那意味着我只使用这个导出字符串时进行一次复杂比较,而不是每次在地图中添加或删除键时, 只是不确定如何将其转换为排序比较器,抱歉我知道我问了很多(我知道如何使用排序和基本的 lambda 函数)。 自动比较器 = [](const pair &a, const pair &b) ... 为了正确起见a_tuple &lt; b_tuple; 原来我需要&gt; 才能得到正确的顺序。 感谢 ajshort,实现了它(未经测试,但没有 IDE 错误)。只想说我真的在学习。我正在查看这里的所有示例,我开始更好地理解不同类的工作方式、如何访问元素等。【参考方案2】:

查看对解释为什么需要此问题的问题的编辑,我建议改为使用具有默认排序的map,然后在您想要输出值时咨询不同的列表:

std::vector<std::string> ordering =  "a", "d", "i", "n", "ns", "ne", "vl", "rr" ;
for (const auto& key : ordering)
    ...output the_map[key]...

无论如何,如果您坚持使用不同排序的map,您可以根据需要进行特殊情况:

struct WeirdLess

    bool operator<(const std:string& lhs, const std::string& rhs) const
    
        if (lhs == "ne" && rhs == "ns" ||
            lhs == "ns" && rhs == "ne" ||
            lhs == "rr" && rhs == "vl ||
            lhs == "vl" && rhs == "rr")
            return rhs < lhs;
        return lhs < rhs;
    
;

std::map<std::string, std::string, WeirdLess> my_map;

不过,如果您最终在 map 中有数百个条目并且其中一半需要特殊的大小写,那么它的可扩展性并不高。尝试提出一些逻辑来捕捉为什么特定字符串更早 - 或者至少是识别它们的启发式方法 - 这可能是一个更好的主意。例如:

    bool operator<(const std:string& lhs, const std::string& rhs) const
    
        if (lhs.size() == 2 && rhs.size() == 2 && lhs[0] == rhs[0])
            return rhs < lhs;
        return lhs < rhs;
    

【讨论】:

operator&lt; 我认为应该是 bool operator() "vl 必须是 "vl" std:string&amp;std::string&amp; 所以你刚刚在你的答案中添加了“for each loop”。但是,我不知道这是否可行。 for each 循环只会在我指定的键之后结束,但我还需要获取未指定的键。 @ElanHickler:很公平 - 问题并没有说清楚:我在其中使用“未指定的键”来表示在预输出期间未明确设置的任何键集列表处理,但如果您不确定键的整体列表,那么这确实是一个问题。【参考方案3】:

这是您为 map 创建比较器函数以为多个键创建特定排序顺序的方法。

其他答案的启发使我能够编写自己的答案,以尽可能最好的方式准确地解决我的问题 (IMO)。

运行:https://ideone.com/7H5CZg

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
using namespace std;

// create vector holding desired ordering
vector<string> order( "a", "d", "i", "n", "ns", "ne", "vl", "rr" );

// create sort function for map   
struct my_comparator 
    bool operator()(const string &s1, const string &s2) 
        // find() - it.begin() gives you the index number. 
        int a = find(order.begin(), order.end(), s1) - order.begin();
        int b = find(order.begin(), order.end(), s2) - order.begin();
        return a < b; // lower index < higher index
    
;

int main() 
    // create the map
    map<string,string, my_comparator> MyMap =   "a","legato" ,  "i","3" ,  "vl","3" ,  "rr","2"  ;

    // output map
    for (auto& it : MyMap) cout << it.first << "=" << it.second << "  ";

    return 0;

输出:a=legato i=3 vl=3 rr=2


更新:这通常会更快,它使用地图而不是矢量来查找键顺序。

您也可以将这种处理方式应用于其他答案。

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
using namespace std;

// using a map is faster in most cases (with more keys and such)
map<string, int> maporder( "a", 1, "d", 2, "i", 3, "n", 4, "ns", 5, "ne", 6, "vl", 7, "rr", 8 );

// create sort function for map   
struct mapcomp 
    bool operator()(const string &s1, const string &s2) 
         // compares ints by looking them up with the input string
        return maporder[s1] < maporder[s2];
    
;

int main() 
    // create the map
    map<string,string, mapcomp> MyMap =   "a","legato" ,  "i","3" ,  "vl","3" ,  "rr","2"  ;

    // output map
    for (auto& it : MyMap) cout << it.first << "=" << it.second << "  ";

    return 0;

【讨论】:

【参考方案4】:

根据持有键顺序的向量对成对向量进行排序

这不是原始问题的答案,而是所描述问题的解决方案。有关此概念的更快版本,请参阅我的其他答案。

运行:https://ideone.com/8ICRwZ

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <algorithm>
using namespace std;

// create vector holding desired ordering
vector<string> order( "a", "d", "i", "n", "ns", "ne", "vl", "rr" );

// create sort function for vector of pairs
bool comparator(const pair<string, string> &s1, const pair<string, string> &s2) 
    // find() - it.begin() gives you the index number. 
    int a = find(order.begin(), order.end(), s1.first) - order.begin();
    int b = find(order.begin(), order.end(), s2.first) - order.begin();
    return a < b; // lower index < higher index
;

int main() 
    // create the map
    map<string,string> MyMap =   "a","legato" ,  "vl","3" , "i", "3",  "rr","2"  ;

    // convert map into vector of pairs
    vector<pair<string,string>> vp;
    for (auto& it : MyMap) vp.push_back( it.first, it.second );

    // sort the vector
    sort(vp.begin(), vp.end(), comparator);

    // put the vector pairs into a string format
    for (auto& it : vp) cout << it.first << "=" << it.second << " ";

    return 0;

输出:a=legato i=3 vl=3 rr=2

【讨论】:

我刚刚在 Visual Studio 中使用元组(ajshort 答案)与我使用向量的答案进行了测试。 Tuple 在调试模式下快 2 倍。元组在发布模式下要慢一些。在 ideone 中,元组要慢得多……我不知道代码分析是如何工作的……这是 ideone 版本:ideone.com/KSccm1【参考方案5】:

此函数根据输入向量对集合进行排序

一位好朋友和伟大的程序员为我写了这个答案。根据 ideone.com,这是最有效的解决方案。这不是原始问题的答案,而是实际问题的答案。

//first param is the container you want to sort
//second param is the container that holds the order of things
vector<pair<string,string>> KeySort(map<string,string> s, const vector<string>& order)
    vector<pair<string,string>> res;
    for (const auto& it : order) 
        auto needle = s.find(it);
        if (needle != s.end()) 
            res.emplace_back(move(*needle));
            s.erase(needle);
        
    
    for (auto&& it : s) res.emplace_back(move(it));
    return res;

ideone 基准测试:https://ideone.com/GYsiM8

ideone 示例使用:https://ideone.com/eAU5US

// create vector holding desired ordering
vector<string> ord( "a", "d", "i", "n", "ns", "ne", "vl", "rr" );

// create the map
map<string,string> MyMap =  "a","legato", "vl","4", 
"i","2", "rr","3", "z","unspecified1", "b","unspecified2";

// sort the vector
vector<pair<string,string>> out = KeySort(MyMap, ord);

输出:a=legato i=2 vl=4 rr=3 b=unspecified2 z=unspecified1

【讨论】:

以上是关于C ++比较器函数指定地图的特定键顺序?的主要内容,如果未能解决你的问题,请参考以下文章

python怎么对字典进行排序

C语言中对字符串进行操作的标准库函数都有哪些

玩转C语言库函数。包含strlenstrcpystrcmpstrcatmemcmpmemmovestrstr等等常用的库函数

玩转C语言库函数。包含strlenstrcpystrcmpstrcatmemcmpmemmovestrstr等等常用的库函数

编写一个函数在指定的字符串中查找指定字符出现的次数

python 字典排序