tags:

views:

743

answers:

14

This is my code

map<string,int> persons;

persons["B"] = 123;
persons["A"] = 321;


for(map<string,int>::iterator i = persons.begin();
    i!=persons.end();
    ++i)
{
    cout<< (*i).first << ":"<<(*i).second<<endl;
}

Expected output:

  B:123
  A:321

But output it gives is:

  A:321
  B:123

I want it to maintain the order in which keys and values were inserted in the map<string,int>.

Is it possible? Or should I use some other STL data structure? Which one?

+4  A: 

maps and sets are meant to impose a strict weak ordering upon the data. Strick weak ordering maintains that no entries are equavalent (different to being equal).

You need to provide a functor that the map/set may use to perform a<b. With this functor the map/set sorts its items (in the STL from GCC it uses a red-black tree). It determines weather two items are equavalent if !a<b && !b<a -- the equavelence test.

The functor looks like follows:

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

If you can provide a function that tells the STL how to order things then the map and set can do what you want. For example

template<typename T>
struct ItemHolder
{
    int insertCount;
    T item;
};

You can then easily write a functor to order by insertCount. If your implementation uses red-black trees your underlying data will remain balanced -- however you will get a lot of re-balancing since your data will be generated based on incremental ordering (vs. Random) -- and in this case a list with push_back would be better. However you cannot access data by key as fast as you would with a map/set.

If you want to sort by string -- provide the functor to search by string, using the insertCount you could potentiall work backwards. If you want to search by both you can have two maps.

map<insertcount, string> x; // auxhilary key
map<string, item> y; //primary key

I use this strategy often -- however I have never placed it in code that is run often. I'm considering boost::bimap.

Hassan Syed
But not to search by string, which it seems he wants.
anon
@Neil Yes, I want it. :)
TheMachineCharmer
It really seems kind of pointless to me to use a tree structure to keep the insertion order... the insertion order can be kept by any array-like structure, from `vector` to `list` passing by `deque` with less memory / runtime overhead. It really feels like corrupting `map` or `set` to use them like that :/
Matthieu M.
+8  A: 

Map is definitely not right for you:

"Internally, the elements in the map are sorted from lower to higher key value following a specific strict weak ordering criterion set on construction."

Quote taken from here.

