一个跟踪插入顺序的std :: map?

时间:2009-07-08 13:45:46

标签: c++ dictionary std insertion-order

我目前有一个std::map<std::string,int>,它将一个整数值存储到一个唯一的字符串标识符中,我确实查找了该字符串。它主要是我想要的,除了它不跟踪插入顺序。因此,当我迭代地图以打印出值时,它们将根据字符串进行排序;但是我希望它们按照(第一次)插入的顺序排序。

我考虑使用vector<pair<string,int>>代替,但我需要查找字符串并将整数值增加大约10,000,000次,因此我不知道std::vector是否会明显变慢。

有没有办法使用std::map或是否有另一个std容器更适合我的需要?

[我在GCC 3.4上,我的std::map]中的值可能不超过50对。

感谢。

15 个答案:

答案 0 :(得分:52)

如果你在std :: map中只有50个值,你可以在打印之前将它们复制到std :: vector,并使用适当的函子通过std :: sort进行排序。

或者您可以使用boost::multi_index。它允许使用多个索引。 在您的情况下,它可能如下所示:

struct value_t {
      string s;
      int    i;
};
struct string_tag {};
typedef multi_index_container<
    value_t,
    indexed_by<
        random_access<>, // this index represents insertion order
        hashed_unique< tag<string_tag>, member<value_t, string, &value_t::s> >
    >
> values_t;

答案 1 :(得分:19)

您可以将std::vectorstd::tr1::unordered_map(哈希表)合并。以下是unordered_map Boost's documentation的链接。您可以使用向量来跟踪插入顺序和哈希表以进行频繁查找。如果您正在进行数十万次查找,则std::map的O(log n)查找与哈希表的O(1)之间的差异可能很大。

std::vector<std::string> insertOrder;
std::tr1::unordered_map<std::string, long> myTable;

// Initialize the hash table and record insert order.
myTable["foo"] = 0;
insertOrder.push_back("foo");
myTable["bar"] = 0;
insertOrder.push_back("bar");
myTable["baz"] = 0;
insertOrder.push_back("baz");

/* Increment things in myTable 100000 times */

// Print the final results.
for (int i = 0; i < insertOrder.size(); ++i)
{
    const std::string &s = insertOrder[i];
    std::cout << s << ' ' << myTable[s] << '\n';
}

答案 2 :(得分:11)

保持平行list<string> insertionOrder

到了打印的时候,迭代列表并查找地图

each element in insertionOrder  // walks in insertionOrder..
    print map[ element ].second // but lookup is in map

答案 3 :(得分:7)

Tessil有一个非常好的有序地图(和集)实现,这是MIT许可。您可以在此处找到它:ordered-map

地图示例

#include <iostream>
#include <string>
#include <cstdlib>
#include "ordered_map.h"

int main() {
tsl::ordered_map<char, int> map = {{'d', 1}, {'a', 2}, {'g', 3}};
map.insert({'b', 4});
map['h'] = 5;
map['e'] = 6;

map.erase('a');


// {d, 1} {g, 3} {b, 4} {h, 5} {e, 6}
for(const auto& key_value : map) {
    std::cout << "{" << key_value.first << ", " << key_value.second << "}" << std::endl;
}


map.unordered_erase('b');

// Break order: {d, 1} {g, 3} {e, 6} {h, 5}
for(const auto& key_value : map) {
    std::cout << "{" << key_value.first << ", " << key_value.second << "}" << std::endl;
}
}

答案 4 :(得分:4)

如果您需要两种查找策略,最终会有两个容器。您可以将vector与实际值(int s)一起使用,并在其旁边放置map< string, vector< T >::difference_type> ,将索引返回到向量中。

要完成所有这些,您可以将它们封装在一个类中。

但我相信boost has a container有多个指数。

答案 5 :(得分:1)

