map的自定义排序

1、map按照value排序

首先想到的是利用stl中的sort方法,但sort方法只能只能对线性容器进行排序(vector,list,deque),对于map这种关联型容器

,会破坏数据结构,可以迂回下,把map中的元素放到vector中,并且自定义容器中元素的比较方法。

#include<string>
#include<map>
#include<vector>
#include<iostream>
#include<utility>
#include<algorithm>
//字符串分割函数
std::vector<std::string> split(std::string str, std::string pattern)
{
    std::string::size_type pos;
    std::vector<std::string> result;
    str += pattern;//扩展字符串以方便操作
    int size = str.size();
    for (int i = 0; i < size; i++)
    {
        pos = str.find(pattern, i);
        if (pos < size)
        {
            std::string s = str.substr(i, pos - i);
            result.push_back(s);
            i = pos + pattern.size() - 1;
        }
    }
    return result;
}
int cmp(const std::pair<std::string,int> &pairIt1,const std::pair<std::string,int> &pairIt2)
{
    return pairIt1.second > pairIt2.second;
}
int main(void)
{
    std::string str_input;
    std::map<std::string,int> map_result;
    std::vector<std::string> split_result;
    std::vector< std::pair<std::string,int> > vector_sort;
    std::string tmp(",");
    getline(std::cin,str_input);
    std::cout << str_input << std::endl;
    split_result = split(str_input,tmp);
    int size = split_result.size();
    for(int i=0;i < size;i++)
    {
        map_result[split_result[i]]++;
        std::cout << split_result[i] << std::endl;
    }
    std::map<std::string,int>::iterator obj = map_result.begin();
    std::map<std::string,int>::iterator end = map_result.end();
    for( ; obj!=end; obj++)
    {
        vector_sort.push_back(*obj);
        std::cout << obj->second << std::endl;
    }
    sort(vector_sort.begin(),vector_sort.end(),cmp);
    size = vector_sort.size();
    for(int i = 0; i < size; i++)
    {
        std::cout <<vector_sort[i].first << " " <<             
         vector_sort[i].second << std::endl;
    }
    return 0;
}                    
 

 2、map按照key排序

首先map的模板定义为:

tempate <tepename _Key,typename _TP,typename _Compare=std::less<key>,
typename _Alloc=std::allocator<std::pair<const _Key,_Ip>>>
class map{
public:
   typedef _Key key_type;
   typedef _Ip mapped_type;
   typedef std::pair<const _Key,_Ip> 
   typedef _Compare key_compare;
   typedef _Alloc allocator_type;     
};

可以看到map类模板有4个模板参数:_Key,_Tp,_Compare,_Alloc,其中_Compare与_Alloc已经有了默认参数。 

其中less和greater是两个关系类仿函数,可以用来指定key的顺序,模板如下:

备注:binary_function可以作为一个二元函数对象的基类,它只定义了参数和返回值的类型,本身并不重载()操作符,这个任务应该交由派生类去完成。

template <class T> struct less : binary_function <T,T,bool> {  
  bool operator() (const T& x, const T& y) const  
    {return x<y;}  
};
template <class T> struct greater : binary_function <T,T,bool> {  
  bool operator() (const T& x, const T& y) const  
    {return x>y;}  
};  

结论:

所以当我们打算定义map的key排序时,就必须在定义一个map类型对象的时候,指定一个有严格顺序或者强序的Comapre类。

//自定义map的key排列顺序
map<string,int,greater<string>> m1;
struct myCompare {
 bool operator()(const string& l, const string& r)const
 {
  return l.length() > r.length();
 }
};
map<string,int,myCompare> m2;

3、自定义map不区分大小写

通过自定义仿函数来实现不区分大小写:

