简体   繁体   中英

Iterating over Unorderd_map using indexed for loop

I am trying to access values stored in anunorderd_map using a for loop , but I am stuck trying to access values using the current index of my loop. Any suggestion, or link to look-on? thanks. [Hint: I don't want to use an iterator].

my sample code:

#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;

int main()
{
    unordered_map<int,string>hash_table;
    //filling my hash table
    hash_table.insert(make_pair(1,"one"));
    hash_table.insert(make_pair(2,"two"));
    hash_table.insert(make_pair(3,"three"));
    hash_table.insert(make_pair(4,"four"));

   //now, i want to access values of my hash_table with for loop, `i` as index.
   //
  for (int i=0;i<hash_table.size();i++ )
  {
    cout<<"Value at index "<<i<<" is "<<hash_table[i].second;//I want to do something like this. I don't want to use iterator!
  }
  return 0;
}

There are two ways to access an element from an std::unordered_map .

  1. An iterator.
  2. Subscript operator, using the key.

I am stuck trying to access values using the current index of my loop

As you can see, accessing an element using the index is not listed in the possible ways to access an element.

I'm sure you realize that since the map is unordered the phrase element at index i is quite meaningless in terms of ordering. It is possible to access the i th element using the begin iterator and std::advance but...

Hint: I don't want to use an iterator].

Hint: You just ran out of options. What you want to do is not possible. Solution: Start wanting to use tools that are appropriate to achieving your objective.

If you want to iterate a std::unordered_map , then you use iterators because that's what they're for. If you don't want to use iterators, then you cannot iterate an std::unordered_map . You can hide the use of iterators with a range based for loop, but they're still used behind the scenes.

If you want to iterate something using a position - index, then what you need is an array such as a std::vector .

First, why would you want to use an index versus an iterator?
Suppose you have a list of widgets you want your UI to draw. Each widget can have its own list of child widgets, stored in a map. Your options are:

  1. Make each widget draw itself. Not ideal since widgets are now coupled to the UI kit you are using.
  2. Return the map and use an iterator in the drawing code. Not ideal because now the drawing code knows your storage mechanism.

An API that can avoid both of these might look like this.

const Widget* Widget::EnumerateChildren(size_t* io_index) const;

You can make this work with maps but it isn't efficient. You also can't guarantee the stability of the map between calls. So this isn't recommended but it is possible .

const Widget* Widget::EnumerateChildren(size_t* io_index) const
{
    auto& it = m_children.begin();
    std::advance(it, *io_index);
    *io_index += 1;
    return it->second;
}

You don't have to use std::advance and could use a for loop to advance the iterator yourself. Not efficient or very safe.

A better solution to the scenario I described would be to copy out the values into a vector.

void Widget::GetChildren(std::vector<Widget*>* o_children) const;

You can't do it without an iterator. An unordered map could store the contents in any order and move them around as it likes. The concept of "3rd element" for example means nothing.

If you had a list of the keys from the map then you could index into that list of keys and get what you want. However unless you already have it you would need to iterate over the map to generate the list of keys so you still need an iterator.

An old question.

OK, I'm taking the risk: here may be a workaround (not perfect though: it is just a workaround).

This post is a bit long because I explain why this may be needed. In fact one might want to use the very same code to load and save data from and to a file. This is very useful to synchronize the loading and saving of data, to keep the same data, the same order, and the same types.

For example, if op is load_op or save_op:

 load_save_data( var1, op );
 load_save_data( var2, op );
 load_save_data( var3, op );
 ...

load_save_data hides the things performed inside. Maintenance is thus much more easy.

The problem is when it comes to containers. For example (back to the question) it may do this for sets (source A) to save data:

int thesize = theset.size();
load_save(thesize, load); // template (member) function with 1st arg being a typename
for( elem: theset) {
   load_save_data( thesize, save_op );
 }

However, to read (source B):

int thesize;
load_save_data( thesize, save);
for( int i=0; i<thesize, i++) {
   Elem elem;
   load_save_data( elem, load_op);
   theset.insert(elem);
}

So, the whole source code would be something like this, with too loops:

if(op == load_op) { A } else { B }

The problem is there are two different kinds of loop, and it would be nice to merge them as one only. Ideally, it would be nice to be able to do:

int thesize;
load_save_data( thesize, save);
for( int i=0; i<thesize, i++) {
   Elem elem;
   if( op == save_op ) {
      elem=theset[i]; // not possible
   }
   load_save_data( elem, op);
   if( op == load_op ) {
      theset.insert(elem);  
   }
}

(as this code is used in different contexts, care may be taken to provide enough information to the compiler to allow it the strip the unnecessary code (the right "if"), not obvious but possible)

This way, each call to load_save_data is in the same order, the same type. You forget a field for both or none, but everything is kept synchronized between save and load. You may add a variable, change a type, change the order etc in one place only. The code maintenance is thus easier.

A solution to the impossible "theset[i]" is indeed to use a vector or a map instead of a set but you're losing the properties of a set (avoid two identical items).

So a workaround ( but it has a heavy price : efficiency and simplicity) is something like:

void ...::load_save( op )
{
    ...
   int thesize;
   set<...> tmp;
   load_save_data( thesize, save);
   for( int i=0; i<thesize, i++) {
      Elem elem;
      if( op == save_op ) {
         elem=*(theset.begin());      \
         theset.erase(elem);           >       <-----
         tmp.insert(elem);            /
      }
      load_save_data( elem, op);
      if( op == load_op ) {
         theset.insert(elem);  
      }
   }
   if(op == save_op) {
      theset.insert(tmp.begin(), tmp.end());   <-----
   }
   ...
}

Not very beautiful but it does the trick, and (IMHO) itis the closest answer to the question.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM