Một bản đồ std :: theo dõi thứ tự chèn?


113

Tôi hiện có một std::map<std::string,int>lưu trữ giá trị số nguyên cho một mã định danh chuỗi duy nhất và tôi thực hiện tra cứu với chuỗi. Nó hầu hết làm những gì tôi muốn, ngoại trừ việc nó không theo dõi thứ tự chèn. Vì vậy, khi tôi lặp lại bản đồ để in ra các giá trị, chúng được sắp xếp theo chuỗi; nhưng tôi muốn chúng được sắp xếp theo thứ tự chèn (đầu tiên).

Tôi đã nghĩ đến việc sử dụng một vector<pair<string,int>>thay thế, nhưng tôi cần phải tra cứu chuỗi và tăng các giá trị số nguyên lên khoảng 10.000.000 lần, vì vậy tôi không biết liệu a std::vectorcó chậm hơn đáng kể hay không.

Có cách nào để sử dụng std::maphoặc có một stdthùng chứa khác phù hợp hơn với nhu cầu của tôi không?

[Tôi đang sử dụng GCC 3.4 và có lẽ tôi không có nhiều hơn 50 cặp giá trị trong std::map].

Cảm ơn.


8
Một phần của thời gian tra cứu nhanh đối với std :: map liên quan đến thực tế là nó được sắp xếp theo thứ tự, vì vậy nó có thể thực hiện tìm kiếm nhị phân. Chỉ không thể có bánh của bạn và ăn nó quá!
bobobobo

1
Cuối cùng bạn đã sử dụng những gì sau đó?
aggsol

Câu trả lời:


56

Nếu bạn chỉ có 50 giá trị trong std :: map, bạn có thể sao chép chúng vào std :: vector trước khi in ra và sắp xếp qua std :: sort bằng cách sử dụng functor thích hợp.

Hoặc bạn có thể sử dụng boost :: multi_index . Nó cho phép sử dụng một số chỉ mục. Trong trường hợp của bạn, nó có thể giống như sau:

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;

Thật tuyệt! Boost thậm chí còn có một bộ chọn thành viên để thực hiện công việc!
xtofl

2
Vâng, multi_index là tính năng yêu thích của tôi trong thúc đẩy :)
Kirill V. Lyadvinsky

3
@Kristo: vấn đề không phải về kích thước vùng chứa, mà là về việc sử dụng lại quá trình triển khai hiện có cho chính xác vấn đề này. Thật là sang trọng. Phải thừa nhận rằng C ++ không phải là một ngôn ngữ chức năng, vì vậy cú pháp hơi phức tạp.
xtofl

4
Từ khi nào lập trình về việc lưu các phím bấm?
GManNickG

1
Cảm ơn vì đã đăng bài này. Có sách "tăng đa chỉ số cho hình nộm" không? Tôi có thể sử dụng nó ...
don morning

25

Bạn có thể kết hợp a std::vectorvới a std::tr1::unordered_map(bảng băm). Đây là một liên kết đến tài liệu của Boost cho unordered_map. Bạn có thể sử dụng vectơ để theo dõi thứ tự chèn và bảng băm để thực hiện tra cứu thường xuyên. Nếu bạn đang thực hiện hàng trăm nghìn lần tra cứu, sự khác biệt giữa O (log n) tìm kiếm std::mapvà O (1) cho bảng băm có thể là đáng kể.

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, Làm thế nào mà điều đó khiến câu trả lời của tôi không hữu ích và do đó xứng đáng được phản đối? Có phải mã của tôi không chính xác theo một cách nào đó?
Michael Kristofik

Đây là cách tốt nhất để làm điều đó. Chi phí bộ nhớ rất rẻ (chỉ với 50 chuỗi!), Cho phép std::maphoạt động như nó phải làm (tức là bằng cách tự phân loại khi bạn chèn) và có thời gian chạy nhanh. (Tôi đọc này sau khi viết phiên bản của tôi, nơi tôi đã sử dụng std :: danh sách!)
bobobobo

Tôi nghĩ rằng std :: vector hoặc std :: list là một vấn đề của sở thích, và không rõ ràng cái nào tốt hơn. (Vector có quyền truy cập ngẫu nhiên không cần thiết, cũng có bộ nhớ liền kề, cũng không cần thiết. Danh sách lưu trữ thứ tự mà không cần chi phí của một trong hai tính năng đó, ví dụ như phân bổ lại trong khi phát triển).
Oliver Schönrock

