使用std :: sort()按元素块排序

时间:2009-10-30 12:29:15

标签: c++ c arrays sorting

我有一个边数组,它被定义为C风格的双精度数组,其中每4个双精度定义一个边,如下所示:

double *p = ...;
printf("edge1: %lf %lf %lf %lf\n", p[0], p[1], p[2], p[3]);
printf("edge2: %lf %lf %lf %lf\n", p[4], p[5], p[6], p[7]);

所以我想使用std::sort()按边长度对其进行排序。如果它是struct Edge { double x1, y1, x2, y2; }; Edge *p;,我会很高兴。

但在这种情况下,double数组的块大小不是由指针类型表示的。 qsort()允许您明确指定块大小,但std::sort() 通过指针类型推断块大小

出于性能原因(内存使用和CPU),让我们说创建新数组或以某种方式转换数组是不可取的。出于性能原因,我们可以说我们确实希望使用std::sort()代替qsort()

是否可以在转换数据时不浪费单个CPU周期来调用std::sort()

可能的方法:

一种显而易见的方法是尝试强制转换指针:

double *p = ...;
struct Edge { double arr[4]; };
Edge *p2 = reinterpret_cast<Edge*>(p);
std::sort(...);

但是如何确保数据正确对齐?另外,我如何确保它始终在所有平台和架构上正确对齐?

或者我可以使用typedef double[4] Edge;吗?

10 个答案:

答案 0 :(得分:2)

您可以使用“stride iterator”。 “stride iterator”包装另一个迭代器和整数步长。这是一个简单的草图:

template<typename Iter>
class stride_iterator
{
    ...

    stride_iterator(Iter it, difference_type step = difference_type(1))
    : it_(it), step_(step) {}

    stride_iterator& operator++() {
        std::advance(it_,step_);
        return *this;
    }

    Iter base() const { return it_; }

    difference_type step() const { return step_; }

    ...

private:
    Iter it_;
    difference_type step_;
};

此外,辅助功能如下

template<typename Iter>
stride_iterator<Iter> make_stride_iter(
    Iter it,
    typename iterator_traits<Iter>::difference_type step)
{
    return stride_iterator<Iter>(it,step);
}

template<typename Iter>
stride_iterator<Iter> make_stride_iter(
    stride_iterator<Iter> it,
    typename iterator_traits<Iter>::difference_type step)
{
    return stride_iterator<Iter>(it.base(),it.step() * step);
}

应该使用stride迭代器相当容易:

int array[N*L];
std::sort( make_stride_iter(array,L),
           make_stride_iter(array,L)+N );

自己(所有运算符)实现迭代器适配器可能不是一个好主意。正如Matthieu指出的那样,如果你使用Boost的iterator adapter工具,你可以安全地打字。

修改 我刚刚意识到这不会做你想要的,因为std :: sort只会交换每个块的第一个元素。我不认为这是一个简单易用的解决方案。我看到的问题是,在使用std :: sort时,无法(轻松)自定义交换“元素”(您的块)。您可以编写迭代器来返回带有特殊交换函数的特殊引用类型,但我不确定C ++标准是否保证std :: sort将使用通过ADL查找的交换功能。您的实现可能会将其限制为std :: swap。

我猜最好的答案仍然是:“只需使用qsort”。

答案 1 :(得分:2)

如何重新排序矢量?用1..N / L初始化向量,传递std :: sort一个比较器,它将元素i1 * L..i1 * L + L与i2 * L..i2 * L + L进行比较,并且当向量正确排序时,根据新订单重新排序C数组。

回应评论:是的,事情变得复杂,但这可能只是一个很好的并发症!看看here

答案 2 :(得分:1)

我不记得究竟是怎么做的,但是如果你可以伪造匿名函数,那么你可以创建一个comp(L)函数来返回长度为L的数组的comp版本...那样L成为参数,而不是全局参数,您可以使用qsort。正如其他人所提到的,除了你的数组已经排序,或者向后或类似的情况,qsort将与其他任何算法一样快。 (毕竟它有一个叫做quicksort的原因......)

