从订购的容器中制作比较器

时间:2016-06-03 17:51:33

标签: c++ c++11 comparator

给定一个对象列表,创建一个仿函数对象作为比较器的最简洁方法是什么,这样比较器就会尊重列表中对象的顺序。保证列表中的对象是唯一的,列表包含可能对象的整个空间。

例如,假设我们有:

const std::vector<std::string> ordering {"dog", "cat", "mouse", "elephant"};

现在我们想要一个函数作为比较器,比如地图:

using Comparator = std::function<bool(const std::string&, const std::string&>;
using MyMap      = std::map<std::string, int, Comparator>;

我有一个解决方案,但这不是我所说的那样:

const auto cmp = [&ordering] (const auto& lhs, const auto& rhs)
                 {
                     const std::array<std::reference_wrapper<const std::decay_t<decltype(lhs)>, 2> values {lhs, rhs};
                     return *std::find_first_of(std::cbegin(ordering), std::cend(ordering),
                                                std::cbegin(values), std::cend(values),
                                                [] (const auto& lhs, const auto& rhs) {
                                                    return lhs == rhs.get();
                                                }) == lhs;
                 };

是否有一些不那么冗长的东西?

5 个答案:

答案 0 :(得分:3)

您可以使用:

const std::vector<std::string> ordering {"dog", "cat", "mouse", "elephant"};

struct cmp
{
   bool operator()(const std::string& lhs, const std::string& rhs)
   {
      return (std::find(ordering.begin(), ordering.end(), lhs) <
              std::find(ordering.begin(), ordering.end(), rhs));
   }
};

using MyMap = std::map<std::string, int, cmp>;

http://ideone.com/JzTNwt处查看它。

答案 1 :(得分:1)

KISS。使用具有明确语义的可重用原语构建解决方案。

order_by进行投影A->B并使用A上的排序返回B上的排序。它可选择在B上进行排序(此处未使用):

template<class F, class Next=std::less<>>
auto order_by(F&& f, Next&& next = {}) {
  return [f=std::forward<F>(f), next=std::forward<Next>(next)]
    (auto&& lhs, auto&& rhs)->bool
  {
    return next(f(lhs), f(rhs));
  };
}

index_in接受一个容器c并返回一个接受一个元素的函数,并在c中确定其索引:

template<class C>
auto index_in( C&& c ) {
  return [c=std::forward<C>(c)](auto&& x){
    using std::begin; using std::end;
    return std::find( begin(c), end(c), x ) - begin(c);
  };
}
template<class T>
auto index_in( std::initializer_list<T> il ) {
  return [il](auto&& x){
    using std::begin; using std::end;
    return std::find( begin(il), end(il), x ) - begin(il);
  };
}
然后我们将它们组成:

auto cmp = order_by( index_in(std::move(ordering) ) );

每个组件都可以独立测试和验证,而组合物显然可以&#34;作品。我们还可以重写index_in以使用比线性更快的查找,例如从键到索引的映射,并且我们有两个可以相互进行单元测试的实现。

我发现order_by非常有用。 index_in我以前从未使用过。

这个技巧使得在一行上构造结果map,而不是存储cmp,实际上,因为最终描述简短明了。

template<class T>
using Comparator = std::function<bool(T const&, T const&>;
using MyMap      = std::map<std::string, int, Comparator<std::string>>;
MyMap m = order_by( index_in({"dog", "cat", "mouse", "elephant"}) );

看起来也很漂亮。

这是第二种方法。

我们可以将一些工作移到lambda之外。

template<class T0, class...Ts>
std::array< std::reference_wrapper<T0>, sizeof...(Ts)+1 >
make_ref_array( T0& t0, Ts&... ts ) {
  return {std::ref(t0), std::ref(ts)...};
}

然后我们可以从第一原则而不是算法编写lambda:

Comparator cmp = [&ordering] (const auto& lhs, const auto& rhs)
{
  if (lhs==rhs) return false; // x is never less than x.
  for (auto&& e:ordering)
    for (auto&& z:make_ref_array(lhs, rhs))
      if (e==z.get())
        return std::addressof(z.get())==std::addressof(lhs);
  return false;
};

得到的lambda稍微冗长。

如果您使用基于范围的算法,它也可能会有所帮助。

在我的两个解决方案中,列表中的所有元素都大于列表中的任何元素,并且彼此相等。

答案 2 :(得分:1)

跳过算法并写一个for循环:

auto cmp = [&ordering](auto const& lhs, auto const& rhs) {
    for (auto const& elem : ordering) {
        // check rhs first in case the two are equal
        if (elem == rhs) {
            return false;
        }
        else if (elem == lhs) {
            return true;
        }
    }
    return false;
};

技术上可能比你的解决方案更长,但我觉得它更容易阅读。

或者,根据订购的大小,可以将两者都投入到地图中:

std::unordered_map<std::string, int> as_map(std::vector<std::string> const& ordering)
{
    std::unordered_map<std::string, int> m;
    for (auto const& elem : ordering) {
        m.emplace(elem, m.size());
    }
    return m;
}

auto cmp = [ordering_map = as_map(ordering)](auto const& lhs, auto const& rhs){
    auto left = ordering_map.find(lhs);
    auto right = ordering_map.find(rhs);
    return left != ordering_map.end() && right != ordering_map.end() &&
        left->second < right->second;
};

答案 3 :(得分:0)

这与R. Sahu的答案基本相同。但是因为我已经输入了...我在这里主张的主要是将ordering保持在cmp内部,假设你不需要外部。

const auto cmp = [ordering = vector<string>{ "dog", "cat", "mouse", "elephant" }](const auto& lhs, const auto& rhs) { return find(cbegin(ordering), cend(ordering), lhs) < find(cbegin(ordering), cend(ordering), rhs); };

Live Example

ordering封装在cmp中会使读者在将ordering更改为小时时必须查看的范围。 (就我个人而言,我不会将cmp构造为Rvalue,我只是因为同样的原因而将其转移directly into the constructor ...虽然它变得有点像一个简单的单行:

map<string, int, function<bool(const string&, const string&)>> myMap([ordering = vector<string>{ "dog", "cat", "mouse", "elephant" }](const auto& lhs, const auto& rhs) { return find(cbegin(ordering), cend(ordering), lhs) < find(cbegin(ordering), cend(ordering), rhs); });

Live Example

答案 4 :(得分:0)

我建议您将密钥设置为包含密钥(在本例中为std::string)和数组中的索引的结构类型。

这样的东西
struct Key
{
    std::string str;
    size_t index;
};

using MyMap      = std::map<Key, int, Comparator>;

struct Comparator
{
    bool operator()(const Key &a, const Key &b) const
    {
        return a.index < b.index;
    }
};