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


113

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

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

使用する方法はありますstd::mapか、またはstd私のニーズにより適した別のコンテナはありますか?

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

ありがとう。


8
std :: mapの高速ルックアップ時間の一部は、順番に並べ替えられているため、バイナリ検索を実行できます。ケーキがなくて食べられない!
bobobobo 2012年

1
当時何を使っていたのですか?
aggsol 2017年

回答:


56

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;

それは素晴らしいことです!Boostには、メンバーセレクターで作業を行うこともできます。
xtofl 2009

2
はい、multi_indexはboostでの私のお気に入りの機能です:)
Kirill V. Lyadvinsky 2009

3
@Kristo:それはコンテナのサイズではなく、この問題のために既存の実装を再利用することです。それは上品です。確かに、C ++は関数型言語ではないため、構文は少し複雑です。
xtofl 2009

4
キーストロークの保存に関するプログラミングはいつからですか?
GManNickG 2009

1
これを投稿してくれてありがとう。「ダミーのためのブーストマルチインデックス」本はありますか?私はそれを使うことができました...
2013

25

a std::vectorstd::tr1::unordered_map(ハッシュテーブル)を組み合わせることができます。これは、Boostのドキュメントリンクですunordered_map。ベクトルを使用して挿入順序を追跡し、ハッシュテーブルを使用して頻繁に検索を行うことができます。数十万回のルックアップを実行している場合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';
}

4
@xtofl、それによって私の回答が役に立たなくなり、投票に値するのはなぜですか?私のコードは何らかの点で間違っていますか?
マイケルクリストフィク

これはそれを行うための最良の方法です。非常に安価なメモリコスト(50文字列のみ!)、std::map想定どおりに(つまり、挿入時にそれ自体をソートすることで)機能し、実行時間が高速です。(私はstd :: listを使用した私のバージョンを書いた後でこれを読みました!)
bobobobo

std :: vectorまたはstd :: listは好みの問題であり、どちらが良いか明確ではないと思います。(ベクターには、不要なランダムアクセスがあり、連続メモリもあり、これも不要です。Listは、これら2つの機能のいずれかを費やすことなく順序を格納します(例:成長中の再割り当て)。
OliverSchönrock19年

14

平行を保つlist<string> insertionOrder

印刷するときは、リストを反復処理して、マップを検索します

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

1
これも私の最初の考えでしたが、2番目のコンテナーのキーを複製しますよね?std :: stringキーが見事ではない場合、そうでしょう?
OliverSchönrock19年

2
@OliverSchonrock C ++ 17以降では、リストのをstd::string_view参照するマップのキーに使用できます。これはコピーを回避しますが、要素がそれらを参照するマップ内のキーよりも長く存続するように注意する必要があります。std::stringinsertionOrderinsertionOrder
flyx

マップとリストを1つに統合したコンテナを作成してしまいました:codereview.stackexchange.com/questions/233177/… 重複なし
OliverSchönrock19年

10

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

両方のルックアップ戦略が必要な場合は、2つのコンテナが作成されます。vector実際の値(ints)でa を使用し、そのmap< string, vector< T >::difference_type> 隣にを配置して、インデックスをベクトルに返すことができます。

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

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


3

(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のようなものがすでに存在するかどうかを確認しました。がっかりしたよ。だから私は自分で書いた。そして今、それを共有しました。


2

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


2

これを実装する別の方法は、の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介して直接反復する必要がないため、insertion_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_のエントリを簡単に参照できます。しかし、私はこれらの設計問題を演習として残します:-)。

更新:私は、insertion_order_にマップとベクターを使用する効率について何か言うべきだと思います

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

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


「挿入ID」でアイテムを取得する必要がある場合は、マップアプローチも適しています。たとえば、5番目に挿入されたアイテムが必要な場合は、キー5(counter_の開始位置に応じて4)を使用して、insertion_orderでルックアップを実行します。ベクトルアプローチでは、5番目のアイテムが削除された場合、実際には6番目のアイテムが挿入されます。
トム

2

ここでは、ブーストのマルチインデックスを使用せず、唯一の標準テンプレートライブラリを必要とソリューションです:
あなたは使用することができますstd::map<std::string,int>;し、vector <data>;どこマップであなたは、挿入順にベクトルとベクトルのデータを格納するデータの位置のインデックスを格納します。ここで、データへのアクセスは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
}

1

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


1

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


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


-1

挿入呼び出しで増加するペア(str、int)と静的intのマップは、データのペアにインデックスを付けます。おそらくインデックス()メンバーを持つ静的なint valを返すことができる構造体を入れますか?


2
例を追加する必要があります。
m02ph3u5 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.