【问题标题】:iterating over an STL container using element keys使用元素键迭代 STL 容器
【发布时间】:2014-06-26 22:37:29
【问题描述】:

我需要一些帮助来选择一种有效的算法来将向量中的元素放入预排序的桶中 - 或者理想地输出迭代器范围(因为我认为它们很有效)。下面的示例完全是人为的,但其想法是使用元素的键来确定输出桶。我不是在问如何按顺序排序,因为这是一个非常简单的简单调用问题(它可以根据键工作并重新排序元素)

std::sort(testVec.begin(), testVec.end(), comparator);

我在 coliru 上放了一个live example,它很容易修改和修复(不是那么容易,否则我不会问这个问题)。我也可以浏览这个排序列表中的元素,虽然键值相同,但将其附加到一个新的存储桶中,但我正在寻找更像 STL 的东西,现在上面闻起来有点像不得已而为 hack,最终的解决方案也需要高效,因为 testVec 可能很大并且对象的大小也很大。我不想修改 testvec - 所以它应该是不可变的。

理想情况下,我正在寻找某种能够吐出范围迭代器或同样有效的构造。实际对象很大,因此传递引用或移动它们确实是唯一的选择 - 我的实际对象(相当于 MyStr)是可移动的。某种 foreach 键、应用键谓词或我无法弄清楚的东西是我正在寻找的东西。我对下面的 3 个存储桶进行了硬编码,只是为了展示我需要能够达到的目标 - 这完全是一个 hack。

提前感谢您对此问题的任何帮助

#include <string>
#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
#include <algorithm>

struct MyStr
{
    int key;
    std::string strval;

    MyStr(int key, const std::string& rStrVal) 
        : key(key)
        , strval(rStrVal) 
    {}

    // let stream operators be friend functions instead of members!
    inline friend std::ostream& operator << (std::ostream& os, const MyStr& val) {
        os << "key[" << val.key << "], strval['" << val.strval << "']";
        return os;
    }

    bool operator < (const MyStr& str) const {
        return (key > str.key);
    }
};

int main()
{
    std::vector <MyStr> testVec = {
        MyStr(4, "key 4"), 
        MyStr(3, "key 3"), 
        MyStr(3, "key 3"), 
        MyStr(2, "key 2"), 
        MyStr(2, "key 2"), 
        MyStr(2, "key 2")
    };

    //auto comparator = [](const MyStr& lhs, const MyStr& rhs) {
    //    return lhs.key < rhs.key;
    //};

    std::vector <MyStr> foursBucket;
    std::vector <MyStr> threesBucket;
    std::vector <MyStr> twosBucket;

    auto ostriter = std::ostream_iterator<MyStr>(std::cout, ",");
    std::for_each(testVec.begin(), testVec.end(), 
        [&](const MyStr& next){
            switch (next.key) {
            case 4:
                foursBucket.push_back(next);
                break;
            case 3:
                threesBucket.push_back(next);
                break;
            case 2:
                twosBucket.push_back(next);
                break;
            }
        });
    std::cout << "Elements with Key Value 2" << std::endl;
    std::copy(twosBucket.begin(), twosBucket.end(), ostriter);
    std::cout << std::endl;
    std::cout << "Elements with Key Value 3" << std::endl;
    std::copy(threesBucket.begin(), threesBucket.end(), ostriter);
    std::cout << std::endl;
    std::cout << "Elements with Key Value 4" << std::endl;
    std::copy(foursBucket.begin(), foursBucket.end(), ostriter);
    std::cout << std::endl;
}

产生以下输出

Elements with Key Value 2
key[2], strval['key 2'],key[2], strval['key 2'],key[2], strval['key 2'],
Elements with Key Value 3
key[3], strval['key 3'],key[3], strval['key 3'],
Elements with Key Value 4
key[4], strval['key 4'],

如您所见,结构非常简单,我已经展示了当前如何使用谓词对对象进行排序,但我不知道从众多算法中选择哪种来有效地迭代

【问题讨论】:

  • 您可能只是在寻找类似std::multiset 的东西吗?它将是一个容器,存储不会是连续的,但是如果您可以简单地存储迭代器范围,我不明白您需要不同的容器来做什么。
  • 我需要能够分别处理这些单独的范围中的每一个——这就是我有单独的桶的原因。理想情况下,如果我可以调用一些以输入范围作为参数的魔术谓词 -r lambda 函数,我就完成了,谓词将被调用的次数与唯一键的次数一样多

标签: c++ algorithm sorting c++11 stl


【解决方案1】:

您正在寻找unordered_multimap。它是一个无序的关联容器,会根据键的哈希值将键值对放入桶中(以下示例中为int)。