14

Giữ một song song list<string> insertionOrder.

Khi đến lúc in, hãy lặp lại danh sách và tra cứu bản đồ .

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

1
Đây cũng là suy nghĩ đầu tiên của tôi, nhưng nó sao chép các khóa trong một thùng chứa thứ hai, phải không? Trong trường hợp khóa std :: string không rực rỡ, phải không?
Oliver Schönrock

2
@OliverSchonrock Kể từ C ++ 17, bạn có thể sử dụng std::string_viewcho các khóa của bản đồ tham chiếu đến std::stringtrong insertionOrderdanh sách. Điều này tránh sao chép nhưng bạn cần phải cẩn thận rằng các insertionOrderphần tử tồn tại lâu hơn các phím trong bản đồ đề cập đến chúng.
flyx

Tôi đã kết thúc việc viết một vùng chứa tích hợp bản đồ và danh sách thành một: codereview.stackexchange.com/questions/233177/… Không trùng lặp
Oliver Schönrock

10

Tessil có một triển khai rất hay của bản đồ có thứ tự (và bộ) là giấy phép MIT. Bạn có thể tìm thấy nó ở đây: bản đồ đã đặt hàng

Ví dụ bản đồ

#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

Nếu bạn cần cả hai chiến lược tra cứu, bạn sẽ có hai vùng chứa. Bạn có thể sử dụng a vectorvới ( intcác) giá trị thực của mình và đặt một map< string, vector< T >::difference_type> bên cạnh nó, trả về chỉ mục vào vectơ.

Để hoàn thành tất cả những điều đó, bạn có thể gói gọn cả hai trong một lớp.

Nhưng tôi tin rằng boost có một vùng chứa với nhiều chỉ số.


3

Những gì bạn muốn (mà không cần dùng đến Boost) là những gì tôi gọi là "băm có thứ tự", về cơ bản là một bản trộn của một băm và một danh sách được liên kết với chuỗi hoặc khóa số nguyên (hoặc cả hai cùng một lúc). Hàm băm có thứ tự duy trì thứ tự của các phần tử trong quá trình lặp lại với hiệu suất tuyệt đối của một hàm băm.

Tôi đã tập hợp một thư viện đoạn mã C ++ tương đối mới để lấp đầy những gì tôi xem là lỗ hổng trong ngôn ngữ C ++ dành cho các nhà phát triển thư viện C ++. Đến đây:

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

Vồ lấy:

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

Nếu dữ liệu do người dùng kiểm soát sẽ được đưa vào hàm băm, bạn cũng có thể muốn:

security/security_csprng.cpp
security/security_csprng.h

Gọi nó:

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

Tôi đã chạy vào chuỗi SO này trong giai đoạn nghiên cứu của mình để xem liệu bất kỳ thứ gì giống như OrderedHash đã tồn tại mà không yêu cầu tôi phải thả vào một thư viện khổng lồ hay không. Tôi đã thất vọng. Vì vậy, tôi đã viết của riêng tôi. Và bây giờ tôi đã chia sẻ nó.


2

Bạn không thể làm điều đó với bản đồ, nhưng bạn có thể sử dụng hai cấu trúc riêng biệt - bản đồ và vectơ và giữ cho chúng được đồng bộ hóa - đó là khi bạn xóa khỏi bản đồ, hãy tìm và xóa phần tử khỏi vectơ. Hoặc bạn có thể tạo một map<string, pair<int,int>>- và trong cặp của bạn lưu trữ kích thước () của bản đồ khi chèn để ghi lại vị trí, cùng với giá trị của int và sau đó khi bạn in, hãy sử dụng thành viên vị trí để sắp xếp.


2

Một cách khác để thực hiện điều này là mapthay vì a vector. Tôi sẽ chỉ cho bạn cách tiếp cận này và thảo luận về sự khác biệt:

Chỉ cần tạo một lớp có hai bản đồ đằng sau hậu trường.

#include <map>
#include <string>

using namespace std;