答案 3 :(得分:1)

它不是任何ANSI,ISO或POSIX标准的一部分,但有些系统提供qsort_r()函数,它允许您将额外的上下文参数传递给比较函数。然后你可以这样做:

int comp(void *thunk, const void *a, const void *b)
{
    int L = (int)thunk;
    // compare a and b as you would normally with a qsort comparison function
}

qsort_r(array, N, sizeof(int) * L, (void *)L, comp);

或者,如果您没有qsort_r,则可以使用ffcall库中的callback(3)包在运行时创建闭包。例如:

#include <callback.h>
void comp_base(void *data, va_alist alist)
{
    va_start_int(alist);  // return type will be int

    int L = (int)data;
    const void *a = va_arg_ptr(alist, const void*);
    const void *b = va_arg_ptr(alist, const void*);

    // Now that we know L, compare
    int return_value = comp(a, b, L);

    va_return_int(alist, return_value);  // return return_value
}

...    

// In a function somewhere
typedef int (*compare_func)(const void*, const void*);

// Create some closures with different L values
compare_func comp1 = (compare_func)alloc_callback(&comp_base, (void *)L1);
compare_func comp2 = (compare_func)alloc_callback(&comp_base, (void *)L2);
...
// Use comp1 & comp2, e.g. as parameters to qsort
...
free_callback(comp1);
free_callback(comp2);

请注意,callback库是线程安全的,因为所有参数都在堆栈或寄存器中传递。该库负责分配内存,确保内存可执行,并在必要时刷新指令缓存,以允许在运行时执行动态生成的代码(即闭包)。它应该适用于各种各样的系统,但由于错误或缺乏实现,它很可能无法在你的系统上运行。

另请注意,这会给函数调用增加一些开销。上面对comp_base()的每次调用都必须从传递它的列表中解压缩它的参数(这是一种高度依赖于平台的格式)并将其返回值重新填入。大多数时候,这个开销是微不足道的,但对于一个比较函数,其中执行的实际工作非常小,并且在调用qsort()期间会多次调用,开销非常大。

答案 4 :(得分:1)

对于新问题,我们需要传递sort()一种迭代器,它不仅可以让我们比较正确的事物(即每次都要确保通过我们double[]的4个步骤而不是1)但也交换正确的东西(即交换4 double而不是1)。

我们可以通过简单地重新解释我们的双数组来完成它们,就像它是一个4个双精度数组一样。这样做:

typedef double Edge[4];

不起作用,因为您无法分配数组,swap将需要。但这样做:

typedef std::array<double, 4> Edge;

或者,如果不是C ++ 11:

struct Edge {
    double vals[4];
};

满足这两个要求。因此:

void sort(double* begin, double* end) {
    typedef std::array<double, 4> Edge;

    Edge* edge_begin = reinterpret_cast<Edge*>(begin);
    Edge* edge_end = reinterpret_cast<Edge*>(end);

    std::sort(edge_begin, edge_end, compare_edges);
}

bool compare_edges(const Edge& lhs, const Edge& rhs) {
    // to be implemented
}

如果你担心对齐,总是可以断言没有额外的填充:

static_assert(sizeof(Edge) == 4 * sizeof(double), "uh oh");

答案 5 :(得分:0)

std::array< std::array<int, L>, N > array;
// or std::vector< std::vector<int> > if N*L is not a constant
std::sort( array.begin(), array.end() );

答案 6 :(得分:0)

namespace
{
    struct NewCompare
    {
        bool operator()( const int a, const int b ) const
        {
            return a < b;
        }

    };
}

std::sort(array+start,array+start+L,NewCompare);

