แผนที่มาตรฐาน :: ที่ติดตามลำดับของการแทรก?


113

ขณะนี้ฉันมีstd::map<std::string,int>ที่เก็บค่าจำนวนเต็มให้กับตัวระบุสตริงที่ไม่ซ้ำกันและฉันค้นหาด้วยสตริง ส่วนใหญ่ทำในสิ่งที่ฉันต้องการยกเว้นว่าจะไม่ติดตามลำดับการแทรก ดังนั้นเมื่อฉันทำแผนที่ซ้ำเพื่อพิมพ์ค่าพวกมันจะถูกจัดเรียงตามสตริง แต่ฉันต้องการให้เรียงลำดับตามลำดับของการแทรก (ครั้งแรก)

ฉันคิดจะใช้ a vector<pair<string,int>>แทน แต่ฉันต้องค้นหาสตริงและเพิ่มค่าจำนวนเต็มประมาณ 10,000,000 ครั้งดังนั้นฉันไม่รู้ว่า a std::vectorจะช้าลงอย่างมากหรือไม่

มีวิธีใช้std::mapหรือมีstdภาชนะอื่นที่เหมาะกับความต้องการของฉันมากกว่านี้หรือไม่?

[ฉันใช้ GCC 3.4 และฉันอาจมีค่าไม่เกิน 50 คู่ในตัวstd::map]

ขอบคุณ.


8
ส่วนหนึ่งของเวลาในการค้นหาอย่างรวดเร็วสำหรับ std :: map นั้นเกี่ยวข้องกับความจริงที่ว่ามันถูกจัดเรียงตามลำดับดังนั้นจึงสามารถทำการค้นหาแบบไบนารีได้ กินเค้กไม่ได้แล้วก็กินด้วย!
bobobobo

1
คุณใช้อะไรในตอนนั้น?
aggsol

คำตอบ:


56

หากคุณมีค่าเพียง 50 ค่าใน std :: map คุณสามารถคัดลอกไปที่ std :: vector ก่อนที่จะพิมพ์และจัดเรียงผ่าน std :: sort โดยใช้ functor ที่เหมาะสม

หรือคุณอาจจะใช้เพิ่ม :: 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
@ คริสโต: มันไม่เกี่ยวกับขนาดคอนเทนเนอร์ แต่เกี่ยวกับการนำการใช้งานที่มีอยู่กลับมาใช้ใหม่สำหรับปัญหานี้ ดีงาม เป็นที่ยอมรับว่า C ++ ไม่ใช่ภาษาที่ใช้งานได้ดังนั้นไวยากรณ์จึงค่อนข้างซับซ้อน
xtofl

4
ตั้งแต่เมื่อใดที่มีการเขียนโปรแกรมเกี่ยวกับการบันทึกจังหวะสำคัญ?
GManNickG

1
ขอบคุณสำหรับการโพสต์สิ่งนี้ มีหนังสือ "boost multi-index for dummies" หรือไม่? ฉันสามารถใช้มันได้ ...
อย่าสดใส

25

คุณอาจรวมstd::vectorกับstd::tr1::unordered_map(ตารางแฮช) นี่คือการเชื่อมโยงไปยังเอกสารของ Boostunordered_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 นั่นทำให้คำตอบของฉันไม่เป็นประโยชน์ได้อย่างไรและจึงควรค่าแก่การโหวตลงคะแนน? รหัสของฉันไม่ถูกต้องหรือไม่?
Michael Kristofik

วิธีนี้เป็นวิธีที่ดีที่สุด ต้นทุนหน่วยความจำถูกมาก (เพียง 50 สตริง!) ช่วยให้std::mapทำงานได้ตามที่ควรจะเป็น (เช่นเรียงลำดับตัวเองเมื่อคุณแทรก) และมีรันไทม์ที่รวดเร็ว (ฉันอ่านสิ่งนี้หลังจากเขียนเวอร์ชันของฉันซึ่งฉันใช้ std :: list!)
bobobobo

