【问题标题】:Preserving insertion order on a std::set or std::unordered_set在 std::set 或 std::unordered_set 上保留插入顺序
【发布时间】:2018-03-12 16:53:59
【问题描述】:

在将此标记为重复之前,我一直是hereherehere, a duplicate of the first

我知道boost::multi_index,并使用我缺少它的环境,并且std::unordered_set 不一定以确定性插入顺序存储元素。

我发现使用两个容器的概念,说一个额外的std::vector 不礼貌。

喜欢是一个涉及比较器的解决方案,我可以在std::set 的模板参数中使用它(澄清一下,这可能是一个简单的函子结构,包含bool operator()() 重载、常规函数或 lambda)。有可能吗?

附录

  1. 必须通过 std:: 容器的开始迭代器/结束迭代器构造函数进行初始化,例如在这个 sn-p 中。

    std::string str; cin >> str;
    std::set<char>(str.begin(), str.end());
    
  2. 另外,另一个有趣的用例是创建一个哑哈希包装函子,允许将插入顺序推入std::unordered_set 的模板参数。

【问题讨论】:

  • 你有什么问题? (让您的问题自包含,您可以通过问号的存在来判断某事是否是问题。其他一切都是咆哮或推文。)
  • 有可能吗?我承认我最终听起来像那样,所以我想澄清一下。任何帮助表示赞赏。 :)
  • 因为它很麻烦,而且可能 lambda 可以在这里工作。我很懒,仅此而已:P AFAIK std::set 也允许这样做,所以如果我可以配置它的行为,为什么不使用我喜欢的容器呢?
  • 否 - 标题用于索引问题列表中的帖子。您的问题主体需要单独独立。 在你的问题中说出你要问的问题,不要指望人们会四处寻找你的问题。
  • @mushi:不不,您仍然在插入时扫描重复项,但扫描必须是线性搜索(因为范围未排序)。没有重复。

标签: c++ c++11 boost stl set


【解决方案1】:

您不能直接将 lambda 表达式作为集合的模板参数,因为 lambda 表达式是一个值,而集合的模板参数是一个类型。对问题的明显更正,即使用 lambda 和 decltype 的构造是否可以工作,导致了一个有趣的问题,即 lambda 表达式表示 unique 类型(“闭包类型”),所以你永远不能创建相同闭包类型的两个单独的 lambda 表达式。*

但是,在更抽象的意义上,您可以使用模板参数推导在本地上下文中实现您想要的,例如:

template <typename F>
int f(int* first, int* last, F comp)
{
    std::set<int, F> s(comp);
    while (first != last) s.insert(*first++);
    ...
}

现在您可以使用 lambda 表达式作为参数调用 f,从而有效地“使用 lambda 作为集合的比较器”。或者,对于一个更简单的示例,您可以只为 lambda 指定一个命名变量(将所有模板推导放入单个 auto

auto comp = [](...) { ... };
std::set<int, decltype(comp)> s(comp);

*) 有一个提议允许在未评估的上下文中使用 lambdas 来解决这一点,但其前景尚不确定。它有一些有趣的副作用,比如让闭包类型影响名称修改。

【讨论】:

  • 很明显,我需要一个decltype() 来获取模板参数列表中的 lambda 类型。只需一个单一的 lambda 就可以了。答案对这个问题无效。尽管如此,这种努力还是值得称赞的,所以我会赞成。
  • 如果您至少需要两次 lambda,它就不能是“一次性的”!闭包类型不是默认可构造的。 (还有另一个解决这个问题的提议,它实际上对 C++20 来说很有机会。)
  • 我想我不会需要它两次。我可以说这个 lambda 存在于 main() 中,所以它可能不需要是一个 lambda。感谢您强调这一点,我已经更新了我的问题。
  • 这不是我要找的用例先生._.
  • (是的,你可以说auto f = []...; set&lt;int, decltype(f)&gt; s(f);;这是我在示例中使用的“演绎”的一个更简单的变体。)
【解决方案2】:

保留插入顺序的 adt 是 std::vector。 您可以像这样轻松地包装它以获得类似 std::set 的行为:

#include <iostream>
#include <vector>
#include <utility>
#include <algorithm>

using namespace std;

template < typename T >
class VectorSet : public vector<T> {
public:
    using iterator = typename vector<T>::iterator;
    using value_type = typename vector<T>::value_type;