使用std::stable_sort(对真实数据集进行测试 - 对于某些数据混合其速度要快得多!

在许多编译器(GCC iirc)上有一个令人讨厌的问题:std :: sort()模板断言比较器是正确的,通过测试 TWICE ,一旦反转,以确保结果反转!这绝对会完全破坏正常构建中的中等数据集的性能。解决方案是这样的:

#ifdef NDEBUG
  #define WAS_NDEBUG
  #undef NDEBUG
#endif
#define NDEBUG
#include <algorithm>
#ifdef WAS_NDEBUG
  #undef WAS_NDEBUG
#else
  #undef NDEBUG
#endif

改编自这篇优秀的博客文章:http://www.tilander.org/aurora/2007/12/comparing-stdsort-and-qsort.html

答案 7 :(得分:0)

我不确定你是否可以在没有更多工作的情况下取得同样的结果。 std::sort()用于对由两个随机访问迭代器定义的元素序列进行排序。不幸的是,它确定了迭代器中元素的类型。例如:

std::sort(&array[0], &array[N + L]);

将对array的所有元素进行排序。问题是它假定迭代器的下标,递增,递减和其他索引运算符跨越序列的元素。我相信你可以对数组的切片进行排序的唯一方法(我认为这就是你所追求的),就是编写一个基于L进行索引的迭代器。这就是sellibitze has done in the stride_iterator answer

答案 8 :(得分:0)

Arkadiy有正确的想法。如果您创建一个指针数组并对其进行排序,则可以进行排序:

#define NN 7
#define LL 4

int array[NN*LL] = {
    3, 5, 5, 5,
    3, 6, 6, 6,
    4, 4, 4, 4,
    4, 3, 3, 3,
    2, 2, 2, 2,
    2, 0, 0, 0,
    1, 1, 1, 1
};

struct IntPtrArrayComp {
    int length;
    IntPtrArrayComp(int len) : length(len) {}
    bool operator()(int* const & a, int* const & b) {
        for (int i = 0; i < length; ++i) {
            if (a[i] < b[i]) return true;
            else if (a[i] > b[i]) return false;
        }
        return false;
    }
};

void sortArrayInPlace(int* array, int number, int length)
{
    int** ptrs = new int*[number];
    int** span = ptrs;
    for (int* a = array; a < array+number*length; a+=length) {
        *span++ = a;
    }
    std::sort(ptrs, ptrs+number, IntPtrArrayComp(length));
    int* buf = new int[number];
    for (int n = 0; n < number; ++n) {
        int offset = (ptrs[n] - array)/length;
        if (offset == n) continue;

        // swap
        int* a_n = array+n*length;
        std::move(a_n, a_n+length, buf);
        std::move(ptrs[n], ptrs[n]+length, a_n);
        std::move(buf, buf+length, ptrs[n]);

        // find what is pointing to a_n and point it 
        // to where the data was move to
        int find = 0;
        for (int i = n+1; i < number; ++i) {
            if (ptrs[i] == a_n) {
                find = i;
                break;
            }
        }
        ptrs[find] = ptrs[n];
    }
    delete[] buf;
    delete[] ptrs;
}

int main()
{
    for (int n = 0; n< NN; ++n) {
        for (int l = 0; l < LL; ++l) {
            std::cout << array[n*LL+l];
        }
        std::cout << std::endl;
    }
    std::cout << "----" << std::endl;
    sortArrayInPlace(array, NN, LL);
    for (int n = 0; n< NN; ++n) {
        for (int l = 0; l < LL; ++l) {
            std::cout << array[n*LL+l];
        }
        std::cout << std::endl;
    }
    return 0;
}

输出:

3555
3666
4444
4333
2222
2000
1111
----
1111
2000
2222
3555
3666
4333
4444

答案 9 :(得分:0)

很多这些答案看起来有点矫枉过正。如果你真的必须使用jmucchiello的例子来做C ++风格:

template <int Length>
struct Block
{
    int n_[Length];

    bool operator <(Block const &rhs) const
    {
        for (int i(0); i < Length; ++i)
        {
            if (n_[i] < rhs.n_[i])
                return true;
            else if (n_[i] > rhs.n_[i])
                return false;
        }
        return false;
    }
};

然后按:

排序
sort((Block<4> *)&array[0], (Block<4> *)&array[NN]);

它不一定要复杂。