ฉันคิดว่า std :: vector หรือ std :: list เป็นเรื่องของรสนิยมและไม่ชัดเจนว่าอันไหนดีกว่ากัน (เวกเตอร์มีการเข้าถึงแบบสุ่มซึ่งไม่จำเป็นนอกจากนี้ยังมีหน่วยความจำที่ต่อเนื่องกันซึ่งไม่จำเป็นด้วยเช่นกัน List จะจัดเก็บคำสั่งซื้อโดยไม่ต้องเสียค่าใช้จ่ายจากคุณสมบัติทั้ง 2 อย่างนั้นเช่นการจัดสรรใหม่ในขณะที่เติบโต)
Oliver Schönrock

14

list<string> insertionOrderให้ขนาน

เมื่อมันเป็นเวลาที่จะพิมพ์ย้ำในรายการและทำในการค้นหาลงในแผนที่

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

1
นี่เป็นความคิดแรกของฉันเหมือนกัน แต่มันซ้ำกันคีย์ในคอนเทนเนอร์ที่ 2 ใช่ไหม ในกรณีของคีย์ std :: string ที่ไม่ยอดเยี่ยมใช่ไหม?
Oliver Schönrock

2
@OliverSchonrock ตั้งแต่ C ++ 17 คุณสามารถใช้std::string_viewสำหรับคีย์ของแผนที่ที่อ้างถึงstd::stringในinsertionOrderรายการ วิธีนี้หลีกเลี่ยงการคัดลอก แต่คุณต้องระวังว่าinsertionOrderองค์ประกอบนั้นอยู่ได้นานกว่าคีย์ในแผนที่ที่อ้างถึง
บิน

ฉันลงเอยด้วยการเขียนคอนเทนเนอร์ซึ่งรวมแผนที่และรายการไว้ในที่เดียว: codereview.stackexchange.com/questions/233177/… ไม่มีการทำซ้ำ
Oliver Schönrock

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> ข้างๆโดยส่งคืนดัชนีเป็นเวกเตอร์

ในการทำทุกอย่างให้เสร็จสมบูรณ์คุณอาจรวมทั้งสองอย่างไว้ในคลาสเดียว

แต่ฉันเชื่อว่าบูสต์มีคอนเทนเนอร์ที่มีดัชนีหลายตัว


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 นี้ในระหว่างขั้นตอนการวิจัยของฉันเพื่อดูว่ามีอะไรเช่น OrderHash อยู่แล้วหรือไม่โดยไม่ต้องให้ฉันเข้าไปในห้องสมุดขนาดใหญ่ ฉันรู้สึกผิดหวัง. เลยเขียนของตัวเอง และตอนนี้ฉันได้แบ่งปันแล้ว


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_ เพื่อให้คุณสามารถอ้างอิงรายการใน insertion_order_ ได้อย่างง่ายดาย แต่ฉันปล่อยให้ปัญหาการออกแบบเหล่านี้เป็นแบบฝึกหัด :-)

อัปเดต : ฉันคิดว่าฉันควรจะพูดอะไรเกี่ยวกับประสิทธิภาพของการใช้แผนที่เทียบกับเวกเตอร์สำหรับ insertion_order_

  • ค้นหาข้อมูลโดยตรงในทั้งสองกรณีคือ O (1)
  • ส่วนแทรกในแนวทางเวกเตอร์คือ O (1) ส่วนแทรกในแนวทางแผนที่คือ O (บันทึก)
  • การลบในแนวทางเวกเตอร์คือ 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

สิ่งนี้ค่อนข้างเกี่ยวข้องกับคำตอบของ Faisals คุณสามารถสร้างคลาส Wrapper รอบ ๆ แผนที่และเวกเตอร์และทำให้ตรงกันได้อย่างง่ายดาย การห่อหุ้มที่เหมาะสมจะช่วยให้คุณควบคุมวิธีการเข้าถึงและด้วยเหตุนี้คอนเทนเนอร์ที่จะใช้ ... เวกเตอร์หรือแผนที่ หลีกเลี่ยงการใช้ 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 คงที่ด้วยสมาชิก index () บางที?


2
คุณควรเพิ่มตัวอย่าง
m02ph3u5
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.