std::unordered_multimap<int, std::string> 
    mymap{{4, "key 4"}, 
          {3, "key 3"}, 
          {3, "key 3"}, 
          {2, "key 2"}, 
          {2, "key 2"}, 
          {2, "key 2"},
         };

for(auto const& kv : mymap) {
    std::cout << "key: " << kv.first << " value: " << kv.second << '\n';
}

输出:

key: 2 value: key 2
key: 2 value: key 2
key: 2 value: key 2
key: 3 value: key 3
key: 3 value: key 3
key: 4 value: key 4

Live demo


在下面的评论中,您澄清说您收到了vector&lt;MyStr&gt; 输入,并且无法更改容器类型。在这种情况下,请使用 std::equal_range 查找包含特定键的所有元素。

// comparator for equal_range
struct comp
{
    bool operator()(int key, MyStr const& m) const { return m.key < key; }
    bool operator()(MyStr const& m, int key) const { return key < m.key; }
};

// sort the vevctor
std::sort(testVec.begin(), testVec.end());

// search for all elements with key=2
auto range = std::equal_range(testVec.begin(), testVec.end(), 2, comp());

for(auto it = range.first; it != range.second; ++it) {
    std::cout << "key: " << it->key << " value: " << it->strval << '\n';
}

输出:

key: 2 value: key 2
key: 2 value: key 2
key: 2 value: key 2

Live demo


要遍历每个唯一键,最简单的方法是使用std::unique_copy 创建一个仅包含具有唯一键的元素的新容器。然后遍历这个容器并在每个键上使用equal_range

bool operator==(MyStr const& m1, MyStr const& m2) { return m1.key == m2.key; }

// sort the vevctor
std::sort(testVec.begin(), testVec.end());

std::vector<MyStr> unique_keys;
std::unique_copy(testVec.begin(), testVec.end(), std::back_inserter(unique_keys));

for(auto const& u : unique_keys) {
    std::cout << "Searching for key: " << u.key << '\n';
    auto range = std::equal_range(testVec.begin(), testVec.end(), u.key, comp());

    for(auto it = range.first; it != range.second; ++it) {
        std::cout << "key: " << it->key << " value: " << it->strval << '\n';
    }
}

Live demo

如果复制元素的成本很高,并且您宁愿避免创建一个包含唯一元素的新容器,您可以创建自己的输出迭代器来模仿std::back_insert_iterator。它的operator= 实现将采用MyStr const&amp; 参数,但push_back 仅将参数中的键放入唯一键容器中,在这种情况下为vector&lt;int&gt;

另一种方法(我不确定是否可行)可以用来避免修改输入范围并避免将元素复制到新范围,即创建vector&lt;MyStr *&gt;,其中每个元素都指向对应的原始范围内的元素。然后重复上述所有步骤,除了将vector::iterators 传递给算法,而是使用boost::indirect_iterator。此迭代器将对容器中的指针应用额外级别的取消引用,然后算法应该像在 vector&lt;MyStr&gt; 上运行一样工作。

【讨论】:

  • 谢谢 praetorian,但我的函数的实际输入是带有可排序键的元素的固定向量。理想情况下,如果我可以围绕这个向量进行一些 lambda 回调,从而使用与具有特定键的元素相对应的开始/结束范围参数多次调用它,我就完成了。我不知道如何有效地做到这一点。
  • @johnco3 抱歉,我没有正确理解您的问题。我已经使用equal_range 更新了答案,以查找具有特定键的所有元素。
  • 真正的改进! - 我想你快到了,但我还需要一个外部循环来查找所有唯一键(在上面的硬代码 2 中),然后我可以在内部循环中调用你的解决方案,就像我需要调用 std::unique 或其他东西并遍历结果集合中的键,但是这需要对集合进行预排序,这不是一个选项,因为 std::vector 是一个 const& 如果我更愿意避免复制元素
  • @johnco3 equal_range 要求输入范围根据您正在搜索的元素进行分区,在您的情况下,由于您正在搜索所有键,因此意味着输入范围需要排序。 unique(_copy) 要求对整个范围进行排序。您可能可以不复制所有元素,而是创建一个vector&lt;MyStr *&gt;,其中每个元素都指向输入范围中的相应元素。然后使用boost::indirect_iterator 完成我在上面所做的一切(不确定该方法是否有效)。
  • @johnco3 最后,如果您在问题的开头写了上面的两个 cmets,那么现在应该已经回答了。您没有描述您想要做什么,而是描述了everything you'd tried so far。下次小心不要落入那个陷阱(很容易)。
猜你喜欢
  • 1970-01-01
  • 2015-06-06
  • 1970-01-01
  • 2019-09-23
  • 2016-10-11
  • 2013-04-16
  • 1970-01-01
  • 2013-01-28
  • 1970-01-01
相关资源
最近更新 更多