Ekleme sırasını takip eden bir std :: map?


113

Şu anda std::map<std::string,int>benzersiz bir dize tanımlayıcısına bir tamsayı değeri depolayan bir cihazım var ve dizeye bakıyorum. Ekleme siparişini takip etmemesi dışında çoğunlukla istediğimi yapıyor. Dolayısıyla, değerleri yazdırmak için haritayı yinelediğimde, bunlar dizeye göre sıralanır; ancak (ilk) ekleme sırasına göre sıralanmalarını istiyorum.

Bunun vector<pair<string,int>>yerine a kullanmayı düşündüm , ancak dizgeye bakmam ve tamsayı değerlerini yaklaşık 10.000.000 kez artırmam gerekiyor, böylece a'nın std::vectorönemli ölçüde daha yavaş olup olmayacağını bilmiyorum .

Kullanmanın bir yolu var mı std::mapveya stdihtiyacıma daha uygun başka bir kap var mı?

[GCC 3.4'teyim ve muhtemelen 50'den fazla değerim yok std::map].

Teşekkürler.


8
Std :: map için hızlı arama süresinin bir kısmı, ikili arama yapabilmesi için sıraya göre sıralanması ile ilgilidir. Sadece pastanı yiyip de yiyemezsin!
bobobobo

1
O zamanlar ne kullanmaya başladın?
aggsol

Yanıtlar:


56

Eğer std :: map'de sadece 50 değeriniz varsa, bunları yazdırmadan önce std :: vector'a kopyalayabilir ve uygun functor kullanarak std :: sort yoluyla sıralayabilirsiniz.

Veya boost :: multi_index'i kullanabilirsiniz . Birkaç indeks kullanımına izin verir. Sizin durumunuzda aşağıdaki gibi görünebilir:

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;

Bu harika! Boost'un işi yapacak bir üye seçicisi bile var!
xtofl

2
Evet, multi_index, boost'ta en sevdiğim özelliktir :)
Kirill V. Lyadvinsky

3
@Kristo: Konu konteyner boyutu değil, bu sorun için mevcut uygulamayı yeniden kullanmakla ilgili. Bu klas. Kuşkusuz, C ++ işlevsel bir dil değildir, bu nedenle sözdizimi biraz ayrıntılıdır.
xtofl

4
Ne zamandan beri tuş vuruşlarını kaydetmekle ilgili programlama yapılıyor?
GManNickG

1
Bunu gönderdiğiniz için teşekkürler. "Aptallar için çoklu endeksi artırma" kitabı var mı? Kullanabilirim ...
parlak don

25

Bir std::vectorile bir std::tr1::unordered_map(karma tablo) birleştirebilirsiniz. İşte bir bağlantı var Boost belgelerine için unordered_map. Ekleme sırasını takip etmek için vektörü ve sık aramaları yapmak için karma tabloyu kullanabilirsiniz. Yüz binlerce arama yapıyorsanız, std::mapkarma tablo için O (log n) araması ve O (1) arasındaki fark önemli olabilir.

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, Bu, cevabımı nasıl yardımcı olmaz ve dolayısıyla olumsuz oylamaya değer yapar? Kodum bir şekilde yanlış mı?
Michael Kristofik

