【问题标题】:C++, Sort One Vector Based On Another One [duplicate]C++,基于另一个向量对一个向量进行排序
【发布时间】:2016-09-19 00:33:42
【问题描述】:

最好的例子是我想根据分数对名称进行排序。

vector <string> Names {"Karl", "Martin", "Paul", "Jennie"};
vector <int> Score{45, 5, 14, 24};

因此,如果我将分数排序为 {5、14、24、45},则名称也应根据其分数进行排序。

【问题讨论】:

  • 为什么不用std::vector&lt;std::pair&lt;int,std::string&gt;&gt; 来代替?
  • 或者至少创建一个struct 和一个int 和一个string 并有一个vector。
  • 或对索引向量进行排序 std::vector&lt;size_t&gt; 提供自定义比较器 comp(i, j) := Score[i] &lt; Score[j]。

标签: c++ sorting vector


【解决方案1】:

将名称和分数合并到一个结构中的另一种方法是创建一个索引列表并对其进行排序:

 std::vector<int> indices(Names.size());
 std::iota(indices.begin(), indices.end(), 0);
 std::sort(indices.begin(), indices.end(),
           [&](int A, int B) -> bool {
                return Score[A] < Score[B];
            });

现在indices 可用于按所需的排序顺序索引Names 和Scores。

