Std :: карта, яка відстежує порядок вставки?


113

В даний час у мене є std::map<std::string,int>те, що зберігає ціле значення до унікального ідентифікатора рядка, і я шукаю цей рядок. Він робить переважно те, що я хочу, за винятком того, що він не відстежує порядок вставки. Отже, коли я повторюю карту для друку значень, вони сортуються відповідно до рядка; але я хочу, щоб вони були сортовані за порядком (першої) вставки.

Я думав про використання vector<pair<string,int>>замість цього, але мені потрібно шукати рядок і збільшувати цілі значення приблизно в 100000000 разів, тож я не знаю, чи std::vectorбуде значно повільнішим.

Чи є спосіб використання std::mapчи є інший stdконтейнер, який краще відповідає моїм потребам?

[Я на GCC 3.4, і, мабуть, у мене не більше 50 пар значень std::map].

Дякую.


8
Ну, частина швидкого пошуку часу для std :: map пов'язана з тим, що вона сортується за порядком, тому вона може робити двійковий пошук. Просто не можна мати торт і їсти його теж!
bobobobo

1
Що ви в кінцевому підсумку використовували тоді?
аггсол

Відповіді:


56

Якщо у вас є лише 50 значень в std :: map, ви можете скопіювати їх у std :: vector перед роздруком і сортувати за допомогою std :: sorting за допомогою відповідного функтора.

Або ви можете використовувати 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;

Це чудово! Підвищення навіть має члена-селектора, щоб виконувати цю роботу!
xtofl

2
Так, multi_index - це моя улюблена функція у прискоренні :)
Кирило В. Лядвінський

3
@ Kristo: справа не в розмірі контейнера, а про повторне використання існуючої реалізації саме для цієї проблеми. Це класно. Справді, C ++ не є функціональною мовою, тому синтаксис є дещо складним.
xtofl

4
З тих пір, коли було програмування щодо збереження клавіш?
GManNickG

1
Дякуємо, що опублікували це. Чи існує книга "Мультиіндекс для підвищення муляжів"? Я міг би його використати ...
нехай яскравий

25

Можна комбінувати std::vectorз std::tr1::unordered_map(хеш - таблиці). Ось посилання на документацію Boost для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';
}

4
@xtofl, як це робить мою відповідь не корисною і, таким чином, гідною протидії? Якийсь код якимось чином невірний?
Майкл Кристофік

Це найкращий спосіб зробити це. Дуже дешева вартість пам'яті (всього на 50 струн!), Дозволяє std::mapпрацювати як належить (тобто сортуючи себе під час вставки), і має швидкий час виконання. (Я читав це після написання своєї версії, де я використовував std :: list!)
bobobobo

Я думаю, що std :: vector або std :: список - це питання смаку, і не зрозуміло, що краще. (Вектор має випадковий доступ, який не потрібен, також має суміжну пам'ять, яка також не потрібна. Список зберігає замовлення без витрат жодної з цих двох функцій, наприклад, перерозподіл під час зростання).
Олівер Шенрок

14

Проведіть паралель list<string> insertionOrder .

Коли настав час для друку, повторіть список і зробіть пошук на карті .

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

1
Це теж була моя перша думка, але вона дублює ключі у 2-му контейнері, правда? У разі std :: string ключ, який не є блискучим, правда?
Олівер Шенрок

2
@OliverSchonrock Станом на C ++ 17, ви можете використовувати std::string_viewдля клавіш на карті посилання на список std::stringу insertionOrderсписку. Це дозволяє уникнути копіювання, але потрібно бути обережним, щоб insertionOrderелементи пережили ключі на карті, що посилається на них.
flyx

Я закінчив писати контейнер, який інтегрував карту та список в один: codereview.stackexchange.com/questions/233177/… Немає дублювання
Олівер Шенрок

10

Tessil має дуже хорошу реалізацію замовленої карти (та набору), яка є ліцензією MIT. Ви можете знайти його тут: замовлена ​​карта

Приклад карти

#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

Якщо вам потрібні обидві стратегії пошуку, ви отримаєте два контейнери. Ви можете використовувати a vectorзі своїми фактичними значеннями intта поставити map< string, vector< T >::difference_type> поруч, повертаючи індекс у вектор.

Щоб виконати все це, ви можете інкапсулювати обидва в одному класі.

Але я вважаю, що у 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 під час моєї дослідницької фази, щоб побачити, чи щось подібне OrriedHash вже існувало, не вимагаючи від мене зайти у велику бібліотеку. Я розчарувався. Тому я написав своє. І тепер я поділився цим.


2

Ви не можете зробити це з картою, але ви можете використовувати дві окремі структури - карту та вектор і підтримувати їх синхронізованими - тобто, коли ви видаляєте з карти, знайдіть і видаліть елемент з вектора. Або ви можете створити map<string, pair<int,int>>- і у вашій парі зберігайте розмір () карти при вставці, щоб записати позицію разом із значенням int, а потім під час друку використовуйте член позиції для сортування.


2

Ще один спосіб здійснити це - 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для 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_, щоб ви могли легко посилатися на запис у insert_order_. Але я залишаю ці проблеми дизайну як вправу :-).

Оновлення : Я вважаю, що я повинен сказати щось про ефективність використання карти порівняно з вектором для insertion_order_

  • пошук безпосередньо в дані, в обох випадках - O (1)
  • вставки у векторному підході - O (1), вставки у підході до карти - O (logn)
  • делети у векторному підході є O (n), оскільки вам потрібно сканувати предмет, який потрібно видалити. З картографічним підходом вони є O (вхід).

Можливо, якщо ви не збираєтеся так багато використовувати делетів, вам слід скористатися векторним підходом. Підхід до карти був би кращим, якби ви підтримували інше впорядкування (наприклад, пріоритет) замість порядку вставки.


Підхід до карти також кращий, якщо вам потрібно отримати елементи за допомогою "ідентифікатора вставки". Наприклад, якщо ви хочете, щоб елемент, який було вставлено 5-й, ви робите пошук у insertion_order за допомогою ключа 5 (або 4, залежно від місця запуску counter_). При векторному підході, якщо 5-й елемент буде видалено, ви фактично отримаєте 6-й елемент, який було вставлено.
Том

2

Ось рішення, яке вимагає лише стандартної бібліотеки шаблонів без використання multiindex boost:
Ви можете використовувати 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

Одне, що вам потрібно врахувати, - це невелика кількість елементів даних, які ви використовуєте. Можливо, що швидше буде використовувати просто вектор. На карті є кілька накладних витрат, які можуть призвести до того, що робити пошук у малих наборах даних дорожче, ніж у більш простому векторі. Отже, якщо ви знаєте, що ви завжди будете використовувати однакову кількість елементів, зробіть деякий бенчмаркінг і подивіться, чи ефективність карти та вектора - це те, що ви насправді вважаєте. Ви можете знайти пошук у векторі, у якому лише 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 з членом index (), можливо?


2
Вам слід додати приклад.
m02ph3u5
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.