삽입 순서를 추적하는 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

답변:


56

std :: map에 50 개의 값만있는 경우 인쇄하기 전에 std :: vector에 복사하고 적절한 functor를 사용하여 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

2
예, multi_index는 제가 부스트에서 가장 좋아하는 기능입니다. :)
Kirill V. Lyadvinsky

3
@Kristo : 컨테이너 크기가 아니라이 문제에 대해 기존 구현을 재사용하는 것입니다. 고급 스럽습니다. 물론 C ++는 기능적 언어가 아니므로 구문이 다소 정교합니다.
xtofl

4
언제부터 키 입력 저장에 대해 프로그래밍 했습니까?
GManNickG

1
게시 해 주셔서 감사합니다. "인형을위한 다중 색인 증가"책이 있습니까? 나는 ... 그것을 사용할 수 있습니다
밝은 돈

25

a std::vectorstd::tr1::unordered_map(해시 테이블)을 결합 할 수 있습니다 . 여기에 링크의 부스트의 문서 에 대한이 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, 어떻게 내 답변이 도움이되지 않아서 반대 투표를 할 가치가 있습니까? 내 코드가 어떤 식 으로든 올바르지 않습니까?
Michael Kristofik

이것이 최선의 방법입니다. 매우 저렴한 메모리 비용 (단지 50 개 문자열), std::map예상대로 작동 (즉, 삽입 할 때 자체 정렬) 할 수 있으며 런타임이 빠릅니다. (나는 표준 : : 목록을 사용하여 내 버전을 작성 후이 글을 읽을!)
bobobobo

std :: vector 또는 std :: list는 취향의 문제이며 어느 것이 더 나은지 명확하지 않습니다. (벡터는 필요하지 않은 랜덤 액세스를 가지고 있으며 또한 필요하지 않은 연속 메모리를 가지고 있습니다. List는 두 기능 중 하나를 사용하지 않고 주문을 저장합니다 (예 : 성장하는 동안 재 할당)).
Oliver Schönrock 19

14

평행을 유지하십시오 list<string> insertionOrder.

인쇄 할 시간이되면 목록을 반복 하고 지도를 조회합니다 .

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

1
이것은 내 첫 번째 생각이기도했지만 두 번째 컨테이너의 키를 복제합니다. 훌륭하지 않은 std :: string 키의 경우, 맞습니까?
Oliver Schönrock 19

2
@OliverSchonrock C ++ 17부터는 목록 에서를 std::string_view참조하는 맵의 키에 사용할 수 있습니다 . 이렇게하면 복사를 피할 수 있지만 요소가 참조하는 맵의 키보다 오래 지속 되도록주의해야 합니다. std::stringinsertionOrderinsertionOrder
flyx

: 나는 1로지도와 목록을 통합 컨테이너 작성 결국 codereview.stackexchange.com/questions/233177/... 없음 중복
올리버 Schönrock

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

두 조회 전략이 모두 필요한 경우 두 개의 컨테이너로 끝납니다. vector실제 값 ( ints) 과 함께 a 를 사용 하고map< string, vector< T >::difference_type> 옆 인덱스를 벡터에 반환 할 수 있습니다.

이 모든 것을 완료하려면 두 가지를 하나의 클래스로 캡슐화 할 수 있습니다.

하지만 부스트에는 여러 인덱스 가있는 컨테이너가 있다고 생각 합니다.


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

맵으로는 그렇게 할 수 없지만 맵과 벡터라는 두 개의 개별 구조를 사용하여 동기화 된 상태로 유지할 수 있습니다. 즉, 맵에서 삭제하고 벡터에서 요소를 찾아서 삭제할 수 있습니다. 또는 map<string, pair<int,int>>-를 만들고 쌍에 삽입시지도의 size ()를 int의 값과 함께 위치를 기록하고 인쇄 할 때 위치 멤버를 사용하여 정렬 할 수 있습니다.


2

이를 구현하는 또 다른 방법 mapvector . 이 접근 방식을 보여주고 차이점에 대해 논의하겠습니다.

배후에 두 개의 맵이있는 클래스를 만드십시오.

#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.NET을 직접 반복하는 데 신경 쓰지 않기 때문에 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;
}

디자인을 더 좋게 만들고 성능에 대해 걱정할 수있는 방법은 많이 있지만,이 기능을 직접 구현할 수있는 좋은 스켈레톤입니다. 템플릿으로 만들 수 있으며, 실제로 insert_order_의 항목을 쉽게 참조 할 수 있도록 data_에 값으로 쌍을 저장할 수 있습니다. 그러나 나는 이러한 디자인 문제를 연습으로 남겨 둡니다. :-).

최신 정보 : insert_order_에 대한 맵 대 벡터 사용의 효율성에 대해 말해야한다고 생각합니다.

  • 데이터를 직접 조회합니다. 두 경우 모두 O (1)
  • 벡터 접근 방식의 삽입은 O (1), 맵 접근 방식의 삽입은 O (logn)
  • 벡터 접근법에서 삭제는 제거 할 항목을 스캔해야하기 때문에 O (n)입니다. 맵 접근 방식에서는 O (logn)입니다.

삭제를 많이 사용하지 않으려면 벡터 접근 방식을 사용해야합니다. 게재 순서 대신 다른 순서 (예 : 우선 순위)를 지원하는 경우지도 접근 방식이 더 좋습니다.


맵 접근 방식은 "삽입 ID"로 항목을 가져와야하는 경우에도 더 좋습니다. 예를 들어 5 번째로 삽입 된 항목을 원하는 경우 키 5 (또는 counter_를 시작하는 위치에 따라 4)를 사용하여 insertion_order에서 조회를 수행합니다. 벡터 접근 방식을 사용하면 5 번째 항목이 삭제되면 실제로 삽입 된 6 번째 항목을 얻게됩니다.
Tom

2

여기 부스트의 multiindex를 사용하지 않고 표준 템플릿 라이브러리가 필요 솔루션입니다 :
당신은 사용할 수 있습니다 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

이것은 Faisals 답변과 다소 관련이 있습니다. 지도와 벡터 주위에 래퍼 클래스를 만들고 쉽게 동기화 할 수 있습니다. 적절한 캡슐화를 통해 액세스 방법을 제어 할 수 있으므로 사용할 컨테이너 (벡터 또는지도)를 사용할 수 있습니다. 이것은 Boost 또는 이와 유사한 것을 사용하지 않습니다.


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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.