    pair<iterator, bool> insert (const value_type& val) {
        auto it = ::find(this->begin(), this->end(), val);
        if (it == this->end())
            it = ::vector<T>::insert(this->end(), val);

        return pair<iterator, bool>(it, true);
    }
};

int main()
{
    VectorSet<int> my;
    my.insert(1);
    my.insert(4);
    my.insert(3);
    my.insert(4);

    for (auto & v : my) {
        cout << v << endl;
    }

    return 0;
}

【讨论】:

  • 你为什么要改造std::find
  • 另外它太冗长了。更不用说,哦,我的,扩展一个 std:: 容器:O
  • @mushi,扩展容器是一个值得商榷的问题,我其实觉得它很有艺术性:)
  • 好吧,HackedStack 和 HackedQueue 每次看到它们时都会让我崩溃
  • 大声笑 :) 绝对!但是 pro::vector 看起来有那么糟糕吗? ;]
【解决方案3】:

你不能,除非你使用额外的索引。两种方法:

1。使用显式索引

Live On Coliru

#include <set>
#include <vector>
#include <functional>
#include <algorithm>

using namespace std;

#include <iostream>

string read_word() {
    string str;
    cin >> str;
    return str;
}

int main() {
    using Ref = std::reference_wrapper<char const>;

    auto const str = read_word();
    std::cout << "Word: "   << str << "\n";

    auto v = [&]() -> vector<Ref> {
        set<Ref> u(str.begin(), str.end());
        return {u.begin(), u.end()};
    }();

    std::cout << "Unique: " << string(v.begin(), v.end()) << "\n";

    auto pos = [str](char ch) { return str.find(ch); };
    std::sort(v.begin(), v.end(), [pos](auto& a, auto& b) { return pos(a) < pos(b); });

    std::cout << "Insertion: " << string(v.begin(), v.end()) << "\n";
}

打印例如

Word: pineapple
Unique: aeilnp
Insertion: pineal

2。使用 Boost 多索引

同样的交易

Live On Coliru

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/ordered_index.hpp>

namespace bmi = boost::multi_index; 

using Index = bmi::multi_index_container<char, 
      bmi::indexed_by<
          bmi::sequenced<>,
          bmi::ordered_unique<bmi::tag<struct unique>, bmi::identity<char> >
      > > ;

#include <iostream>

std::string read_word() {
    std::string str;
    std::cin >> str;
    return str;
}

int main() {

    auto const str = read_word();
    std::cout << "Word: "   << str << "\n";

    Index idx(str.begin(), str.end());

    std::cout << "Insertion: " << std::string(idx.begin(), idx.end()) << "\n";

    auto& u = idx.get<unique>();
    std::cout << "Unique: " << std::string(u.begin(), u.end()) << "\n";
}

打印

Word: pineapple
Insertion: pineal
Unique: aeilnp

【讨论】:

  • 我想说 boost 多索引方法基本上是你想要的。请注意,您也可以在另一个容器上使用侵入式集合来实现这种特殊组合。
【解决方案4】:

我认为一个奇怪的解决方案(虽然不涉及任何集合)可能是使用元素类型的std::mapstd::time_point 作为键类型。如果根本没有任何东西,这将确保插入顺序。

【讨论】:

  • 除非它只是一个非常非常低效的向量。你将如何仍然通过原始键查找
  • 红黑实现的地图不会变成一维向量,而且当我制作 OP 时,我认为我不需要随机访问,所以简单的迭代可能是我的目标因为,在这种情况下,我不需要使用 std::time_point 键查找。
  • 确实如此。它不会变成一维向量,这正是造成浪费的原因,因为您不需要花哨的功能。地图要复杂/昂贵几个数量级,并且在遍历方面不会为您提供向量不会提供的任何东西
  • 除非需要显式调用std::sort。否则,我喜欢std::vector 方法。它可能更有效。
  • 这很有启发性。当然,我发帖的时候并没有考虑过。 Boost 很好地涵盖了所有用例。 :p
猜你喜欢
  • 1970-01-01
  • 2014-08-10
  • 2021-05-30
  • 1970-01-01
  • 2020-11-23
  • 2015-10-11
  • 1970-01-01
  • 2019-04-26
  • 2011-02-08
相关资源
最近更新 更多