你不能用地图做到这一点,但你可以使用两个独立的结构 - 地图和矢量并保持它们同步 - 也就是当你从地图中删除时,从矢量中找到并删除元素。或者您可以创建一个map<string, pair<int,int>> - 并在您的对存储中插入到记录位置时的地图大小()以及int的值,然后在打印时使用位置成员进行排序。

答案 6 :(得分:1)

这与费萨尔斯的回答有些相关。您可以围绕地图和矢量创建一个包装类,并轻松地使它们保持同步。适当的封装将允许您控制访问方法,从而控制使用哪个容器...矢量或地图。这可以避免使用Boost或类似的东西。

答案 7 :(得分:1)

实现此目的的另一种方法是使用map而不是vector。我将向您展示这种方法并讨论差异:

只需创建一个在幕后有两个地图的类。

#include <map>
#include <string>

using namespace std;

class SpecialMap {
  // usual stuff...

 private:
  int counter_;
  map<int, string> insertion_order_;
  map<string, int> data_;
};

然后,您可以按正确的顺序将迭代器公开到data_上的迭代器。您这样做的方法是遍历insertion_order_,对于您从该迭代中获得的每个元素,使用data_

中的值在insertion_order_中进行查找

您可以使用效率更高的hash_map进行insert_order,因为您不关心直接遍历insertion_order_

要进行插入,您可以使用以下方法:

void SpecialMap::Insert(const string& key, int value) {
  // This may be an over simplification... You ought to check
  // if you are overwriting a value in data_ so that you can update
  // insertion_order_ accordingly
  insertion_order_[counter_++] = key;
  data_[key] = value;
}

有很多方法可以让设计更好,并担心性能,但这是一个很好的框架,可以帮助您开始自己实现此功能。您可以将其模板化,并且实际上可以将对存储为data_中的值,以便您可以轻松地引用insertion_order_中的条目。但我把这些设计问题作为练习: - )。

更新 :我想我应该说一下insert_order _使用map与vector的效率_

  • 直接查找数据,在两种情况下都是O(1)
  • 在向量方法中插入的是O(1),在map方法中插入的是O(logn)
  • 向量方法中的删除是O(n),因为您必须扫描要删除的项目。使用地图方法,它们是O(logn)。

也许如果你不打算使用删除,你应该使用矢量方法。如果您支持不同的排序(如优先级)而不是插入顺序,那么映射方法会更好。

答案 8 :(得分:1)

//应该像这个男人一样!

//这样可以保持插入的复杂性为O(logN),删除也是O(logN)。

class SpecialMap {
private:
  int counter_;
  map<int, string> insertion_order_;
  map<string, int> insertion_order_reverse_look_up; // <- for fast delete
  map<string, Data> data_;
};

答案 9 :(得分:1)

此解决方案仅需要标准模板库而不使用boost的多索引:
您可以使用std::map<std::string,int>;vector <data>;在map中存储vector中数据位置的索引,vector按插入顺序存储数据。这里访问数据具有O(log n)复杂性。以插入顺序显示数据具有O(n)复杂度。插入数据具有O(log n)复杂度。

例如:

#include<iostream>
#include<map>
#include<vector>

struct data{
int value;
std::string s;
}

typedef std::map<std::string,int> MapIndex;//this map stores the index of data stored 
                                           //in VectorData mapped to a string              
typedef std::vector<data> VectorData;//stores the data in insertion order

void display_data_according_insertion_order(VectorData vectorData){
    for(std::vector<data>::iterator it=vectorData.begin();it!=vectorData.end();it++){
        std::cout<<it->value<<it->s<<std::endl;
    }
}
int lookup_string(std::string s,MapIndex mapIndex){
    std::MapIndex::iterator pt=mapIndex.find(s)
    if (pt!=mapIndex.end())return it->second;
    else return -1;//it signifies that key does not exist in map
}
int insert_value(data d,mapIndex,vectorData){
    if(mapIndex.find(d.s)==mapIndex.end()){
        mapIndex.insert(std::make_pair(d.s,vectorData.size()));//as the data is to be
                                                               //inserted at back 
                                                               //therefore index is
                                                               //size of vector before
                                                               //insertion
        vectorData.push_back(d);
        return 1;
    }
    else return 0;//it signifies that insertion of data is failed due to the presence
                  //string in the map and map stores unique keys
}