【讨论】:

    【解决方案2】:

    正如其他答案中已经建议的那样:将每个人的姓名和分数结合起来可能是最简单的解决方案。

    通常,这可以通过有时被称为“压缩”操作来实现:将两个向量组合成一个成对的向量 - 以及相应的“解压缩”。

    一般实现,可能如下所示:

    #include <vector>
    #include <string>
    #include <algorithm>
    #include <iostream>
    #include <iterator>
    
    // Fill the zipped vector with pairs consisting of the
    // corresponding elements of a and b. (This assumes 
    // that the vectors have equal length)
    template <typename A, typename B>
    void zip(
        const std::vector<A> &a, 
        const std::vector<B> &b, 
        std::vector<std::pair<A,B>> &zipped)
    {
        for(size_t i=0; i<a.size(); ++i)
        {
            zipped.push_back(std::make_pair(a[i], b[i]));
        }
    }
    
    // Write the first and second element of the pairs in 
    // the given zipped vector into a and b. (This assumes 
    // that the vectors have equal length)
    template <typename A, typename B>
    void unzip(
        const std::vector<std::pair<A, B>> &zipped, 
        std::vector<A> &a, 
        std::vector<B> &b)
    {
        for(size_t i=0; i<a.size(); i++)
        {
            a[i] = zipped[i].first;
            b[i] = zipped[i].second;
        }
    }
    
    
    int main(int argc, char* argv[])
    {
        std::vector<std::string> names {"Karl", "Martin", "Paul", "Jennie"};
        std::vector<int> score {45, 5, 14, 24};
    
        // Zip the vectors together
        std::vector<std::pair<std::string,int>> zipped;
        zip(names, score, zipped);
    
        // Sort the vector of pairs
        std::sort(std::begin(zipped), std::end(zipped), 
            [&](const auto& a, const auto& b)
            {
                return a.second > b.second;
            });
    
        // Write the sorted pairs back to the original vectors
        unzip(zipped, names, score);
    
        for(size_t i=0; i<names.size(); i++)
        {
            std::cout << names[i] << " : " << score[i] << std::endl;
        }
        return 0;
    }
    

    【讨论】:

    • 假设std::pair 定义了operator&lt; 以按字典顺序进行比较,您应该可以使用std::pair&lt;B, A&gt; 而不是std::pair&lt;A, B&gt; 来节省几行代码。然后你可以写zip(score, names, zipped)而不是zip(names, score, zipped)并使用std::sort(std::begin(zipped), std::end(zipped))而不需要定义一个lambda进行比较。
    • @Jasha 好点,但仅适用于这个特定场景。对于一个不小于可比的first,或者为了降序排序,仍然需要一个自己的比较器,所以在这个(假设是-通用)答案。
    【解决方案3】:

    做到这一点的最佳方法是拥有一个结构,将名称与其分数结合起来并拥有一个向量。

    struct Person
    {
        std::string Name;
        int Score;
    };
    

    然后你可以声明你的向量:

    std::vector<Person> people{ { "Karl", 45 }, { "Martin", 5 }, { "Paul", 14 } };
    

    使用来自&lt;algorithm&gt; 的std::sort 很容易对其进行排序:

    std::sort(people.begin(), people.end(), 
                   [](const auto& i, const auto& j) { return i.Score < j.Score; } );
    

    如果您想按降序排序,也可以更改 lambda:

    std::sort(people.begin(), people.end(), 
                   [](const auto& i, const auto& j) { return i.Score > j.Score; } );
    

    【讨论】:

    • 为什么结构名是复数?它应该是带有名为 people 的向量的 Person。
    • 因为我想清楚已经太迟了。固定和所有。
    【解决方案4】:

    如果您无法将数据合并成对向量或两者的结构,您可以创建一个迭代器向量,或从 0 到 size-1 的索引。然后使用自定义比较器对其进行排序。最后,创建一个新向量,使用迭代器或索引填充它。

    template<class T1, class A1, class T2, class A2>
    std::vector<T1, A1> sort_by(
      std::vector<T1,A1> const& vin, std::vector<T2,A2> const& keys
    ){
      std::vector<std::size_t> is;
      is.reserve(vin.size());
      for (auto&& unused:keys)
        is.push_back(is.size());
      std::sort(begin(is),end(is),[&](std::size_t l, std::size_t r){
        return keys[l]<keys[r];
      });
      std::vector<T1, A1> r;
      r.reserve(vin.size());
      for(std::size_t i:is)
        r.push_back(vin[i]);
      return r;
    }
    

    【讨论】:

    • 如果你能举个例子,我将不胜感激。
    • 不喜欢std::iota?
    【解决方案5】:

    这么多人问了这个问题,没有人给出满意的答案。这是一个 std::sort 帮助器,它可以同时对两个向量进行排序,只考虑一个向量的值。该解决方案基于自定义RadomIt(随机迭代器),直接对原始矢量数据进行操作,无需临时副本、结构重排或附加索引:

    namespace std {
    
    namespace sort_helper {
    
    template <typename _Data, typename _Order>
    struct value_reference_t;
    
    template <typename _Data, typename _Order>
    struct value_t {
        _Data data;
        _Order val;
        inline value_t(_Data _data, _Order _val) : data(_data), val(_val) {}
        inline value_t(const value_reference_t<_Data,_Order>& rhs);
    };
    
    template <typename _Data, typename _Order>
    struct value_reference_t {
        _Data* pdata;
        _Order* pval;
        value_reference_t(_Data* _itData, _Order* _itVal) : pdata(_itData), pval(_itVal) {}
        inline value_reference_t& operator = (const value_reference_t& rhs) { *pdata = *rhs.pdata; *pval = *rhs.pval; return *this; }
        inline value_reference_t& operator = (const value_t<_Data,_Order>& rhs) { *pdata = rhs.data; *pval = rhs.val; return *this; }
        inline bool operator < (const value_reference_t& rhs) { return *pval < *rhs.pval; }
    };
    
    template <typename _Data, typename _Order>
    struct value_iterator_t :
        iterator< random_access_iterator_tag, value_t<_Data,_Order>, ptrdiff_t, value_t<_Data,_Order>*, value_reference_t<_Data,_Order> >
    {
        _Data* itData;
        _Order* itVal;
        value_iterator_t(_Data* _itData, _Order* _itVal) : itData(_itData), itVal(_itVal) {}
        inline ptrdiff_t operator - (const value_iterator_t& rhs) const { return itVal - rhs.itVal; }
        inline value_iterator_t operator + (ptrdiff_t off) const { return value_iterator_t(itData + off, itVal + off); }
        inline value_iterator_t operator - (ptrdiff_t off) const { return value_iterator_t(itData - off, itVal - off); }
        inline value_iterator_t& operator ++ () { ++itData; ++itVal; return *this; }
        inline value_iterator_t& operator -- () { --itData; --itVal; return *this; }
        inline value_iterator_t operator ++ (int) { return value_iterator_t(itData++, itVal++); }
        inline value_iterator_t operator -- (int) { return value_iterator_t(itData--, itVal--); }
        inline value_t<_Data,_Order> operator * () const { return value_t<_Data,_Order>(*itData, *itVal); }
        inline value_reference_t<_Data,_Order> operator * () { return value_reference_t<_Data,_Order>(itData, itVal); }
        inline bool operator  < (const value_iterator_t& rhs) const { return itVal  < rhs.itVal; }
        inline bool operator == (const value_iterator_t& rhs) const { return itVal == rhs.itVal; }
        inline bool operator != (const value_iterator_t& rhs) const { return itVal != rhs.itVal; }
    };
    
    template <typename _Data, typename _Order>
    inline value_t<_Data,_Order>::value_t(const value_reference_t<_Data,_Order>& rhs)
        : data(*rhs.pdata), val(*rhs.pval) {}
    
    template <typename _Data, typename _Order>
    bool operator < (const value_t<_Data,_Order>& lhs, const value_reference_t<_Data,_Order>& rhs) {
        return lhs.val < *rhs.pval; }
    
    template <typename _Data, typename _Order>
    bool operator < (const value_reference_t<_Data,_Order>& lhs, const value_t<_Data,_Order>& rhs) {
        return *lhs.pval < rhs.val; }
    
    template <typename _Data, typename _Order>
    void swap(value_reference_t<_Data,_Order> lhs, value_reference_t<_Data,_Order> rhs) {
        std::swap(*lhs.pdata, *rhs.pdata);
        std::swap(*lhs.pval, *rhs.pval); }
    
    
    } // namespace sort_helper
    
    } // namespace std
    

    这是一个使用示例,使用标准 std::sort 根据 Age 值对 Names 和 Age 进行排序:

    char* Names[] = { "Karl", "Paul", "Martin", "Jennie" };
    int Age[] = { 45, 14, 5, 24 };
    typedef std::sort_helper::value_iterator_t<char*,int> IndexIt;
    std::sort(IndexIt(Names, Age), IndexIt(Names+4, Age+4));
    

    排序为:

    { "Martin", "Paul", "Jennie", "Karl" };
    { 5, 14, 24, 45 };
    

    在 Visual Studio 2017 和 GCC 5.4.0 上测试的代码。

    【讨论】:

    • 不幸的是doesn't seem to work under GCC,即使在我修复了value_iterator_t 中的typedef 用法之后。此外,您不应该使用以_[A-Z] 开头的名称,不要将内容放入namespace std。两者都使您的代码行为未定义。
    • 感谢您的发现,我现在已修复它并且也适用于 GCC。
    • 这在 VS 16.8 中停止工作。这是问题和我建议的解决方法:stackoverflow.com/a/69767844/209649
    【解决方案6】:

    您可以这样做的一种方法是将名称和分数存储在单个数据结构中,例如 std::vector&lt;std::pair&lt;std::string,int&gt;&gt;,然后可以按如下方式进行排序:

    #include <algorithm>
    #include <vector>
    #include <string>
    #include <utility>
    //...
    std::vector<std::pair<std::string, int>> names_scores_vec;
    // ... populate names_scores_vec...
    // lambda for sorting, change to > for descending order
    auto sort_by_scores = [](const std::pair<string,int>& _lhs, 
        const std::pair<string,int>& _rhs) { return _lhs.second < _rhs.second; };
    std::sort(names_scores_vec.begin(), names_scores_vec.end(), sort_by_scores);
    

    或者,如果您想要重复的键(即允许重复的名称),请使用 std::map 或 std::multimap 等存储。

    【讨论】:

      【解决方案7】:

      这不能通过自定义迭代器类型来完成吗?

      编辑:

      我在想的最简单的形式——根据第一个向量对一对向量进行排序——是有一个迭代器,它的函数如取消引用、下标、成员访问和相等以及排序比较将调用相应的函数第一个迭代器,所有其他函数(复制,算术,交换,...​​)都作用于两个迭代器。

      template <typename Driver, typename Passenger>
      struct duo_iterator { . . . };
      
      template <typename D, typename P>
      auto make_duo_iterator(D d, P p) -> duo_iterator<D, P> { . . . }
      
      sort(make_duo_iterator(begin(v1), begin(v2)),
           make_duo_iterator(end(v1), end(v2)));
      

      迭代器可以扩展为 multi_iterator 以使用任何重新排序算法,指向任意数量的额外捎带序列。
      这可能是一个有趣的小项目。或者,在 Boost 或其他地方可能已经存在类似的东西。

      EDIT2:

      忘记上面的。
      Eric Niebler 的 Range-v3 library 有一个 view::zip 包装器,“给定 N 个范围,返回一个新范围,其中第 M 个元素是对所有 N 个范围的第 M 个元素调用 make_tuple 的结果。”
      使用元组的第一个元素上的谓词对范围进行排序可能就可以解决问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-04
        • 2012-07-05
        • 1970-01-01
        • 2018-07-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多