按固定组件对元组容器进行排序

Sorting container of tuples by fixed components

本文关键字:排序 元组 组件      更新时间:2023-10-16

给定一个由对或元组组成的容器,我们希望通过指定二进制谓词和固定组件来对其进行排序,以便排序仅关注每个元组的该组件。 此工作代码说明了这一点:

#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
template <int N, typename BinaryPredicate, typename ForwardIterator>
void sortByFixedComponent (ForwardIterator first, ForwardIterator last) {
    std::sort (first, last, [](const typename ForwardIterator::value_type& x, const typename ForwardIterator::value_type& y)->bool {
        return BinaryPredicate()(std::get<N>(x), std::get<N>(y));
    });
}
// ------------------- Testing --------------------
struct Length {
    bool operator()(const std::string& a, const std::string& b) const {return a.length() < b.length();}
};
int main() {
    std::vector<std::pair<int,int>> v = {
        {3,5}, {6,8}, {3,7}, {1,9}, {3,1}, {1,2}, {3,5}
    };
    sortByFixedComponent<0, std::less<int>> (v.begin(), v.end());  // Sorts v according to the first components.
    for (const std::pair<int,int>& x : v) std::cout << "(" << x.first << ", " << x.second << ") ";  std::cout << 'n';
    sortByFixedComponent<1, std::greater<int>> (v.begin(), v.end());  // Sorts v according to the second components (in reverse order).
    for (const std::pair<int,int>& x : v) std::cout << "(" << x.first << ", " << x.second << ") ";  std::cout << 'n';
    using Tuple = std::tuple<int, std::string, char>;
    std::vector<Tuple> t = {
        Tuple{3,"hi",'q'}, Tuple{6,"dog",'u'}, Tuple{3,"hello",'c'}, Tuple{1,"cat",'r'}, Tuple{3,"no",'a'}, Tuple{1,"orange",'z'}, Tuple{3,"door",'x'}
    };
    sortByFixedComponent<1, Length> (t.begin(), t.end());  // Sorts t according to the second components in order of string lengths.
}