Unfortunately there is no unordered associative container in the STL, so either you use a nonassociative one like vector, or write your own :-(

Péter Török
@Hassan What exactly is incorrect???
Péter Török
That he can't use a map to implement both insert ordering and string ordering -- using two maps ofcourse.
Hassan Syed
@Hassan You mean to achieve insert ordering by using the insertion order as a key?
Péter Török
If it's deemed important by the user, why not.
Hassan Syed
@Hassan Looks a bit like overkill to me, but if he wants that, I don't protest.
Péter Török
+19  A: 

There is no standard container that does directly what you want. The obvious container to use if you want to maintain insertion order is a vector. If you also need look up by string, use a vector AND a map. The map would in general be of string to vector index, but as your data is already integers you might just want to duplicate it, depending on your use case.

anon
vectors have incompatible complexity guarantees with maps though.
Hassan Syed
@Hassan So what? Complexity guarantees don't prevent containers working together.
anon
list would be better no ?
Hassan Syed
@Hassan List is almost never better, so no. If you disagree, say why.
anon
Afaik lists should have constant insert time -- provided items are added to the head. Which is the case here.
Hassan Syed
@Hassan Constant time != faster. And the user would be adding at the TAIL - i.e. using push_back() - he wants the last added to appear last.
anon
well switch polarity and that case is met :P
Hassan Syed
@Hassan But vector (and deque) are amortised constant time for adding at the tail, are normally faster than lists, allow random access and take up less memory. As I've said before, the list should be the choice of last resort, not the first.
anon
As usual: if you need a container without any special requirement you need a `vector`.
Matthieu M.
+1  A: 

Use a vector. It gives you complete control over ordering.

EvilTeach
+3  A: 

Besides Neil's recommendation of a combined vector+map if you need both to keep the insertion order and the ability to search by key, you can also consider using boost multi index libraries, that provide for containers addressable in more than one way.

David Rodríguez - dribeas
A: 

I also think Map is not the way to go. The keys in a Map form a Set; a single key can occur only once. During an insert in the map the map must search for the key, to ensure it does not exist or to update the value of that key. For this it is important (performance wise) that the keys, and thus the entries, have some kind of ordering. As such a Map with insert ordering would be highly inefficient on inserts and retrieving entries.

Another problem would be if you use the same key twice; should the first or the last entry be preserved, and should it update the insert order or not?

Therefore I suggest you go with Neils suggestion, a vector for insert-time ordering and a Map for key-based searching.

extraneon
A: 

Yes, the map container is not for you.
As you asked, you need the following code instead:

   struct myClass {
      std::string stringValue;
      int         intValue;
      myClass( const std::string& sVal, const int& iVal ):
               stringValue( sVal ),
               intValue( iVal) {}
   };

   std::vector<myClass> persons;

   persons.push_back( myClass( "B", 123 ));
   persons.push_back( myClass( "A", 321 ));


   for(std::vector<myClass>::iterator i = persons.begin();
      i!=persons.end();
      ++i)
   {
      std::cout << (*i).stringValue << ":" << (*i).intValue << std::endl;
   }

Here the output is unsorted as expected.

avp
+1  A: 

Well, there is no STL container which actually does what you wish, but there are possibilities.

1. STL

By default, use a vector. Here it would mean:

struct Entry { std::string name; int it; };

typedef std::vector<Entry> container_type;

If you wish to search by string, you always have the find algorithm at your disposal.

class ByName: std::unary_function<Entry,bool>
{
public:
  ByName(const std::string& name): m_name(name) {}
  bool operator()(const Entry& entry) const { return entry.name == m_name; }
private:
  std::string m_name;
};

// Use like this:
container_type myContainer;
container_type::iterator it =
  std::find(myContainer.begin(), myContainer.end(), ByName("A"));

2. Boost.MultiIndex

This seems way overkill, but you can always check it out here.

It allows you to create ONE storage container, accessible via various indexes of various styles, all maintained for you (almost) magically.

Rather than using one container (std::map) to reference a storage container (std::vector) with all the synchro issues it causes... you're better off using Boost.

Matthieu M.
In your first possibility, searching by name will take linear time and will not be efficient. I'm curious... Why do you think Boost.MultiIndex is overkill? It'll do exactly what the OP wants, and indexing by name should take logarithmic time (and perhaps even less if a hashed index is used).
Emile Cormier
Well, linear time does not sound great for sure... but what's the `n` ? If we're talking a few dozen items, it seems well sufficient. I usually don't bother with performance related issues before they become clearly established.
Matthieu M.
+6  A: 

Like Matthieu has said in another answer, the Boost.MultiIndex library seems the right choice for what you want. However, this library can be a little tough to use at the beginning especially if you don't have a lot of experience with C++. Here is how you would use the library to solve the exact problem in the code of your question:

struct person {
    std::string name;
    int id;
    person(std::string const & name, int id) 
    : name(name), id(id) { 
    }
};

int main() {

    using namespace::boost::multi_index;
    using namespace std;

    // define a multi_index_container with a list-like index and an ordered index
    typedef multi_index_container<
      person,        // The type of the elements stored
      indexed_by<    // The indices that our container will support
        sequenced<>,                           // list-like index
        ordered_unique<member<person, string, 
                              &person::name> > // map-like index (sorted by name)
      >
    > person_container;

    // Create our container and add some people
    person_container persons;
    persons.push_back(person("B", 123));
    persons.push_back(person("C", 224));
    persons.push_back(person("A", 321));

    // Typedefs for the sequence index and the ordered index
    enum { Seq, Ord };
    typedef person_container::nth_index<Seq>::type persons_seq_index;
    typedef person_container::nth_index<Ord>::type persons_ord_index;

    // Let's test the sequence index
    persons_seq_index & seq_index = persons.get<Seq>();
    for(persons_seq_index::iterator it = seq_index.begin(), 
                                    e = seq_index.end(); it != e; ++it)
        cout << it->name << ":"<< it->id << endl;
    cout << "\n";

    // And now the ordered index
    persons_ord_index & ord_index = persons.get<Ord>();
    for(persons_ord_index::iterator it = ord_index.begin(), 
                                    e = ord_index.end(); it != e; ++it)
        cout << it->name << ":"<< it->id << endl;
    cout << "\n";

    // Thanks to the ordered index we have fast lookup by name:
    std::cout << "The id of B is: " << ord_index.find("B")->id << "\n";
}

Which produces the following output:

B:123
C:224
A:321

A:321
B:123
C:224

The id of B is: 123
Manuel
+1 This is exactly what Boost.Multiarray was meant for. Perhaps you should mention that a hashing index can also be used. I wish I could +1 more, your answer deserves to be way up higher.
Emile Cormier
@Emile - thanks for your kind words. Normally my answers come too late and barely anyone notices them. I guess I'm not fit for SO :)
Manuel
@Manuel: I hear ya. SO does indeed seem to favor the Fastest Guns in the West. Take solace in the fact that sooner or later, someone else will find this answer via search and may find it helpful. :-)
Emile Cormier
we'll get it up there eventually. ;)
just somebody
A: 

I'd vote for typedef std::vector< std::pair< std::string, int > > UnsortedMap;

Assignment looks a bit different, but your loop remains exactly as it is now.

andreas buykx
A: 

Map is ordered collection (second parametr in template is a order functor), as set. If you want to pop elements in that sequenses as pushd you should use deque or list or vector.

den bardadym
A: 

In order to do what they do and be efficient at it, maps use hash tables and sorting. Therefore, you would use a map if you're willing to give up memory of insertion order to gain the convenience and performance of looking up by key.

If you need the insertion order stored, one way would be to create a new type that pairs the value you're storing with the order you're storing it (you would need to write code to keep track of the order). You would then use a map of string to this new type for storage. When you perform a look up using a key, you can also retrieve the insertion order and then sort your values based on insertion order.

One more thing: If you're using a map, be aware of the fact that testing if persons["C"] exists (after you've only inserted A and B) will actually insert a key value pair into your map.

carleeto
+1  A: 

For preserving all the time complexity constrains you need map + list:

struct Entry
{
   string key;
   int val;
};

typedef list<Entry> MyList;
typedef MyList::iterator Iter;
typedef map<string, Iter> MyMap;

MyList l;
MyMap m;

int find(string key)
{
   Iter it = m[key]; // O(log n)
   Entry e = *it;
   return e.val;
}

void put(string key, int val)
{
   Entry e;
   e.key = key;
   e.val = val;
   Iter it = l.insert(l.end(), e); // O(1)
   m[key] = it;                    // O(log n)
}

void erase(string key)
{
   Iter it = m[key];  // O(log n)
   l.erase(it);       // O(1)
   m.erase(key);      // O(log n)
}

void printAll()
{
   for (Iter it = l.begin(); it != l.end(); it++)
   {
       cout<< it->key << ":"<< it->val << endl;
   }
}

Enjoy

Slava
A: 
Saurabh Shah