I used list to place cities into a trip. Then I iterate over
the list to display the trip itinerary. I would like to access
the cities by the name rather than by the trip order. So, I
thought I could use a map rather than a list but the key determines
the order. I would still like to control the order of the sequence
but be able to access the entries by a key.
Can these features be combined? Is there some standard way to address
this?
#include <list>
#include <iostream>
struct City{
City(std::string a_n, int a_d):name(a_n), duration(a_d){}
std::string name;
int duration;
};
int main(){
std::list<City*> trip;
trip.push_back(new City("NY", 5));
trip.push_back(new City("LA", 2));
for (std::list<City*>::iterator ii=trip.begin(); ii!=trip.end(); ++ii)
std::cout << (*ii)->name << " for " << (*ii)->duration << " days." <<std::endl;
}
Often times you will need to compose multiple lists and maps. The common way is to store a pointer to the Cities in your by city lookup map from the pointers in your list. Or you can use a class like Boost.MultiIndex to do what you want in what I would say is much cleaner. It also scales much better and there is a lot less boiler plate code if you want to add new indexes. It is also usually more space and time efficient
typedef multi_index_container<
City,
indexed_by<
sequenced<>, //gives you a list like interface
ordered_unique<City, std::string, &City::name> //gives you a lookup by name like map
>
> city_set;
Create a map<string,int> m;, where the values are indexes to a vector<City>, for example m["NY"] == 0 and m["LA"] == 1.
Use two collections:
A list to store the actual objects in the order you are interested in.
A map to map names to the objects.
The best solution is to use Boost.MultiIndex, though that's slightly more involved. Unfortunately, I don't have time now to provide sample code; sorry.
Related
I'm working on a word Tagging system for a C++ project. I need a system where a map stores the following key-value information:
word["with"] = 16, 6, 15;
Where ["with"] is the index, and the 3-tuple (16, 6, 15) are values of the index. I've tried maps, but I keep getting semantic errors, which I understand are a result of not being able to give a key more then 1 value.
I tried multi maps, but I can't seem to get the syntax to suit my needs?
I would like to refrain from using Structs or Classes, as this database already contains 200 words, and I'm trying to keep my lines of code readable and too a minimum.
How would I go about this? Am I missing something? How would you declare a system like this?
You should declare your map as std::map<std::string, std::vector<unsigned int>>, so you can have a vector of values for your index.
You can make a map that maps Strings to Vectors or some other data structure that can hold an arbitrary number of integers.
Worth noting, however, that things like Structs and Classes are components of a language meant to organize code. Structs group related data; classes model groups of related data and their associated behaviors. It's certainly possible to do everything without them but that would make for some very unreadable code.
The number of lines and whether or not you use classes/structs are poor metrics for the complexity and readability of your code. And the modularity they offer far exceeds the minute runtime cost of dereferencing those values.
word["with"] = 16, 6, 15;//This usage is wrong
std::multimap or std::unordered_multimap should work for you.
If you define word as follows:
std::multimap<std::string,int> word;
You should insert values to map as shown below:
std::string key="with";
word.insert(std::pair<std::string,int>(key,16));
word.insert(std::pair<std::string,int>(key,6));
word.insert(std::pair<std::string,int>(key,15));
for( auto &x : word)
std::cout<<x.first<<" " << x.second<<"\n";
As user4581301 pointed out in comment if you have C++11 enabled compiler, you can insert values into std::multimap as follows:
word.emplace("with",16);
word.emplace("with",6);
word.emplace("with",15);
Demo: http://coliru.stacked-crooked.com/a/c7ede5c497172c5d
Example for using C++ maps to hold multiple integer values:
#include<iostream>
#include<map>
#include<vector>
using namespace std;
int main(){
std::map<int, std::vector<int> > mymap2;
std::vector<int> myvector;
myvector.push_back(8);
myvector.push_back(11);
myvector.push_back(53);
mymap2[5] = myvector;
cout << mymap2[5][0] << endl;
cout << mymap2[5][1] << endl;
cout << mymap2[5][2] << endl;
}
Prints:
8
11
53
Just replace the int datatypes with a string and you should be able to map strings to lists of numbers.
How do you make a variable name where you create a variable and then in brackets the variable number? (By the way, I'm just guessing out how the code should be so that you get what I'm trying to say.) For example:
int var[5];
//create a variable var[5], but not var[4], var[3], var[2], etc.
Then, the variable number must be able to be accessed by a variable value:
int number = 5;
int var[number]; //creates a var[5], not a var[4], etc.
int var[2]; //creates a var[2], not a var[1], etc.
cout >>var[number];
number = 2;
cin << var[number];
If I'm way off track with my "example", please suggest something else. I need something similar to this for my game to operate, because I must be able to create an unlimited instance of bullets, but they will also be destroyed at one point.
It looks like you are looking for the functionality provided by std::map which is a container used to map keys to values.
Documentation of std::map
Example use
In the below example we bind the value 123 to the integer key 4, and the value 321 to key 8. We then use a std::map<int,int>::const_iterator to iterate over the key/value pairs in our std::map named m.
#include <map>
...
std::map<int, int> m;
m[4] = 123;
m[8] = 321;
for (std::map<int, int>::const_iterator cit = m.begin (); cit != m.end (); ++cit)
std::cout << cit->first << " -> " << cit->second << std::endl;
output:
4 -> 123
8 -> 321
It looks like you want variable length arrays, which is not something C++ supports. In most cases, the correct solution is to use an std::vector instead, as in
int number = 42; // or whatever
std::vector<int> var(number);
You can use std::vector as you would use an array in most cases, and you gain a lot of bonus functionality.
If I understand what you want correctly (which I'm not certain that I do), you want to be able to create a place to hold objects and use them according to some index number, but to only create the specific objects which go in it on demand. You want do to this either because 1) you don't know how many objects you're going to create or 2) you aren't going to use every index number or 3) both.
If (1) then you should probably just use a vector, which is an array-like structure which grows automatically as you add more things to it. Look up std::vector.
If (2) then you could use an array of pointers and initially set all of the values to null and then use new to create the objects as needed. (Or you could use the solution recommend in part 3.)
If (3) then you want to use some form of map or hash table. These structures will let you find things by number even when not all numbers are in use and will grow as needed. I would highly recommend a hash table, but in C++, there isn't one in the STL, so you have to build your own or find one in a third-party library. For ease, you can use std::map, which is part of the STL. It does basically the same thing, but is slower. Some C++ distributions also include std::hash_map. If it's available, that should be used instead because it will be faster than std::map.
There are two ways in which I can easily make a key,value attribution in C++ STL: maps and sets of pairs. For instance, I might have
map<key_class,value_class>
or
set<pair<key_class,value_class> >
In terms of algorithm complexity and coding style, what are the differences between these usages?
They are semantically different. Consider:
#include <set>
#include <map>
#include <utility>
#include <iostream>
using namespace std;
int main() {
pair<int, int> p1(1, 1);
pair<int, int> p2(1, 2);
set< pair<int, int> > s;
s.insert(p1);
s.insert(p2);
map<int, int> m;
m.insert(p1);
m.insert(p2);
cout << "Set size = " << s.size() << endl;
cout << "Map size = " << m.size() << endl;
}
http://ideone.com/cZ8Vjr
Output:
Set size = 2
Map size = 1
Set elements cannot be modified while they are in the set. set's iterator and const_iterator are equivalent. Therefore, with set<pair<key_class,value_class> >, you cannot modify the value_class in-place. You must remove the old value from the set and add the new value. However, if value_class is a pointer, this doesn't prevent you from modifying the object it points to.
With map<key_class,value_class>, you can modify the value_class in-place, assuming you have a non-const reference to the map.
map<key_class,value_class> will sort on key_class and allow no duplicates of key_class.
set<pair<key_class,value_class> > will sort on key_class and then value_class if the key_class instances are equal, and will allow multiple values for key_class
The basic difference is that for the set the key is the pair, whereas for the map the key is key_class - this makes looking things up by key_class, which is what you want to do with maps, difficult for the set.
Both are typically implemented with the same data structure (normally a red-black balanced binary tree), so the complexity for the two should be the same.
std::map acts as an associative data structure. In other words, it allows you to query and modify values using its associated key.
A std::set<pair<K,V> > can be made to work like that, but you have to write extra code for the query using a key and more code to modify the value (i.e. remove the old pair and insert another with the same key and a different value). You also have to make sure there are no more than two values with the same key (you guessed it, more code).
In other words, you can try to shoe-horn a std::set to work like a std::map, but there is no reason to.
To understand algorithmic complexity, you first need to understand the implementation.
std::map is implemented using RB-tree where as hash_map are implemented using arrays of linked list. std::map provides O(log(n)) for insert/delete/search operation, hash_map is O(1) is best case and o(n) in worst case depending upon hash collisions.
Visualising that semantic difference Philipp mentioned after stepping through the code, note how map key is a const int and how p2 was not inserted into m:
Say I have a high score table structured like
name score
name score
....
I need to do some file operations and manipulate certain areas of the file, and I thought the best way to do this would be to store it in a container that preserved the order of the file, do the data manipulation with the container, then output back to the file.
I considered using a map< std::string, int >, but a map wouldn't preserve the order of the file. Would a vector< pair< std::string, int >> be better, or is there some kind of ordered map I can use? I also need the container to repeat a name if necessary. I think a multimap keeps one key, but allows multiple values for that key, which isn't what I want since it wouldn't preserve order.
Use the
std::vector< std::pair< std::string, int > >
solution.
Or even better, do a HighScoreEntry class, and have a std::vector< HighScoreEntry > -- then you'll be able to add additional data to it later, and embed score handling code in the class.
To add an entry, push the entry to the end of the vector, and run std::sort on it, just remember to write a comparison operator for HighScoreEntry.
class HighScoreEntry
{
public:
std::string name;
uint32 score;
bool operator<(const HighScoreEntry& other) const
{
// code that determines ordering goes here
return score < other.score
}
};
Highscore table:
std::vector< HighScoreEntry > highscores;
Sorting:
std::sort( highscores.begin(), highscores.end() );
To preserve sort order for highscores that have the same score, use a timestamp, and add it to the comparator.
typedef std::pair<int, int> score_entry; // score, timestamp/serial
typedef std::map<score_entry, std::string, std::greater<score_entry> > score_map;
It's ordered by the score and timestamp/serial (in descending order), and allows duplicates of the same high score. (If you want earlier timestamp/serial to be listed first, put in negative numbers.)
Using a serial number instead of a timestamp means that you can allow duplicates without having to use multimap. Thanks to Steve Jessop for the suggestion!
If you want an ordered map, then you want it ordered by score, so it should be a multimap<int, string>, to preserve order and permit tied scores.
This strikes me as silly, though, not to mention it's not obvious what on earth is being "mapped". Container performance on a high-score table is very unlikely ever to matter, so I'd just use the vector of pair<string,int>.
Why use a priority_queue? Should make adding new scores easy as well.
I also need the container to repeat a name if necessary.
So stick the same name into your vector-of-pairs twice. Is that really such a big deal?
I want to store strings and issue each with a unique ID number (an index would be fine). I would only need one copy of each string and I require quick lookup. I check if the string exist in the table often enough that i notice a performance hit. Whats the best container to use for this and how do i lookup if the string exist?
I would suggest tr1::unordered_map. It is implemented as a hashmap so it has an expected complexity of O(1) for lookups and a worst case of O(n). There is also a boost implementation if your compiler doesn't support tr1.
#include <string>
#include <iostream>
#include <tr1/unordered_map>
using namespace std;
int main()
{
tr1::unordered_map<string, int> table;
table["One"] = 1;
table["Two"] = 2;
cout << "find(\"One\") == " << boolalpha << (table.find("One") != table.end()) << endl;
cout << "find(\"Three\") == " << boolalpha << (table.find("Three") != table.end()) << endl;
return 0;
}
try this:
(source: adrinael.net)
Try std::map.
First and foremost you must be able to quantify your options. You have also told us that the main usage pattern you're interested in is lookup, not insertion.
Let N be the number of strings that you expect to be having in the table, and let C be the average number of characters in any given string present in the said table (or in the strings that are checked against the table).
In the case of a hash-based approach, for each lookup you pay the following costs:
O(C) - calculating the hash for the string you are about to look up
between O(1 x C) and O(N x C), where 1..N is the cost you expect from traversing the bucket based on hash key, here multiplied by C to re-check the characters in each string against the lookup key
total time: between O(2 x C) and O((N + 1) x C)
In the case of a std::map-based approach (which uses red-black trees), for each lookup you pay the following costs:
total time: between O(1 x C) and O(log(N) x C) - where O(log(N)) is the maximal tree traversal cost, and O(C) is the time that std::map's generic less<> implementation takes to recheck your lookup key during tree traversal
In the case of large values for N and in the absence of a hash function that guarantees less than log(N) collisions, or if you just want to play it safe, you're better off using a tree-based (std::map) approach. If N is small, by all means, use a hash-based approach (while still making sure that hash collision is low.)
Before making any decision, though, you should also check:
http://meshula.net/wordpress/?p=183
http://wyw.dcweb.cn/mstring.htm
Are the Strings to be searched available statically? You might want to look at a perfect hashing function
sounds like an array would work just fine where the index is the index into the array. To check if it exists, just make sure the index is in bounds of the array and that its entry isn't NULL.
EDIT: if you sort the list, you could always use a binary search which should have fast lookup.
EDIT: Also, if you want to search for a string, you can always use a std::map<std::string, int> as well. This should have some decent lookup speeds.
Easiest is to use std::map.
It works like this:
#include <map>
using namespace std;
...
map<string, int> myContainer;
myContainer["foo"] = 5; // map string "foo" to id 5
// Now check if "foo" has been added to the container:
if (myContainer.find("foo") != myContainer.end())
{
// Yes!
cout << "The ID of foo is " << myContainer["foo"];
}
// Let's get "foo" out of it
myContainer.erase("foo")
Google sparse hash maybe