现在的挑战是连接(在该指定组件中绑定的元组(。 因此,让我们sortByFixedComponent将决定领带之间排序的包Z<Is...>P<BinaryPredicates...>放入。 在上面的例子中,有连接,所以转到 Is... 中的下一个 int 和 BinaryPredicates... 中的下一个二进制谓词,并在这个新的固定组件和新的二进制谓词中的这些关系之间进行排序。 如果在此之后仍有联系,请继续Is...BinaryPredicates.... 这最终应该导致唯一的排序。

当然,这种递归是我卡住的地方。 这就是我到目前为止所拥有的,它编译只是因为我还没有填写递归步骤:

#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
template <typename, typename, typename> struct SortByFixedComponent;
template <template <int...> class Z, int N, template <typename...> class P, typename BinaryPredicate, typename ForwardIterator>
struct SortByFixedComponent<Z<N>, P<BinaryPredicate>, ForwardIterator> {
    void operator()(ForwardIterator first, ForwardIterator last) {
        std::sort (first, last, [](const typename ForwardIterator::value_type& x, const typename ForwardIterator::value_type& y)->bool {
            return BinaryPredicate()(std::get<N>(x), std::get<N>(y));
        });
    }
};
template <template <int...> class Z, int FirstInt, int... RestInt, template <typename...> class P, typename FirstBinaryPredicate, typename... RestBinaryPredicate, typename ForwardIterator>
struct SortByFixedComponent<Z<FirstInt, RestInt...>, P<FirstBinaryPredicate, RestBinaryPredicate...>, ForwardIterator> :
        SortByFixedComponent<Z<RestInt...>, P<RestBinaryPredicate...>, ForwardIterator> {
    void operator()(ForwardIterator first, ForwardIterator last) {
        std::sort (first, last, [](const typename ForwardIterator::value_type& x, const typename ForwardIterator::value_type& y)->bool {
            return FirstBinaryPredicate()(std::get<FirstInt>(x), std::get<FirstInt>(y));
        });
// How to sort among the ties using SortByFixedComponent<Z<RestInt...>, P<RestBinaryPredicate...>, ForwardIterator>::operator()(first, last); ??? 
    }
};
template <typename Ints, typename BinaryPredicates, typename ForwardIterator>
void sortByFixedComponent (ForwardIterator first, ForwardIterator last) {
    SortByFixedComponent<Ints, BinaryPredicates, ForwardIterator>()(first, last);
}
struct Length {
    bool operator()(const std::string& a, const std::string& b) const {return a.length() < b.length();}
};
template <typename...> struct P {};
template <int...> struct Z {};
int main() {
    std::vector<std::pair<int,int>> v = {
        {3,5}, {6,8}, {3,7}, {1,9}, {3,1}, {1,2}, {3,5}
    };
    sortByFixedComponent< Z<0>, P<std::less<int>> > (v.begin(), v.end());
    for (const std::pair<int,int>& x : v) std::cout << "(" << x.first << ", " << x.second << ") ";  std::cout << 'n';
    sortByFixedComponent< Z<1>, P<std::greater<int>> > (v.begin(), v.end());
    for (const std::pair<int,int>& x : v) std::cout << "(" << x.first << ", " << x.second << ") ";  std::cout << 'n';
    using Tuple = std::tuple<int, std::string, char>;
    std::vector<Tuple> t = {
        Tuple{3,"hi",'q'}, Tuple{6,"dog",'u'}, Tuple{3,"hello",'c'}, Tuple{1,"cat",'r'}, Tuple{3,"no",'a'}, Tuple{1,"orange",'z'}, Tuple{3,"door",'x'}
    };
    sortByFixedComponent< Z<1>, P<Length> > (t.begin(), t.end());
//  sortByFixedComponent<Z<2,0,1>, P<std::greater<char>, Length, std::less<int>>> (t.begin(), t.end());  // Want this line to work.
}

最终,在main((中,我想要这条线

sortByFixedComponent<Z<2,0,1>, P<std::greater<char>, Length, std::less<int>>> (t.begin(), t.end());

去工作。 它是对容器进行排序

    std::vector<Tuple> t = {
        Tuple{3,"hi",'q'}, Tuple{6,"dog",'u'}, Tuple{3,"hello",'c'}, Tuple{1,"cat",'r'}, Tuple{3,"no",'a'}, Tuple{1,"orange",'z'}, Tuple{3,"door",'x'}
    };
以便首先查看 char 组件(对元组进行排序,以便按字母顺序排列(,然后在领带中查看 int 组件(对领带进行排序,以便

它们以相反的顺序排列(,然后如果仍有任何领带,请查看字符串组件(对领带进行排序,使字符串从最短到最长(。 任何帮助将不胜感激。 当我解决第一个示例时,这个问题出现了,但随后想知道联系。

看一个具体的例子。 在我的第一个示例中,输出是 (1, 9( (1, 2( (3, 5( (3, 7( (3, 1( (3, 5( (6, 8(,因为第一个组件从最小到最大排序。但是 4 对以 3 开头。因此,让我们根据其他比较器的第二个组件对这 4 个进行排序。目标是对此的概括。

更新: 我认为解决这个问题的关键不是递归,所以基本上我上面的尝试可以取消。 我认为需要的是二进制谓词的组合。 所以我的新起点是使用这个辅助结构:

template <typename, typename...> struct PredicateBuilder;
template <typename T, typename BinaryPredicate> 
struct PredicateBuilder<T, BinaryPredicate> {
    const BinaryPredicate binaryPredicate;
    PredicateBuilder (BinaryPredicate func): binaryPredicate (func) {}
    bool operator() (const T& a, const T& b) const {return binaryPredicate (a,b);}
};
template <typename T, typename First, typename... Rest>
struct PredicateBuilder<T, First, Rest...> : PredicateBuilder<T, Rest...> {
    const First binaryPredicate;
    PredicateBuilder (First first, Rest... rest): PredicateBuilder<T, Rest...> (rest...), binaryPredicate (first) {}
    bool operator () (const T& a, const T& b) const {
        if (binaryPredicate (a,b))
            return true;
        else if (binaryPredicate (b,a)) 
            return false;
        else
            return PredicateBuilder<T, Rest...>::operator()(a,b);
    }
};
template<class F>
struct order_by_t {
  F f;
  template<class Lhs, class Rhs>
  bool operator(Lhs&&lhs, Rhs&&rhs)const {
    return f(std::forward<Lhs>(lhs)) < f(std::forward<Rhs>(rhs));
  }
};
template<class F>
order_by_t< std::decay_t<F> > order_by( F&& f ) {
  return {std::forward<F>(f)};
}

这需要一个类型 T 和一个映射 M:T->u,并使用对你的排序来提供 T 上的排序。

template<size_t... Is>
struct retie {
  template<class Tup>
  auto operator()( Tup&& tup )const->
  decltype(std::tie( std::get<Is>(std::forward<Tup>(tup))... ))
  { return std::tie( std::get<Is>(std::forward<Tup>(tup))... ); }
};

这将采用元组tup并以不同的顺序重新绑定其元素。

std::sort( foo.begin(), foo.end(), order_by( retie<3,2,1>{} ) );

将通过元素 3、元素 2 和元素 1 对foo进行排序。

您希望能够做的下一件事是将每个子元素投影到其自己的排序域中。 仍然不是你想要的,但更接近。

为了解决这个问题,我会研究管道并制作一个变order_by

管道可以让你得到,然后投影到一个新的空间。

template<class... Fs>
struct order_by_t;
template<>
struct order_by_t<> {
  template<class Lhs, class Rhs>
  bool operator(Lhs&&lhs, Rhs&&rhs)const {
    return false;
  }
};
template<class F>
struct order_by_t<F> {
  F f;
  template<class Lhs, class Rhs>
  bool operator(Lhs&&lhs, Rhs&&rhs)const {
    return f(std::forward<Lhs>(lhs)) < f(std::forward<Rhs>(rhs));
  }
};
template<class F, class...Fs>
struct order_by_t<F, Fs...>:
  order_by<Fs...>
{
  template<class T, class...Ts>
  order_by_t(T&&t, Ts&&...ts):
    order_by_t<Fs...>{std::forward<Ts>(ts)...},
    f(std::forward<T>(t))
  {}
  F f;
  template<class Lhs, class Rhs>
  bool operator(Lhs&&lhs, Rhs&&rhs)const {
    auto&& lhs_ = f(lhs);
    auto&& rhs_ = f(rhs);
    if (lhs_<rhs_) return true;
    if (rhs_<lhs_) return false;
    return order_by_t<Fs...>::operator()
      (std::forward<Lhs>(lhs),std::forward<Rhs>(rhs));
  }
};
template<class... Fs>
order_by_t< std::decay_t<Fs>... > order_by( Fs&&... fs ) {
  return {std::forward<Fs>(fs)...};
}

现在,order_by可以获取投影集合,一次投影一个,选择第一个要使用的投影。

现在我们想将操作通过管道连接在一起,这样 get 投影之后可以跟着长度投影(例如(。

template<size_t I>
struct get_t {
  template<class Tup>
  auto operator()(Tup&&tup)const->
  decltype(std::get<I>(tup))
  { return std::get<I>(tup); }
};
template<class A, class B>
struct compose_t {
  A a; B b;
  template<class... Ts>
  auto operator()(Ts&&...ts)const->
  std::result_of_t< A const&( std::result_of_T< B const&(Ts...) > ) >
  {
    return a(b(std::forward<Ts>(ts)...));
  }
  // if you have a SFINAE enabled result_of, this is also useful.
  // if you don't, don't include this overload (it isn't used in this code):
  #if 0
  template<class... Ts>
  auto operator()(Ts&&...ts)const->
  std::result_of_t< A const&( std::result_of_T< B const&(Ts) >... ) >
  {
    return a(b(std::forward<Ts>(ts))...);
  }
  #endif
};
template<class A, class B>
compose_t< std::decay_t<A>, std::decay_t<B> >
compose(A&& a, B&& b) {
  return {std::forward<A>(a), std::forward<B>(b)};
}

我们快到了! 将您的Length替换为:

struct get_length {
  size_t operator()( std::string const& s ) const {
    return s.size();
  }
};
struct reversed_sort_t {
  template<class T>
  struct helper {
    T&& t;
    template<class A, class B>
    friend bool operator<(helper<A>&& lhs, helper<B>&& rhs) {
      return std::forward<B>(rhs.t) < std::forward<A>(lhs.t);
    }
  };
  template<class T>
  helper<T> operator()(T&& t) const {
    return {std::forward<T>(t)};
  }
};
//  sortByFixedComponent<Z<2,0,1>, P<std::greater<char>, Length, std::less<int>>> (t.begin(), t.end());  // Want this line to work.
auto ordering = order_by(
  compose( reversed_sort_t{}, get_t<2>{} ),
  compose( get_length{}, get_t<0>{} ),
  get_t<1>{}
);
std::sort( t.begin(), t.end(), ordering );

稍微不同的方法是将多个完整排序链接在一起,而不是将order_by投影相互链接。

如果你真的想要你的特定语法,可以做一些工作来提取IF,并将它们配对并构建一个ordering函数调用。

以上都不是编译或测试的,但设计应该可以工作。

我发现使用定义良好的<将数据投影到已经定义的域中通常比编写自定义比较运算符更令人愉悦。

上面使用了少量C++14功能,主要是_t别名。 如果您缺少该别名,请将std::blah_t< args >替换为typename std::blah< args >::type,或者编写自己的_t别名,如 template<class...Ts> using blah_t=typename std::blah<Ts...>::type; 。 还有一些代码在 C++14 std::result_of 中得到了很大的改进,目前#if 0改进了。

最奇怪的顺序映射是reversed_sort_t,它映射T->reversed_sort_t::helper<T>,其中helper<T>是一种除了引用语义默认复制之外什么都不支持的类型,并反转了<的应用。 此解决方案的旧版本否定,但当限制为 char 时,否定 -128 不会产生 +128。

我想我明白了!

#include <iostream>
#include <tuple>
#include <vector>
#include <string>
#include <algorithm>
#include <functional>
template <typename, typename, typename> struct TuplePredicateBuilder;
template <template <int...> class Z, int N, template <typename...> class P, typename BinaryPredicate, typename Iterator>
struct TuplePredicateBuilder<Z<N>, P<BinaryPredicate>, Iterator> {
    bool operator()(const typename Iterator::value_type& x, const typename Iterator::value_type& y) {
        return BinaryPredicate()(std::get<N>(x), std::get<N>(y));
    }
};
template <template <int...> class Z, int FirstInt, int... RestInt, template <typename...> class P, typename FirstBinaryPredicate, typename... RestBinaryPredicate, typename Iterator>
struct TuplePredicateBuilder<Z<FirstInt, RestInt...>, P<FirstBinaryPredicate, RestBinaryPredicate...>, Iterator> : TuplePredicateBuilder<Z<RestInt...>, P<RestBinaryPredicate...>, Iterator> {
    bool operator()(const typename Iterator::value_type& x, const typename Iterator::value_type& y) {
        if (FirstBinaryPredicate()(std::get<FirstInt>(x), std::get<FirstInt>(y)))
            return true;
        else if (FirstBinaryPredicate()(std::get<FirstInt>(y), std::get<FirstInt>(x)))
            return false;
        else
            return TuplePredicateBuilder<Z<RestInt...>, P<RestBinaryPredicate...>, Iterator>::operator()(x,y);
    }
};
template <typename, typename, typename> struct SortByFixedComponent;
template <template <int...> class Z, int N, template <typename...> class P, typename BinaryPredicate, typename Iterator>
struct SortByFixedComponent<Z<N>, P<BinaryPredicate>, Iterator> {
    void operator()(Iterator first, Iterator last) {
        std::sort (first, last, [](const typename Iterator::value_type& x, const typename Iterator::value_type& y)->bool {
            return BinaryPredicate()(std::get<N>(x), std::get<N>(y));
        });
    }
};
template <template <int...> class Z, int... Is, template <typename...> class P, typename... BinaryPredicates, typename Iterator>
struct SortByFixedComponent<Z<Is...>, P<BinaryPredicates...>, Iterator> {
    void operator()(Iterator first, Iterator last) {
        std::sort (first, last, [](const typename Iterator::value_type& x, const typename Iterator::value_type& y)->bool {
            return TuplePredicateBuilder<Z<Is...>, P<BinaryPredicates...>, Iterator>()(x,y);
        });
    }
};
template <typename Components, typename BinaryPredicates, typename Iterator>
void sortByFixedComponent (Iterator first, Iterator last) {
    SortByFixedComponent<Components, BinaryPredicates, Iterator>()(first, last);
}

测试输出:

struct Length {
    bool operator()(const std::string& a, const std::string& b) const {return a.length() < b.length();}
};
template <typename...> struct P {};
template <int...> struct Z {};
template <typename Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t) {
        TuplePrinter<Tuple, N-1>::print(t);
        std::cout << ", " << std::get<N-1>(t);
    }
};
template <typename Tuple>
struct TuplePrinter<Tuple, 1>{
    static void print (const Tuple& t) {std::cout << std::get<0>(t);}
};
template <typename... Args>
void print (const std::tuple<Args...>& t) {
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")n";
}
int main() {
    using Tuple = std::tuple<std::string, int, char>;
    std::vector<Tuple> t = {
        Tuple{"hi",3,'q'}, Tuple{"dog",6,'u'}, Tuple{"hello",3,'c'}, Tuple{"cat",1,'u'}, Tuple{"no",3,'a'}, Tuple{"orange",1,'u'}, Tuple{"red",3,'u'}
    };
    sortByFixedComponent<Z<2,0,1>, P<std::greater<char>, Length, std::less<int>>> (t.begin(), t.end());
    for (const Tuple& x : t)
        print(x);
}

输出:

(cat, 1, u)
(red, 3, u)
(dog, 6, u)
(orange, 1, u)
(hi, 3, q)
(hello, 3, c)
(no, 3, a)

首先,第 3 个组件按反向字母顺序排列。 然后在这 4 个以 u 结尾的元组中,字符串组件从最短到最长排列。 仍然有三个连接,因此在这些连接中,int 组件从最小到最大排列。 我希望之前阅读的人现在理解我的问题。

我现在将研究Yakk的解决方案,看看他的方法是否真的不同。

更新:我

只想补充一点,我找到了一种更通用的方法来编写上述内容(经过测试以提供完全相同的输出(,使用:

// General predicate builder so that other types than TuplePredicateBuilder can inherit from this as well.
template <typename, typename...> struct PredicateBuilder;
template <typename T, typename BinaryPredicate> 
struct PredicateBuilder<T, BinaryPredicate> {
    bool operator()(const T& a, const T& b) const {return BinaryPredicate()(a,b);}
};
template <typename T, typename First, typename... Rest>
struct PredicateBuilder<T, First, Rest...> : PredicateBuilder<T, Rest...> {
    bool operator()(const T& a, const T& b) const {
        if (First()(a,b))
            return true;
        else if (First()(b,a)) 
            return false;
        else
            return PredicateBuilder<T, Rest...>::operator()(a,b);
    }
};
template <int N, typename BinaryPredicate, typename Iterator>
struct BinaryPredicateNthComponent {
    bool operator()(const typename Iterator::value_type& x, const typename Iterator::value_type& y) {
        return BinaryPredicate()(std::get<N>(x), std::get<N>(y));
    }
};
template <typename, typename, typename> struct TuplePredicateBuilder;
template <template <int...> class Z, int... Is, template <typename...> class P, typename... BinaryPredicates, typename Iterator>
struct TuplePredicateBuilder<Z<Is...>, P<BinaryPredicates...>, Iterator> : 
    PredicateBuilder<typename Iterator::value_type, BinaryPredicateNthComponent<Is, BinaryPredicates, Iterator>...> {};