4

我有一个数据结构,

<Book title>, <Author>, and <rate>

由于书名或作者可以重复,我想构建一个复合键。(假设我不能制作额外的唯一键,例如 ID)

由于数据非常庞大,为了速度,我使用 GCC unordered_map,我构建了这样的结构:

typedef pair<string, string> keys_t
typedef unordered_map<keys_t, double> map_t;

一般来说,一切正常,但是当我想引用一个特定的键时就会出现问题。

例如,假设我想在标题为“数学”的书籍中找到评分最高的书籍,或者我想找到“托尔斯泰”书籍的平均收视率。
在这种情况下,这变得非常麻烦,因为我不能只引用其中一个密钥对。

我碰巧找到了,boost::multi_index但我在理解这些文件时遇到了一些麻烦。有没有人对此有一些想法或指导?

制作多个索引的解决方案,multi_index 的简洁示例,任何其他方法等。任何帮助将不胜感激。

谢谢你。

4

4 回答 4

3

我想出了如何使用boost::multi_index 我引用了这段代码:Boost multi_index composition keys using MEM_FUN

这是我的代码供您参考。

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/composite_key.hpp>
#include <boost/multi_index/member.hpp>
#include <iostream>
#include <string>

using namespace boost::multi_index;
using namespace std;

class Book {
public:
    Book(const string &lang1, const string &lang2, const double &value) : m_lang1(lang1) , m_lang2(lang2) , m_value(value) {}

    friend std::ostream& operator << (ostream& os,const Book& n)    {
        os << n.m_lang1 << " " << n.m_lang2 << " " << n.m_value << endl;
        return os;
    }

    const string &lang1() const { return m_lang1; }
    const string &lang2() const { return m_lang2; }
    const double &value() const { return m_value; }
private:
    string m_lang1, m_lang2;
    double m_value;
};

// These will be Tag names
struct lang1 {};
struct lang2 {};
struct value {};

typedef multi_index_container <
    Book, 
    indexed_by<
        ordered_non_unique<tag<lang1>, BOOST_MULTI_INDEX_CONST_MEM_FUN( Book, const string &, lang1)
        >,
        ordered_non_unique<tag<lang2>, BOOST_MULTI_INDEX_CONST_MEM_FUN(Book, const string &, lang2)
        >,
        ordered_non_unique<tag<value>, BOOST_MULTI_INDEX_CONST_MEM_FUN(Book, const double &, value), greater<double>
        >,
        ordered_unique<
            // make as a composite key with Title and Author
            composite_key<
                Book,
                BOOST_MULTI_INDEX_CONST_MEM_FUN(Book, const string &, lang1),
                BOOST_MULTI_INDEX_CONST_MEM_FUN(Book, const string &, lang2)
            >
        >
    >
> Book_set;

// Indices for iterators
typedef Book_set::index<lang1>::type Book_set_by_lang1;
typedef Book_set::index<lang2>::type Book_set_by_lang2;
typedef Book_set::index<value>::type Book_set_by_value;

int main() {

    Book_set books;
    books.insert(Book("Math", "shawn", 4.3));
    books.insert(Book("Math", "john", 4.2));
    books.insert(Book("Math2", "abel", 3.8));
    books.insert(Book("Novel1", "Tolstoy", 5.0));
    books.insert(Book("Novel1", "Tolstoy", 4.8)); // This will not be inserted(duplicated)
    books.insert(Book("Novel2", "Tolstoy", 4.2));
    books.insert(Book("Novel3", "Tolstoy", 4.4));
    books.insert(Book("Math", "abel", 2.5));
    books.insert(Book("Math2", "Tolstoy", 3.0));

    cout << "SORTED BY TITLE" << endl;
    for (Book_set_by_lang1::iterator itf = books.get<lang1>().begin(); itf != books.get<lang1>().end(); ++itf)
        cout << *itf;

    cout << endl<<"SORTED BY AUTHOR" << endl;
    for (Book_set_by_lang2::iterator itm = books.get<lang2>().begin(); itm != books.get<lang2>().end(); ++itm)
        cout << *itm;

    cout << endl<<"SORTED BY RATING" << endl;
    for (Book_set_by_value::iterator itl = books.get<value>().begin(); itl != books.get<value>().end(); ++itl)
        cout << *itl;

    // Want to see Tolstoy's books? (in descending order of rating)
    cout << endl;
    Book_set_by_lang2::iterator mitchells = books.get<lang2>().find("Tolstoy");
    while (mitchells->lang2() == "Tolstoy")
        cout << *mitchells++;

    return 0;
}

感谢所有发表评论的人!

于 2012-03-05T17:37:40.147 回答
1

我在类似情况下所做的是使用单个容器来包含对象并std::multiset<ObjectType const*, CmpType>为每个可能的索引分开;插入时,我会做一个push_back,然后从 恢复地址back(),并将其插入每个std::set. (在你的情况std::unordered_setstd::unordered_multiset会更好:在我的情况下,不仅顺序很重要,而且我也无法访问最近的编译器unordered_set。)

请注意,这假设对象在容器中后是不可变的。如果你要改变其中的一个,你可能应该从所有集合中提取它,进行修改,然后重新插入它。

这也假设主容器类型永远不会使对象的指针和引用无效;就我而言,我预先知道最大尺寸,所以我可以做 areserve()并使用std::vector. 如果做不到这一点,您可以使用std::deque,或简单地使用 astd::map 作为主(完整)键。

即使这样也需要访问密钥中的完整元素。从您的帖子中不清楚这是否足够——“以数学为标题的书籍”让我觉得您可能需要在标题中进行子字符串搜索(并且“托尔斯泰”应该与“列奥托尔斯泰”匹配吗?)。如果您想匹配任意子字符串,您的多重集将非常非常大(因为您将插入所有可能的子字符串作为条目),或者您将进行线性搜索。(在一个长期运行的系统中,条目没有改变,它可能值得妥协:在第一次请求子字符串时进行线性搜索,但将结果缓存在一个多重集中,以便下一次,你可以找到它们很快。很可能人们会经常使用相同的子字符串,例如“math”用于任何标题中带有“math”的书。)

于 2012-03-02T11:50:15.957 回答
1

有一篇关于同一主题的文章:http: //marknelson.us/2011/09/03/hash-functions-for-c-unordered-containers/

作者 Mark Nelson 试图做类似的事情:“使用简单的类或结构来保存人的名字”,基本上他使用一对作为他的 unordered_map 的键(就像你一样):

typedef pair<string,string> Name;

int main(int argc, char* argv[])
{
    unordered_map<Name,int> ids;
    ids[Name("Mark", "Nelson")] = 40561;
    ids[Name("Andrew","Binstock")] = 40562;
    for ( auto ii = ids.begin() ; ii != ids.end() ; ii++ )
        cout << ii->first.first
        << " "
        << ii->first.second
        << " : "
        << ii->second
        << endl;
        return 0;
}

他意识到 unordered_map 不知道如何为给定的 std::pair 键类型创建散列。因此,他演示了 4 种创建用于 unordered_map 的哈希函数的方法。

于 2012-03-12T12:48:50.230 回答
-1

如果是不频繁的操作,您可以搜索该值。

for(auto& p : m)
{
     if(p.second.name==name_to_find)
     {
          //you now have the element
     }
}

但是,如果地图很大,这将是一个问题,因为它将是一个线性过程而不是 O(log n),这是一个问题,因为地图本身就很慢。

于 2012-03-02T11:19:05.810 回答