web-dev-qa-db-ja.com

挿入の順序を追跡するstd :: map?

現在、一意の文字列識別子に整数値を格納するstd::map<std::string,int>があり、文字列を検索します。それは、挿入順序を追跡しないことを除いて、ほとんど私が望むことをします。したがって、マップを反復処理して値を出力すると、文字列に従って並べ替えられます。しかし、(最初の)挿入の順序に従ってソートされるようにします。

代わりにvector<pair<string,int>>を使用することを考えましたが、文字列を検索して整数値を約10,000,000回インクリメントする必要があるため、std::vectorが大幅に遅くなるかどうかわかりません。

std::mapを使用する方法はありますか、それとも私のニーズに合った別のstdコンテナーがありますか?

[私はGCC 3.4を使用しています。おそらく、std::map]には50ペア以下の値しかありません。

ありがとう。

97
polyglot

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;
52

std::vector とともに std::tr1::unordered_map(ハッシュテーブル)。 Boostのドキュメント for unordered_map。ベクターを使用して挿入順序を追跡し、ハッシュテーブルを使用して頻繁に検索を行うことができます。数十万件のルックアップを行っている場合、O [log n)ルックアップとstd::mapおよび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';
}
19

平行を保つlist<string> insertionOrder

印刷するときは、listを繰り返し、mapを検索します。

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

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;
}
}
7
aggsol

両方のルックアップ戦略が必要な場合、2つのコンテナーになります。実際の値(vectors)でintを使用し、map< string, vector< T >::difference_type>の隣に、ベクトルにインデックスを返します。

すべてを完了するには、両方を1つのクラスにカプセル化できます。

しかし、複数のインデックスを持つ boostにはコンテナがあります と考えています。

4
xtofl

(Boostに頼らずに)必要なのは、「順序付きハッシュ」と呼ばれるものです。これは、基本的に、ハッシュと、文字列または整数キー(または両方)のリンクリストのマッシュアップです。順序付けされたハッシュは、ハッシュの絶対的なパフォーマンスで反復中の要素の順序を維持します。

私は比較的新しい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のようなものが既に存在するかどうかを確認しました。大規模なライブラリに立ち寄る必要はありませんでした。がっかりしました。共有しました。

1
CubicleSoft

これを実装する別の方法は、mapの代わりにvectorを使用することです。このアプローチを示し、違いについて説明します。

背後で2つのマップを持つクラスを作成するだけです。

#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を直接反復処理する必要がないため、挿入_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_のエントリを簡単に参照できるようにすることができます。しかし、これらの設計上の問題は演習として残しておきます:-)。

Update:私は、insert_order_にmap vs vectorを使用する効率について何か言うべきだと思います

  • データを直接検索します。どちらの場合もO(1)です
  • ベクトル手法での挿入はO(1)、マップ手法での挿入はO(logn)
  • ベクトルアプローチでの削除は、削除するアイテムをスキャンする必要があるため、O(n)です。マップアプローチでは、O(logn)です。

削除をあまり使用しない場合は、ベクトルアプローチを使用する必要があります。マップアプローチは、挿入順序ではなく別の順序(優先順位など)をサポートしている場合に適しています。

1
Tom

//この男のようになります!

//これにより、挿入の複雑さが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_;
};
1
Ka Yan

これは、ファイサルの答えにいくらか関連しています。地図とベクターの周りにラッパークラスを作成するだけで、簡単に同期できます。適切なカプセル化により、アクセス方法を制御できるため、ベクトルまたはマップを使用するコンテナを制御できます。これにより、Boostなどを使用する必要がなくなります。

1
Polaris878

マップでそれを行うことはできませんが、マップとベクターの2つの別個の構造を使用して、マップから削除し、ベクターから要素を見つけて削除するときに同期を保つことができます。または、map<string, pair<int,int>>-ペアに、レコードの位置への挿入時にマップのsize()をintの値とともに格納し、印刷時に位置メンバーを使用してソートします。

1
Faisal Vali

つかいます boost::multi_indexマップおよびリストインデックス付き。

考慮する必要があることの1つは、使用しているデータ要素の数が少ないことです。ベクトルのみを使用する方が高速になる可能性があります。マップには、単純なベクトルよりも小さなデータセットでルックアップを行う方が高価になる可能性があるオーバーヘッドがあります。そのため、常に同じ数の要素を使用することがわかっている場合は、ベンチマークを実行して、マップとベクトルのパフォーマンスが実際に考えているとおりかどうかを確認してください。 50個の要素のみを含むベクトルでルックアップがマップとほぼ同じである場合があります。

0
Chad Simpkins