• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

c++ map 根据value排序 map来单词计数

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

 

//http://www.cplusplus.com/doc/tutorial/files/

// reading a text file
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <iomanip>

#include <cstdlib>
using namespace std;

map<string,int> words;// Words found
void put(string word) {
    ++words[word];
}

void print() {


    for (auto iter = words.begin(); iter != words.end(); iter++) {
        cout << "[" << iter->first << "] = " << iter->second << endl;
    }
}

void print(map<string, int>& M) {

    // Declare a multimap
    multimap<int, string, std::greater<int>> MM;

    // Insert every (key-value) pairs from
    // map M to multimap MM as (value-key)
    // pairs
    for (auto& it : M) {
        MM.insert({ it.second, it.first });
    }

    // Print the multimap
    for (auto& it : MM) {
        cout<< std::setw(15) << it.second << ' '
             << it.first << endl;
    }

    cout<< "size: "<< M.size();
}

void tokenizer(string text) {
    const std::string separators {
        " ,;:.\"!?'\n(){}*"
    }; // Word delimiters
    
    size_t start { text.find_first_not_of(separators) }; // First word start index
    while (start != std::string::npos) { // Find the words
        size_t end = text.find_first_of(separators, start + 1); // Find end of word
        if (end == std::string::npos) // Found a separator?
            end = text.length(); // No, so set to end of text
        //words.push_back(text.substr(start, end - start)); // Store the word
        put(text.substr(start, end - start));
        start = text.find_first_not_of(separators, end + 1); // Find first character of next word
    }
}

string h1=R"(D:\HarryPotter\harry potter7.txt)";

int main () {
    string line;
    string text {};
    ifstream myfile (h1);
    if (myfile.is_open()) {
        while ( getline (myfile,line) ) {
            //cout << line << '\n';
            text+=line;
        }
        myfile.close();
    }

    else cout << "Unable to open file";
    tokenizer(text);
    print(words);
    return 0;
}

 

更明智的用现代c++ map

std::map and its siblings (std::multimapstd::unordered_map/multimap) used to be my favourite containers when I was doing competitive programming. In fact, I still like them(though using less frequently nowadays). And with Modern C++, we now have more reasons to use std::map. That's why I have decided to address this topic by writing an article summarizing these new features. So, without much gibberish, let's dive-in directly.

std::map::contains (C++20)

std::map::contains member function is a good step towards code expressiveness. And I am also tire of writing:
Java
 
 
 
 
 
1
if (auto search = freq_of.find(2); search != freq_of.end()) {
2
    cout << "Found" << endl;
3
}
4
// Where assume, freq_of = map<uint32_t, uint32_t>{{3, 1}, {1, 1}, {2, 1}};
 
 
Rather, from C++20, you can write:
Java
 
 
 
 
 
1
if (freq_of.contains(2)) {
2
    cout << "Found" << endl;
3
}
 
 

The code we write is written first for human consumption & only secondarily for the computer to understand.


- John Sonmez

std::map::try_emplace (C++17)

While inserting into the map, we have 2 different possibilities:
  1.  The key doesn't exist yet. Create a fresh key-value pair.
  2.  The key does exist already. Take the existing item and modify it.
A typical approach to insert an element in std::map is by using operator[ ]std::map::insert or std::map::emplace . But, in all of these cases, we have to bear the cost of default/specialized constructor or assignment call. And the worst part is if an item already exists, we have to drop the freshly created item.
Java
 
 
 
 
 
 
1
int main() {
2
    vector v{3, 4, 5, 8, 7, 3, 5, 2, 4};
3
    map<uint32_t, uint32_t> freq_of;
4

5
    for (const auto &n : v) {
6
        if (const auto &[it, inserted] = freq_of.emplace(n, 1); !inserted) {
7
            it->second++;  // Exists already
8
        }
9
    }
10

11
    assert(freq_of[3] == 2);
12

13
    return EXIT_SUCCESS;
14
}
 
 
Instead:
Java
 
 
 
 
 
1
if (const auto &[it, inserted] = freq_of.try_emplace(n, 1); !inserted) {
2
    it->second++;
3
}
 
 
But, since C++17, there is this std::map::try_emplace method that creates items only if the key doesn't exist yet. This boosts the performance in case objects of that type are expensive to create.
 
Although the above example hasn't showcased the expensive to create items. But, yes! whenever you encounter such a situation, must be known how to handle it with std::map::try_emplace.

std::map::insert_or_assign (C++17)

When you have to insert element anyhow. For the sake of convenience, you use std::map::operator[ ]. Which is OK( and dangerous)! Unless you have any constraint on insertion or assignment.
 
For example, while counting the frequency of elements with the added constraint that when an element is repeated(i.e. assigned) you have to remove all the element lesser than the current one.
 