class SpecialMap {
  // usual stuff...

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

Sau đó, bạn có thể hiển thị một trình lặp với trình lặp theo data_thứ tự thích hợp. Cách bạn thực hiện là lặp đi lặp lại insertion_order_và đối với mỗi phần tử bạn nhận được từ lần lặp đó, hãy thực hiện tra cứu data_với giá trị từinsertion_order_

Bạn có thể sử dụng hiệu quả hơn hash_mapcho inserttion_order vì bạn không quan tâm đến việc lặp lại trực tiếp insertion_order_.

Để thực hiện chèn, bạn có thể có một phương pháp như sau:

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

Có rất nhiều cách để bạn có thể làm cho thiết kế tốt hơn và lo lắng về hiệu suất, nhưng đây là một khung tốt để giúp bạn bắt đầu triển khai chức năng này của riêng mình. Bạn có thể tạo mẫu và thực sự có thể lưu trữ các cặp dưới dạng giá trị trong data_ để bạn có thể dễ dàng tham chiếu mục nhập trong inserttion_order_. Nhưng tôi để những vấn đề thiết kế này như một bài tập :-).

Cập nhật : Tôi cho rằng tôi nên nói điều gì đó về hiệu quả của việc sử dụng bản đồ so với vector cho insert_order_

  • tra cứu trực tiếp vào dữ liệu, trong cả hai trường hợp đều là O (1)
  • phần chèn trong cách tiếp cận vectơ là O (1), phần chèn trong cách tiếp cận bản đồ là O (logn)
  • số xóa trong phương pháp tiếp cận vectơ là O (n) vì bạn phải quét mục cần xóa. Với cách tiếp cận bản đồ, chúng là O (logn).

Có thể nếu bạn không sử dụng quá nhiều phép xóa, bạn nên sử dụng phương pháp tiếp cận vector. Cách tiếp cận bản đồ sẽ tốt hơn nếu bạn hỗ trợ một thứ tự khác (như mức độ ưu tiên) thay vì thứ tự chèn.


Cách tiếp cận bản đồ cũng tốt hơn nếu bạn cần lấy các mục bằng "id chèn". Ví dụ: nếu bạn muốn mục được chèn vào thứ 5, bạn thực hiện tra cứu trong insert_order với phím 5 (hoặc 4, tùy thuộc vào nơi bạn bắt đầu counter_). Với cách tiếp cận vector, nếu mục thứ 5 bị xóa, bạn sẽ thực sự nhận được mục thứ 6 đã được chèn.
Tom,

2

Đây là giải pháp chỉ yêu cầu thư viện mẫu tiêu chuẩn mà không cần sử dụng đa chỉ mục của boost:
Bạn có thể sử dụng std::map<std::string,int>;vector <data>;nơi trong bản đồ bạn lưu chỉ mục vị trí của dữ liệu trong vectơ và vectơ lưu trữ dữ liệu theo thứ tự chèn. Ở đây quyền truy cập vào dữ liệu có độ phức tạp O (log n). hiển thị dữ liệu theo thứ tự chèn có độ phức tạp O (n). việc chèn dữ liệu có độ phức tạp O (log n).

Ví dụ:

#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

Điều này có phần liên quan đến câu trả lời của Faisals. Bạn chỉ có thể tạo một lớp bao bọc xung quanh bản đồ và vectơ và dễ dàng giữ cho chúng được đồng bộ hóa. Việc đóng gói đúng cách sẽ cho phép bạn kiểm soát phương thức truy cập và do đó sử dụng vùng chứa nào ... vectơ hoặc bản đồ. Điều này tránh sử dụng Boost hoặc bất kỳ thứ gì tương tự.


1

Một điều bạn cần xem xét là số lượng nhỏ các phần tử dữ liệu bạn đang sử dụng. Có thể là sẽ nhanh hơn nếu chỉ sử dụng vector. Có một số chi phí trong bản đồ có thể khiến việc tra cứu trong các tập dữ liệu nhỏ tốn kém hơn so với vector đơn giản. Vì vậy, nếu bạn biết rằng bạn sẽ luôn sử dụng cùng một số phần tử, hãy thực hiện một số đo điểm chuẩn và xem hiệu suất của bản đồ và vectơ có đúng như bạn nghĩ hay không. Bạn có thể thấy tra cứu trong một vectơ chỉ có 50 phần tử gần giống với bản đồ.


1

// Nên giống như người đàn ông này!

// Điều này duy trì độ phức tạp của chèn là O (logN) và xóa cũng là 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

Một bản đồ của cặp (str, int) và int tĩnh tăng lên trên các cuộc gọi chèn chỉ mục các cặp dữ liệu. Đặt trong một cấu trúc có thể trả về int val tĩnh với một thành viên index ()?


2
Bạn nên thêm một ví dụ.
m02ph3u5
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.