Bunu yapmanın en iyi yolu budur. Çok ucuz bellek maliyeti (sadece 50 dizge için!) std::mapOlması gerektiği gibi çalışmasına izin verir (yani, eklerken kendini sıralayarak) ve hızlı çalışma süresine sahiptir. (Bunu std :: list kullandığım
sürümümü

Std :: vector veya std :: list'in bir zevk meselesi olduğunu düşünüyorum ve hangisinin daha iyi olduğu net değil. (Vektör gerekli olmayan rastgele erişime sahiptir, ayrıca gerekli olmayan bitişik belleğe sahiptir. Liste, siparişi bu 2 özellikten herhangi birinin masrafı olmadan depolar, örneğin büyürken yeniden tahsisler).
Oliver Schönrock

14

Paralel tutun list<string> insertionOrder.

Yazdırma zamanı geldiğinde, listeyi yineleyin ve haritaya arama yapın .

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

1
Bu benim de ilk düşüncemdi, ancak anahtarları 2. bir kapta kopyalıyor, değil mi? Bir std :: string anahtarı olması durumunda, bu mükemmel değil, değil mi?
Oliver Schönrock

2
C @OliverSchonrock olarak ++ 17 kullanabilirsiniz std::string_viewatıfta haritanın tuşları için std::stringde insertionOrderlisteye. Bu, kopyalamayı önler, ancak insertionOrderharitadaki öğelerin onlara atıfta bulunan anahtarlardan daha uzun ömürlü olmasına dikkat etmeniz gerekir .
flyx

Harita ve listeyi tek bir içine entegre eden bir kapsayıcı yazdım : codereview.stackexchange.com/questions/233177/… Yineleme yok
Oliver Schönrock

10

Tessil, MIT lisansı olan sıralı harita (ve set) için çok güzel bir uygulamaya sahiptir. Burada bulabilirsiniz: sıralı harita

Harita örneği

#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

Her iki arama stratejisine de ihtiyacınız varsa, iki kapsayıcıya sahip olacaksınız. vectorGerçek değer ( intler) inizle a map< string, vector< T >::difference_type> kullanabilir ve yanına bir yazarak indeksi vektöre geri döndürebilirsiniz .

Tüm bunları tamamlamak için her ikisini de tek bir sınıfta özetleyebilirsiniz.

Ancak boost'un birden fazla endeksi olan bir kapsayıcıya sahip olduğuna inanıyorum .


3

İstediğiniz şey (Boost'a başvurmadan) benim "sıralı karma" olarak adlandırdığım şeydir, bu esasen bir karma ve dizge veya tamsayı anahtarları (veya aynı anda her ikisi) içeren bağlantılı bir listenin bir karışımıdır. Sıralı bir hash, bir hash'in mutlak performansıyla yineleme sırasında öğelerin sırasını korur.

C ++ kitaplık geliştiricileri için C ++ dilinde delikler olarak gördüğüm şeyleri dolduran nispeten yeni bir C ++ snippet kitaplığı oluşturuyorum. Buraya gidin:

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

Kapmak:

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

Kullanıcı kontrollü veriler karmaya yerleştirilecekse, şunları da isteyebilirsiniz:

security/security_csprng.cpp
security/security_csprng.h

Çağır:

#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();
}

Bu SO iş parçacığına araştırma aşamam sırasında, OrderedHash gibi bir şeyin büyük bir kütüphaneye girmeme gerek kalmadan zaten var olup olmadığını görmek için karşılaştım. Hayal kırıklığına uğramıştım. Ben de kendim yazdım. Ve şimdi bunu paylaştım.


2

Bunu bir harita ile yapamazsınız, ancak iki ayrı yapı kullanabilirsiniz - harita ve vektör ve onları senkronize halde tutun - yani haritadan sildiğinizde, elemanı vektörden bulup silersiniz. Ya da bir oluşturabilir map<string, pair<int,int>>- ve çiftinizde haritanın boyutunu () kaydederek int değerinin yanı sıra kaydedebilir ve yazdırdığınızda, sıralamak için konum üyesini kullanabilirsiniz.


2

Bunu uygulamanın başka bir yolu da a mapyerine a kullanmaktır vector. Size bu yaklaşımı göstereceğim ve farklılıkları tartışacağım:

Sadece arka planda iki harita bulunan bir sınıf oluşturun.

#include <map>
#include <string>

using namespace std;

class SpecialMap {
  // usual stuff...

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

Daha sonra bir yineleyiciyi data_doğru sırada yineleyiciye gösterebilirsiniz. Bunu yapmanın yolu, yinelemektir insertion_order_ve bu yinelemeden elde ettiğiniz her öğe için data_, aşağıdaki değerle bir arama yapın :insertion_order_

hash_mapDoğrudan yinelemeyi önemsemediğiniz için insertion_order için daha verimli olanı kullanabilirsiniz insertion_order_.

Ekleme yapmak için aşağıdaki gibi bir yönteme sahip olabilirsiniz:

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

Tasarımı daha iyi hale getirmenin ve performans konusunda endişelenmenin pek çok yolu vardır, ancak bu, bu işlevi kendi başınıza uygulamaya başlamanız için iyi bir iskelettir. Bunu şablon haline getirebilirsiniz ve aslında çiftleri data_ içinde değerler olarak depolayabilirsiniz, böylece insertion_order_ içindeki girdiye kolayca başvurabilirsiniz. Ama bu tasarım konularını bir alıştırma olarak bırakıyorum :-).

Güncelleme : Sanırım ekleme_sırası_ için harita ve vektör kullanmanın verimliliği hakkında bir şeyler söylemeliyim

  • doğrudan veri aramaları, her iki durumda da O (1)
  • vektör yaklaşımındaki ekler O (1), harita yaklaşımındaki ekler O (logn)
  • vektör yaklaşımındaki silmeler O (n) 'dir çünkü kaldırılacak öğeyi taramanız gerekir. Harita yaklaşımı ile bunlar O (logn) dir.

Belki silmeleri çok fazla kullanmayacaksanız, vektör yaklaşımını kullanmalısınız. Harita yaklaşımı, ekleme siparişi yerine farklı bir sıralamayı (öncelik gibi) destekliyor olsaydınız daha iyi olurdu.


Öğeleri "ekleme kimliği" ile almanız gerekiyorsa, harita yaklaşımı da daha iyidir. Örneğin, 5. eklenen öğeyi istiyorsanız, 5 tuşuyla (veya 4, counter_'ı nerede başlattığınıza bağlı olarak) insertion_order içinde bir arama yaparsınız. Vektör yaklaşımıyla, 5. öğe silinmiş olsaydı, aslında eklenen 6. öğeyi alırsınız.
Tom

2

Boost'un çoklu dizinini kullanmadan sadece standart şablon kitaplığı gerektiren bir çözüm:
Kullanabilir std::map<std::string,int>;ve vector <data>;haritada vektördeki verilerin konumunun dizinini nerede depolayacağınız ve ekleme sırasındaki vektör deposu verileri kullanabilirsiniz. Burada verilere erişim O (log n) karmaşıklığına sahiptir. verilerin ekleme sırasındaki görüntülenmesi O (n) karmaşıklığına sahiptir. veri ekleme O (log n) karmaşıklığına sahiptir.

Örneğin:

#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

Bu, bir şekilde Faisals cevabıyla ilgilidir. Bir harita ve vektör etrafında bir sarmalayıcı sınıfı oluşturabilir ve bunları kolayca senkronize halde tutabilirsiniz. Doğru kapsülleme, erişim yöntemini ve dolayısıyla hangi kabı kullanacağınızı kontrol etmenize izin verir ... vektör veya harita. Bu, Boost veya benzeri bir şey kullanmaktan kaçınır.


1

Dikkate almanız gereken bir şey, kullandığınız az sayıdaki veri unsurudur. Sadece vektörü kullanmak daha hızlı olabilir. Haritada, daha basit vektöre göre küçük veri kümelerinde arama yapmanın daha pahalı olmasına neden olabilecek bazı ek yükler vardır. Bu nedenle, her zaman aynı sayıda öğeyi kullanacağınızı biliyorsanız, biraz kıyaslama yapın ve haritanın ve vektörün performansının gerçekten düşündüğünüz gibi olup olmadığına bakın. Aramayı, yalnızca 50 öğeli bir vektörde haritanın yakınında bulabilirsin.


1

// Bu adam gibi olmalı!

// Bu, eklemenin karmaşıklığını O (logN) ve silme işleminin de O (logN) olmasını sağlar.

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

Bir çift (str, int) ve ekleme sırasında artan statik int haritası, veri çiftlerini dizinler olarak çağırır. Statik int değerini bir index () üyesi ile döndürebilecek bir yapı mı koyabilirsiniz?


2
Bir örnek eklemelisiniz.
m02ph3u5
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.