c++ map 根据value排序 map来单词计数
//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::multimap, std::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:if (auto search = freq_of.find(2); search != freq_of.end()) {
cout << "Found" << endl;
}
// Where assume, freq_of = map<uint32_t, uint32_t>{{3, 1}, {1, 1}, {2, 1}};
if (freq_of.contains(2)) {
cout << "Found" << endl;
}
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:- The key doesn't exist yet. Create a fresh key-value pair.
- The key does exist already. Take the existing item and modify it.
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.int main() {
vector v{3, 4, 5, 8, 7, 3, 5, 2, 4};
map<uint32_t, uint32_t> freq_of;
for (const auto &n : v) {
if (const auto &[it, inserted] = freq_of.emplace(n, 1); !inserted) {
it->second++; // Exists already
}
}
assert(freq_of[3] == 2);
return EXIT_SUCCESS;
}
if (const auto &[it, inserted] = freq_of.try_emplace(n, 1); !inserted) {
it->second++;
}
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.std::map::operator[ ]
isn't feasible. Rather, std::map::insert_or_assign
is more appropriate and returns more information than std::map::operator[ ]
. It also does not require default-constructibility of the mapped type. Consider the following example for the same.int main() {
vector v{8, 3, 9, 5, 8};
map<uint32_t, uint32_t> freq_of;
for (auto &&n : v) {
const auto &[it, is_inserted] = freq_of.insert_or_assign(n, 1);
if (!is_inserted) { // remove all lesser element then current one if repeated
freq_of.erase(begin(freq_of), it);
}
}
assert((freq_of == decltype(freq_of){
{8, 1},
{9, 1},
}));
return EXIT_SUCCESS;
}
std::map::insert With Hint (C++11/17)
Looking up items in anstd::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.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.int main() {
map<uint32_t, string> m{{2, ""}, {3, ""}};
auto where(end(m));
for (const auto &n : {8, 7, 6, 5, 4, 3, 2, 1}) { // Items in non-incremental order
where = m.insert(where, {n, ""});
}
// How it is not done!
// m.insert(end(m), {0, ""});
for (const auto &[key, value] : m) {
cout << key << " : " << value << endl;
}
return EXIT_SUCCESS;
}
O(log(n))
performance again.**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 havestd::map::merge
which can merge the two same type of std::map
.int main() {
map<uint32_t, string> fruits{{5, "grapes"}, {2, "tomoto"}};
map<uint32_t, string> person{{2, "mickel"}, {10, "shree"}};
map<uint32_t, string> fruits_and_persons;
fruits_and_persons.merge(fruits);
assert(fruits.size() == 0);
fruits_and_persons.merge(person);
assert(person.size() == 1);
assert(person.at(2) == "mickel"); // Won't overwrite value at 2 i.e.`mickel`
assert((fruits_and_persons == decltype(fruits){
{2, "tomoto"},
{5, "grapes"},
{10, "shree"},
}));
return EXIT_SUCCESS;
}
std::map::extract (C++17)
Unlikestd::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.std::map
keys are always unique and sorted. Hence, It is crucial that users cannot modify the keys of map nodes that are already inserted. In order to prevent the user from modifying the key items of perfectly sorted map nodes, the const qualifier is added to the key type.std::map
the wrong way. But what if we really need to change the keys of some map items?int main() {
map<int, string> race_scoreboard{{1, "Mickel"}, {2, "Shree"}, {3, "Jenti"}};
using Pair = map<int, string>::value_type;
{
auto Jenti(race_scoreboard.extract(3));
auto Mickel(race_scoreboard.extract(1));
swap(Jenti.key(), Mickel.key());
auto [it, is_inserted, nh] = race_scoreboard.insert(move(Jenti)); // nh = node handle
assert(*it == Pair(1, "Jenti") && is_inserted == true && nh.empty());
race_scoreboard.insert(move(Mickel));
}
assert((race_scoreboard == decltype(race_scoreboard){
{1, "Jenti"},
{2, "Shree"},
{3, "Mickel"},
}));
return EXIT_SUCCESS;
}
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:node_type extract(const_iterator position);
node_type extract(const key_type& x);
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 emptynode_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 thekey()
method, which gives us non-const access to the key, although keys are usually const.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 methodsnh.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::extract
by-default(without insert) will act as std::map::erase!There Is More! Interoperability
Map nodes that have been extracted using thestd::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.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.- 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).
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.- 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.
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.- 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.
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.
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 pairs The idea is to copy all contents from the map to the corresponding vector of pairs and sort the vector of pairs according to second value using the lambda function given below:
bool cmp(pair<T1, T2>& a, pair<T1, T2>& b) { return a.second < b.second; } where T1 and T2 are the data-types that can be the same or different.
Below is the implementation of the above approach:
// C++ program for the above approach #include <bits/stdc++.h> using namespace std; // Comparator function to sort pairs // according to second value bool cmp(pair<string, int >& a, pair<string, int >& b) { return a.second < b.second; } // Function to sort the map according // to value in a (key-value) pairs void sort(map<string, int >& M) { // Declare vector of pairs vector<pair<string, int > > A; // Copy key-value pair from Map // to vector of pairs for ( auto & it : M) { A.push_back(it); } // Sort using comparator function sort(A.begin(), A.end(), cmp); // Print the sorted value for ( auto & it : A) { cout << it.first << ' ' << it.second << endl; } } // Driver Code int main() { // Declare Map map<string, int > M; // Given Map M = { { "GfG" , 3 }, { "To" , 2 }, { "Welcome" , 1 } }; // Function Call sort(M); return 0; } |
Welcome 1 To 2 GfG 3
Method 2 – using the set of pairs The idea is to insert all the (key-value) pairs from the map into a set of pairs that can be constructed using a comparator function that orders the pairs according to the second value.
Below is the implementation of the above approach:
// C++ program for the above approach #include <bits/stdc++.h> using namespace std; // Comparison function for sorting the // set by increasing order of its pair's // second value struct comp { template < typename T> // Comparator function bool operator()( const T& l, const T& r) const { if (l.second != r.second) { return l.second < r.second; } return l.first < r.first; } }; // Function to sort the map according // to value in a (key-value) pairs void sort(map<string, int >& M) { // Declare set of pairs and insert // pairs according to the comparator // function comp() set<pair<string, int >, comp> S(M.begin(), M.end()); // Print the sorted value for ( auto & it : S) { cout << it.first << ' ' << it.second << endl; } } // Driver Code int main() { // Declare Map map<string, int > M; // Given Map M = { { "GfG" , 3 }, { "To" , 2 }, { "Welcome" , 1 } }; // Function Call sort(M); return 0; } |
Welcome 1 To 2 GfG 3
Method 3 – using multimap
Multimap is similar to a map with an addition that multiple elements can have the same keys. Rather than each element is unique, the key-value and mapped value pair have to be unique in this case.
The idea is to insert all pairs from the given map into multimap using originals map’s value as a key in the multimap and original maps key value as a value in the multimap.
Below is the implementation of the above approach:
// C++ program for the above approach #include <bits/stdc++.h> using namespace std; // Function to sort the map according // to value in a (key-value) pairs void sort(map<string, int >& M) { // Declare a multimap multimap< int , string> 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 << it.second << ' ' << it.first << endl; } } // Driver Code int main() { // Declare Map map<string, int > M; // Given Map M = { { "GfG" , 3 }, { "To" , 2 }, { "Welcome" , 1 } }; // Function Call sort(M); return 0; } |
Welcome 1 To 2 GfG 3
Attention reader! Don’t stop learning now. Get hold of all the important DSA concepts with the DSA Self Paced Course at a student-friendly price and become industry ready.
1,
降序:multimap<int, string, std::greater<int>> MM;
https://riptutorial.com/cplusplus/example/13316/sorting-with-std--map--ascending-and-descending-
https://en.cppreference.com/w/cpp/container/map