Java
 
 
 
 
 
 
1
int main() {
2
    vector v{8, 3, 9, 5, 8};
3
    map<uint32_t, uint32_t> freq_of;
4

5
    for (auto &&n : v) {
6
        const auto &[it, is_inserted] = freq_of.insert_or_assign(n, 1);
7

8
        if (!is_inserted) { // remove all lesser element then current one if repeated
9
            freq_of.erase(begin(freq_of), it);
10
        }
11
    }
12
    
13
    assert((freq_of == decltype(freq_of){
14
                           {8, 1},
15
                           {9, 1},
16
                       }));
17

18
    return EXIT_SUCCESS;
19
}
 
 

std::map::insert With Hint (C++11/17)

Looking up items in an std::map takes O(log(n)) time. This is the same for inserting new items. Because the position where to insert them must looked up. Naive insertion of M new items would thus take O(M * log(n)) time.
 
In order to make this more efficient, std::map insertion functions accept an optional insertion hint parameter. The insertion hint is basically an iterator, which points near the future position of the item that is to be inserted. If the hint is correct, then we get amortized O(1) insertion time.
 
Java
 
 
 
 
 
 
1
int main() {
2
    map<uint32_t, string> m{{2, ""}, {3, ""}};
3
    auto where(end(m));
4

5
    for (const auto &n : {8, 7, 6, 5, 4, 3, 2, 1}) { // Items in non-incremental order
6
        where = m.insert(where, {n, ""});
7
    }
8

9
    // How it is not done!
10
    // m.insert(end(m), {0, ""});
11

12
    for (const auto &[key, value] : m) {
13
        cout << key << " : " << value << endl;
14
    }
15

16
    return EXIT_SUCCESS;
17
}
 
 
correct hint will point to an existing element, which is greater than the element to be inserted so that the newly inserted key will be just before the hint. If this does not apply for the hint the user provided during insertion, the insert function will fall back to a nonoptimized insertion, yielding O(log(n)) performance again.
 
For the above example, the first insertion, we got the end iterator of the map, because we had no better hint to start with. After installing an 8 in the tree, we knew that installing 7 will insert a new item just in front of the 8, which qualified it to be a correct hint. This applies to 6 as well, if put into the tree after inserting the 7, and so on. This is why it is possible to use the iterator, which was returned in the last insertion for the next insertion.
 
quick-benchmark.

**Note:* It is important to know that before C++11, insertion hints were considered correct when they pointed before the position of the newly inserted item.*

std::map::merge (C++17)

Same as std::list:splice, which transfers the elements from one list to another. we have std::map::merge which can merge the two same type of std::map.
Java
 
 
 
 
 
 
1
int main() {
2
    map<uint32_t, string> fruits{{5, "grapes"}, {2, "tomoto"}};
3
    map<uint32_t, string> person{{2, "mickel"}, {10, "shree"}};
4
    map<uint32_t, string> fruits_and_persons;
5

6
    fruits_and_persons.merge(fruits);
7
    assert(fruits.size() == 0);
8

9
    fruits_and_persons.merge(person);
10
    assert(person.size() == 1);
11
    assert(person.at(2) == "mickel"); // Won't overwrite value at 2 i.e.`mickel`
12

13
    assert((fruits_and_persons == decltype(fruits){
14
                                      {2, "tomoto"},
15
                                      {5, "grapes"},
16
                                      {10, "shree"},
17
                                  }));
18

19
    return EXIT_SUCCESS;
20
}
 
 
The thing here to note is what happens when there are duplicates! The duplicated elements are not transferred. They're left behind in the right-hand-side map.

std::map::extract (C++17)

Unlike std::map::merge that transfers the elements in bulk, std::map::extract along with std::map::insert transfers element piecewise. But what is the more compelling application of std::map::extract is modifying keys.
 
const qualifier is added to the key type.
 
This kind of restriction is perfectly valid because it makes harder for the user to use std::map the wrong way. But what if we really need to change the keys of some map items?
 
Java
 
 
 
 
 
 
1
int main() {
2
    map<int, string> race_scoreboard{{1, "Mickel"}, {2, "Shree"}, {3, "Jenti"}};
3
    using Pair = map<int, string>::value_type;
4

5
    {
6
        auto Jenti(race_scoreboard.extract(3));
7
        auto Mickel(race_scoreboard.extract(1));
8

9
        swap(Jenti.key(), Mickel.key());
10

11
        auto [it, is_inserted, nh] = race_scoreboard.insert(move(Jenti)); // nh = node handle
12
        assert(*it == Pair(1, "Jenti") && is_inserted == true && nh.empty());
13

14
        race_scoreboard.insert(move(Mickel));
15
    }
16

17
    assert((race_scoreboard == decltype(race_scoreboard){
18
                                   {1, "Jenti"},
19
                                   {2, "Shree"},
20
                                   {3, "Mickel"},
21
                               }));
22

23
    return EXIT_SUCCESS;
24
}
 
 
Consider the above example of the racing scoreboard where you have employed std::map to imitate the racing position. And after a while, Jenti took the lead and Mickel left behind. In this case, how we have switched the keys(position on a race track) of those players.
 