答案 10 :(得分:1)

你想要的是什么(不使用Boost)就是我称之为&#34;有序哈希&#34;,它本质上是一个哈希的混搭和一个带有字符串或整数键的链表(或两者都在同一个时间)。有序散列在迭代期间使用散列的绝对性能维护元素的顺序。

我已经整理了一个相对较新的C ++代码段库,它填补了我在C ++语言中为C ++库开发人员所看到的漏洞。去这里:

https://github.com/cubiclesoft/cross-platform-cpp

抓斗:

templates/detachable_ordered_hash.cpp
templates/detachable_ordered_hash.h
templates/detachable_ordered_hash_util.h

如果用户控制的数据将被放入哈希值,您可能还需要:

security/security_csprng.cpp
security/security_csprng.h

调用它:

#include "templates/detachable_ordered_hash.h"
...
// The 47 is the nearest prime to a power of two
// that is close to your data size.
//
// If your brain hurts, just use the lookup table
// in 'detachable_ordered_hash.cpp'.
//
// If you don't care about some minimal memory thrashing,
// just use a value of 3.  It'll auto-resize itself.
int y;
CubicleSoft::OrderedHash<int> TempHash(47);
// If you need a secure hash (many hashes are vulnerable
// to DoS attacks), pass in two randomly selected 64-bit
// integer keys.  Construct with CSPRNG.
// CubicleSoft::OrderedHash<int> TempHash(47, Key1, Key2);
CubicleSoft::OrderedHashNode<int> *Node;
...
// Push() for string keys takes a pointer to the string,
// its length, and the value to store.  The new node is
// pushed onto the end of the linked list and wherever it
// goes in the hash.
y = 80;
TempHash.Push("key1", 5, y++);
TempHash.Push("key22", 6, y++);
TempHash.Push("key3", 5, y++);
// Adding an integer key into the same hash just for kicks.
TempHash.Push(12345, y++);
...
// Finding a node and modifying its value.
Node = TempHash.Find("key1", 5);
Node->Value = y++;
...
Node = TempHash.FirstList();
while (Node != NULL)
{
  if (Node->GetStrKey())  printf("%s => %d\n", Node->GetStrKey(), Node->Value);
  else  printf("%d => %d\n", (int)Node->GetIntKey(), Node->Value);

  Node = Node->NextList();
}

我在研究阶段遇到了这个SO线程,看看OrderedHash之类的东西是否已经存在而不需要我放入一个庞大的库中。我很失望。所以我写了自己的。现在我已经分享了它。

答案 11 :(得分:0)

您需要考虑的一件事是您使用的数据元素数量很少。使用矢量可能会更快。地图中有一些开销可能导致在小数据集中进行查找比使用更简单的向量更昂贵。因此,如果您知道您将始终使用相同数量的元素,请进行一些基准测试,看看地图和矢量的性能是否是您真正认为的。您可能会发现只有50个元素的向量中的查找与地图几乎相同。

答案 12 :(得分:0)

boost::multi_index与地图和列表索引一起使用。

答案 13 :(得分:0)

我不知道这样做会有多慢,但是您可以将std :: unordered_map用于您的用例,无序映射按插入顺序跟踪数据。

您可以在此处了解更多信息:-

https://www.geeksforgeeks.org/map-vs-unordered_map-c/ https://en.cppreference.com/w/cpp/container/unordered_map

答案 14 :(得分:-1)

对(str,int)和static int的映射,在插入调用时递增索引数据对。放入一个可以返回带有index()成员的静态int val的结构吗?