//方法1
struct
NoCaseCompare{ bool operotor()(cosnt std::string& lhs,const std::string& rhs) cosnt { std::string s1(lhs); for(size_t i=0; i < lhs.size(); ++i) { if(s1[i] >= 'a' && s1[i] <= 'z'){ s1[i] -= 32; } } std::string s2(rhs); for(size_t i=0; i < rhs.size(); ++i) { if(s2[i] >= 'a' && s2[i] <= 'z'){ s2[i] -= 32; } } return s1.comapre(s2) >= 0 ? false : true; } }; map<string,int,NoCaseCompare> m_map;
//方法2
struct
ci_less : std::binary_function<std::string, std::string, bool> { // case-independent (ci) compare_less binary function struct nocase_compare : public std::binary_function<unsigned char, unsigned char, bool> { bool operator()(const unsigned char &c1, const unsigned char &c2) const { return tolower(c1) < tolower(c2); } }; bool operator()(const std::string &s1, const std::string &s2) const { return std::lexicographical_compare(s1.begin(), s1.end(), // source range s2.begin(), s2.end(), // dest range nocase_compare()); // comparison } }; map<string, int, ci_less> m_map;

4、遍历删除容器

在南瑞测试时,发现遍历删除map时不同的标准方法可以不一样,做一个汇总。

C++中的容器按存储方式分为两类:

  • 以数组形式存储的顺序容器,如:vector(序列时容器)、deque(序列时容器)、array(序列时容器) 、string(序列式容器)
  • 以不连续节点形式存储的容器,如:list(链表式容器)、forward_list(链表式容器)、set(关联容器)、map(关联容器)

正确的遍历删除方法:

针对map、list、set可以使用下面2种正确方法

①、使用删除元素之前的迭代器定义下一个元素,建议使用的方法

for (auto it=mymap.begin(); it!=mymap.end();) {
    if (it->first == target) {
        mymap.erase(it++); //here is the key
    } else {
        it++;
    }
}
View Code

②、使用erase返回下一个元素的迭代器

for (auto it=mymap.begin(); it!=mymap.end();) {
    if (it->first == target) {
        it = mymap.erase(it);
    } else {
        it++;
    }
}
View Code

注意:在对 vector、deque遍历删除元素时,可以通过erase的返回值来获取下一个元素的位置,也就是上面的第2种方法;但不能使用上面的第1种方法来遍历删除

 错误的遍历删除方法:

①、其实在C98中(南瑞),这种是可以的,参考链接:https://www.cnblogs.com/dabaopku/p/3912662.html

for (auto it=mymap.begin(); it!=mymap.end(); it++) {
    if (it->first == target) {
        mymap.erase(it); //这里的写法是错误的,错误的,错误的!!!
        //it对应的元素已经被删除,it迭代器失效,在for循环中执行it++会导致未定义行为
    }
}
View Code

②、正确的方法中,关于方法1,为什么不能对vector,是因为下一个迭代器已经失效了。

for (auto it=myvector.begin(); it!=myvector.end();) {
    if (*it == target) {
        myvector.erase(it++); //对vector不能工作,其实是数组形式的容器
    } else {
        it++;
    }
}
View Code

其实上述的原因都是因为迭代器失效,失效的情况总结如下:

  • 当容器调用erase()后,当前位置到容器末尾元素的所有迭代器全部失效(实际当前迭代器肯定失效了)。
  • 当容器调用insert()后,当前位置到容器末尾元素的所有迭代器全部失效。
  • 如果容器扩容,在其他地方重新又开辟了一块内存。原来容器底层的内存上所保存的迭代器全都失效了。

序列式容器:因为vetor、deque 使用了连续分配的内存,erase操作删除一个元素导致后面所有的元素都会向前移动一个位置,这些元素的地址发生了变化,

所以当前位置到容器末尾元素的所有迭代器全部失效。

链表式容器:因为使用list实现的,删除一个结点不会对其他结点造成影响,所以删除当前的 iterator,仅仅会使当前的 iterator 失效。

关联式容器:因为关联式容器使用红黑树或者平衡二叉树来实现,插入、删除一个节点不会对他节点造成影响(删除一个元素,整棵树会调整,以符合红黑树或者二叉树的规范,但是单

个节点在内存中的地址没有变化,变化的是各节点之间的指向关系。),所以删除当前的 iterator,仅仅会使当前的 iterator 失效。

posted @ 2022-04-15 12:37  月光下的脚步  阅读(1916)  评论(0编辑  收藏  举报