【问题标题】:sorting table in place using stl sort使用 stl sort 就地排序表
【发布时间】:2014-11-11 22:55:35
【问题描述】:

我有一个 (i,j,k) 格式(来自稀疏矩阵)的巨大表(大约 50Gb),存储为

uint32_t * idx1, * idx2;
float * vals;
uint32_t tablesize;

我想用给定的比较函数对它进行排序,该函数是 idx1 和 idx2 的函数。这可以使用 std::sort 来完成吗?

具体来说,稀疏矩阵中每个值为 v 的非零条目 (i,j) 通过将 i 放在 idx1 中,j 放在 idx2 中,并将 v 放在 vals 中的相应条目中来存储。然后我想根据 (i1, j1, v1) 对这些条目进行排序

(i1 < i2) || (i1==i2 && j1 <= j2)

我收集到的关于在非标准数据类型上使用 std::sort 的示例假设每个被比较的项目都是一个类的单个实例;这里每个项目由不同数组中的三个值表示。

【问题讨论】:

  • 简而言之,查看std::sort 的3 个参数版本,然后查找functorfunction object
  • 所以你需要帮忙——如果我给你两个 (i,j,k) 值,告诉我们你将如何确定第一个值是否在第二个值之前。还有这个“表”是什么形式的?您需要更详细地告诉我们这些数据的结构。
  • 那么您希望对所有三个数组进行排序吗?最简单的方法是将它们全部组合成一个struct,并且只有一个该类型的数组。
  • 我不明白如何使用三参数版本的排序。 RandomAccessIterator 会在这里是什么?这就是我对非标准数据类型的意思:如果您的值按照 Jonathan 的建议存储为单个类对象(这对我来说是不可行的,因为将这样的结构构造为我的数据我会用完内存),但如果您的数据分布在多个数组中,则无关紧要。

标签: c++ algorithm sorting stl


【解决方案1】:

不幸的是,说服std::sort 或任何标准库处理条带数据非常困难。它旨在假设数据可以通过单个= 复制、通过一个move 移动或通过一个swap 交换。

最好的办法是使用boost::iterator_facade 编写一个自定义迭代器类来包装数据,并隐藏std::sort 的条纹数据格式。我过去想做类似的事情,但我的工作区不允许我们使用boost编辑: 当您的外观被取消引用时,它可能需要创建某种可以分配/移动/交换的代理对象,并对每个条带数组执行正确的操作。这不是微不足道的。

下一个最佳选择是创建一个从零到 N 的 ints 数组,每个数组代表一个条带数据数组的索引。向std::sort 写入一个自定义函子,对这个数组进行排序以匹配您的条件。当您拥有如此庞大的数据集时,这显然远非理想。

【讨论】:

  • 我认为这个答案最接近你想要的,但你可能需要考虑滚动你自己的排序来优化真正巨大的数组; 50 GB 的数据,即使它在 RAM 中通过外部排序算法得到更好的处理,以更好地利用访问的局部性;您也可以从并行排序中受益。
  • 好点。我回答的是“你能在这里使用std::sort”而不是“应该你”。我上面列出的所有体操可能会比复制粘贴基本的qsort 实现并对其进行调整以适应您的需要更费力,而且手动调整的实现也可能更快。跨度>
  • 在取消引用时返回代理的迭代器不是 RandomAccessIterator,因为 ForwardIterators(以及因此更精细的任何东西)在取消引用时需要返回 value_type&amp;const value_type&amp;。 (根据 [forward.iterators]/1.3)
【解决方案2】:

如果您必须继续使用现有数据结构(本质上是三个std::vectors 中的std::tuple),那么使用boost::zip_iterator似乎 是要走的路。 zip_iterator 将三个迭代器(两个用于索引,一个用于值)视为单个元组,您可以使用自定义比较函数对象对数据进行就地排序。唉,boost::zip_iterator 不能和std::sort 一起使用,正如this Q&A 中解释的那样,因为它不能被写入。

这意味着您必须编写自己的可与std::sort 一起使用的 zip_iterator 类。请注意,这不是一个简单的练习,请参阅this Q&A 和/或此paper

std::vectorstd::tuple 进行排序要容易得多。我在下面的尝试使用两个索引和一个值的std::tuple,并将这些条目存储到std::vector。对于排序,我使用 C++14 通用 lambda 将两个索引转发到一个较小的元组中,并使用 std::tuple 的库 operator&lt; 按字典顺序比较它们(即首先在行索引上,然后在列索引上) .

#include <algorithm>
#include <iostream>
#include <tuple>
#include <vector>

using index = uint32_t;
using value = float;
using sparse_entry = std::tuple<index, index, value>;
using sparse_matrix = std::vector<sparse_entry>;

int main()
{
    // sparse 3x3 matrix
    auto m = sparse_matrix { 
        std::make_tuple( 1, 1, -2.2), 
        std::make_tuple( 1, 0, 42  ), 
        std::make_tuple( 0, 2,  3.4), 
        std::make_tuple( 0, 1,  1.7) 
    };    

    // sort by row-index, then column-index
    std::sort(begin(m), end(m), [](auto const& L, auto const& R) {
        return 
            std::forward_as_tuple(std::get<0>(L), std::get<1>(L)) <
            std::forward_as_tuple(std::get<0>(R), std::get<1>(R))
        ;
    });

    for (auto const& elem : m)
        std::cout << "{ " << std::get<0>(elem) << ", " << std::get<1>(elem) << ", " << std::get<2>(elem) << "}, \n";
}

Live Example.

如果您的应用程序可以使用这种转换后的数据布局(并且可能因为缓存性能原因不能使用),那么上面的代码将按照您的意愿进行排序。

注意:正如@Casey 提到的,您也可以使用std::tie 而不是std::forward_as_tuple,但是当您将sparse_entry 更改为成熟的用户定义类时,这可能会咬到您getter 按值返回。

【讨论】:

  • std::tie 的输入速度比std::forward_as_tuple 快一些,在这种情况下效果相同。
  • @Casey std::tie 只接受左值,而 std::forward_as_tuple 也适用于按值返回的 getter。还有,以前是std::tie不是constexpr,所以养成了不使用std::tie的习惯。
  • std::get 没有按值返回的重载,并且几乎不可能扩展,因为 (a) 禁止在 std 中重载函数,并且 (b) 不能部分地重载专门化功能。这种扩展在传递左值时按值返回也是相当不正当的。无论如何,我确实编辑了我的评论以添加“在这种情况下”;)
  • @Casey 更新了我的答案,tnx!顺便说一句,对于吸气剂,你当然会写 std::tie(L.row(), L.column()) 而不是 std::get
  • 啊,我明白了。我将您评论中的“吸气剂”误认为是std::get 的味道。这现在更有意义了。
猜你喜欢
  • 2010-12-15
  • 1970-01-01
  • 2020-09-11
  • 1970-01-01
  • 1970-01-01
  • 2019-11-03
  • 1970-01-01
相关资源
最近更新 更多