std::map::extract comes in two flavours:
Java
 
 
 
 
 
1
node_type extract(const_iterator position);
2
node_type extract(const key_type& x);
 
 
In the above example, we used the second one, which accepts a key and then finds & extracts the map node that matches the key parameter. The first one accepts an iterator, which implies that it is faster because it doesn't need to search for the item.

What If the Node With a Particular Key Does Not Exist?

If we try to extract an item that doesn't exist with the second method (the one that searches using a key), it returns an empty node_type instance i.e. node handle. The empty() member method or overloaded bool operator tells us that whether a node_type instance is empty or not.

OK! Then How Do I Modify std::map Keys?

After extracting nodes, we were able to modify their keys using the key() method, which gives us non-const access to the key, although keys are usually const.
 
Note that in order to reinsert the nodes into the map again, we had to move them into the insert function. This makes sense because the extract is all about avoiding unnecessary copies and allocations. Moreover, while we move a node_type instance, this does not result in actual moves of any of the container values.

Can I Modify Associated Values in std::map Also?

Yes! You can use the accessor methods nh.mapped()(instead of nh.key()) to manipulate the pieces of the entry in a std::map (or nh.value() for the single piece of data in an element of a std::set). Thus you can extract, manipulate, and reinsert a key without ever copying or moving its actual data.

But What About Safety?

If you extract a node from a map and then throw an exception before you've managed to re-insert it into the destination map.
 
std::map::erase!

There Is More! Interoperability

Map nodes that have been extracted using the std::map::extract are actually very versatile. We can extract nodes from a map instance and insert it into any other map or even multimap instance.
 
unordered_multiset.
 
In order to move items between different map/set structures, the types of key, value and allocator need to be identical.

Difference Between operator[ ] vs insert() vs at()

This is trivial for experienced devs but, still I want to go over it quickly.

std::map::operator[ ]

Operation: find-or-add; try to find an element with the given key inside the map, and if it exists it will return a reference to the stored value. If it does not, it will create a new element inserted in place with default initialization and return a reference to it.
 
Applicability:
  •   Not usable for const std::map, as it will create the element if it doesn't exist.
  •   Not suitable for value type that does not default constructible and assignable(in layman term, doesn't have default constructor & copy/move constructor).
When key exists: Overwrites it.

std::map::insert

Operation: insert-or-nop; accepts a value_type ( std::pair) and uses the key(first member) and to insert it. As std::map does not allow for duplicates, if there is an existing element it will not insert anything.
 
Applicability:
  •   Liberty in calling insert different ways that require the creation of the value_type externally and the copy of that object into the container.
  •   Highly applicable when item insertion sequence is somewhat predictable to gain the performance.
When key exists: Not modify the state of the map, but instead return an iterator to the element that prevented the insertion.

std::map::at

Operation: find-or-throw; returns a reference to the mapped value of the element with key equivalent to input key. If no such element exists, an exception of type std::out_of_range is thrown.
 
Applicability:
  •   Not recommended using at() when accessing const maps and when element absence is a logic error.
  •   Yes, it's better to use std::map::find() when you're not sure element is there. Because, throwing and catching std::logic_error exception will not be a very elegant way of programming, even if we don't think about performance.
When key exists: returns a reference to mapped value.

Parting Words

If you see the table of content for this article above, more than half of the member functions are around inserting the elements into the map. To the newbie, this is the reason for anxiety(or standard committee would say modernness). But if you account for the new features & complexity of language those are pretty much justified. BTW, this modernness doesn't stop here, we do have other specialization also available for map like std::swap (C++17), std::erase_if (C++20) & bunch of comparison operators.

使用map的value排序
Sorting a Map by value in C++ STL
  • Difficulty Level : Medium
  •  Last Updated : 31 May, 2020

Maps are associative containers that store elements in a mapped fashion. Each element has a key value and a mapped value. No two mapped values can have equal key values. By default, a Map in C++ is sorted in increasing order based on its key. Below is the various method to achieve this:

Method 1 – using the vector of 

该文章已有0人参与评论

请发表评论

全部评论

上一篇:
c# GDI+简单绘图(四)发布时间:2022-07-14
下一篇:
BZOJ2302[HAOI2011]Problemc【dp】发布时间:2022-07-14
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap