C中的Golfed + Fast排序


11

[ 最新更新:提供基准测试程序和初步结果,请参见下文]

所以我想用一个经典的应用程序来测试速度/复杂性的权衡:排序。

编写ANSI C函数,以递增顺序对浮点数数组进行排序。

您不能使用任何库,系统调用,多线程或内联ASM。

条目由两个部分组成:代码长度性能。评分方式如下:条目将按长度(没有空格的#个字符的对数,因此可以保留一些格式)和性能(一个基准上的#秒的对数)进行排序,并且每个间隔[best,worst]线性归一化为[ 0,1]。一个程序的总分将是两个标准化分数的平均值。最低分获胜。每个用户一个条目。

必须(最终)进行排序(即输入数组必须在返回时包含排序的值),并且您必须使用以下签名,包括名称:

void sort(float* v, int n) {

}

要计算的字符:sort函数中的字符,包括的签名以及函数调用的其他函数(但不包括测试代码)。

程序必须处理任何数字值float和长度> = 0,最大2 ^ 20的数组。

我将sort其及其依赖项插入测试程序,然后在GCC上编译(没有高级选项)。我将一堆数组放入其中,验证结果和总运行时间的正确性。测试将在Ubuntu 13下的Intel Core i7 740QM(Clarksfield)上运行。
阵列长度将跨整个允许范围,短阵列的密度更高。值将是随机的,且具有粗尾分布(正负范围内)。重复的元素将包含在某些测试中。
该测试程序位于:https : //gist.github.com/anonymous/82386fa028f6534af263
它将提交导入为user.cTEST_COUNT实际基准测试中的测试用例()数量为3000。请在问题评论中提供任何反馈。

截止日期:3周(2014年4月7日,格林尼治标准时间16:00)。我将在2周内发布基准测试。
最好在截止日期前发布,以免将代码泄露给竞争对手。

根据基准测试发布的初步结果:
以下是一些结果。最后一列以百分比形式显示分数,分数越高越好,将Johnny Cage放在第一位。比一部分算法慢几个数量级的算法是在部分测试中运行的,并且需要时间推断。qsort包含C自己的用于比较(强尼的速度更快!)。我将在结束时进行最终比较。

在此处输入图片说明


3
可以提供基准吗?不同的排序功能会根据数据的性质执行不同的操作。例如,对于小型阵列,气泡排序比stdlib quicksort更快。我们可能会针对您的基准进行优化。
Claudiu 2014年

@Claudiu我曾经看过一个很棒的简短版本的quicksort,它在每个元素都不同的数据上也可以运行。但是,如果某些元素相同,那么它的运行速度绝对是蜗牛的。我不是在谈论已知的问题,即对排序/部分排序数组中的数据透视表选择不当。我的测试数据被完全随机地拖曳了。这个特定的版本只是不喜欢重复。很奇怪,但事实如此。
级圣河

3
欢迎来到PPCG!尽管我们不禁止特定语言的挑战,但我们强烈鼓励您尽可能以与语言无关的方式提出问题。考虑一下您的下一个问题,并乐于解决这个问题!
乔纳森·范·马特雷

1
@steveverrill:我不关注。单位是什么都没有关系,因为无论如何您将单位从0缩放到1。如果min是1小时,max是3小时,则无论min是60分钟,max是180分钟还是90分钟,耗时1.5小时的东西都是0.25
Claudiu 2014年

1
OP只说没有内联汇编-他没有说任何有关内在函数的内容。
Paul R

Answers:


6

150个字符

快速排序。

/* 146 character.
 * sizeup 1.000; speedup 1.000; */
#define REC_SIZE    \
    sort(l, v+n-l); \
    n = l-v;

/* 150 character.
 * sizeup 1.027; speedup 1.038; */
#define REC_FAST  \
    sort(v, l-v); \
    n = v+n-l;    \
    v = l;

void sort(float* v, int n)
{
    while ( n > 1 )
     {
       float* l = v-1, * r = v+n, x = v[n/2], t;
L:
       while ( *++l < x );
       while ( x < (t = *--r) );

       if (l < r)
        {
          *r = *l; *l = t;
          goto L;
        }
       REC_FAST
     }
}

压缩的。

void sort(float* v, int n) {
while(n>1){float*l=v-1,*r=v+n,x=v[n/2],t;L:while(*++l<x);while(x<(t=*--r));if(l<r){*r=*l;*l=t;goto L;}sort(v,l-v);n=v+n-l;v=l;}
}

领先比赛!
2014年

3

150个字符(无空格)

void sort(float *v, int n) {
    int l=0;
    float t, *w=v, *z=v+(n-1)/2;

    if (n>0) {
      t=*v; *v=*z; *z=t;
      for(;++w<v+n;)
        if(*w<*v)
        {
          t=v[++l]; v[l]=*w; *w=t;
        }
      t=*v; *v=v[l]; v[l]=t;
      sort(v, l++);
      sort(v+l, n-l);
    }
}

太棒了,第一次入学!
2014年

随时通过SSE发布答案,尽管我对挑战的“便携式”解决方案感兴趣,但我会将其列在记分板上。
2014年

if(*w<*v) { t=v[++l]; v[l]=*w; *w=t; }可以是if(*w<*v) t=v[++l], v[l]=*w, *w=t;
ASKASK

3

67 70 69字符

根本不快,但是非常小。我猜这是选择排序和冒泡排序算法之间的混合体。如果您实际上是在尝试阅读本文,那么您应该知道++i-v-n与相同++i != v+n

void sort(float*v,int n){
    while(n--){
        float*i=v-1,t;
        while(++i-v-n)
            *i>v[n]?t=*i,*i=v[n],v[n]=t:0;
    }
}

if(a)b-> a?b:0保存一个字符。
ugoren 2014年

好吧,++i-v-n这当然与++i != v+n仅在有条件的情况下相同。
wchargin 2014年

@ugoren我认为您在错误答案上发表了评论
ASKASK 2014年

@ASKASK,if(*i>v[n])...->*i>v[n]?...:0
ugoren 2014年

您确定优先顺序是这样工作的吗?
ASKASK

2

123个字符(+3个换行符)

标准Shell排序,压缩。

d,i,j;float t;
void sort(float*v,int n){
for(d=1<<20;i=d/=2;)for(;i<n;v[j]=t)for(t=v[j=i++];j>=d&&v[j-d]>t;j-=d)v[j]=v[j-d];
}  

PS:发现它仍然比quicksort慢10倍。您最好忽略此条目。


您选择的差距可能会更好。这也许就是为什么它比quicksort慢很多的原因。en.wikipedia.org/wiki/Shellsort#Gap_sequences
FDinoff

我惊讶地发现间隙序列对速度有多大影响。顺序很好,它接近快速排序,但以我的经验来说仍然很慢。
Florian F

不要对自己太苛刻。您排名第三。
凯文

2

395个字符

合并排序。

void sort(float* v,int n){static float t[16384];float*l,*r,*p,*q,*a=v,*b=v+n/2,
*c=v+n,x;if(n>1){sort(v,n/2);sort(v+n/2,n-n/2);while(a!=b&&b!=c)if(b-a<=c-b&&b-
a<=16384){for(p=t,q=a;q!=b;)*p++=*q++;for(p=t,q=t+(b-a);p!=q&&b!=c;)*a++=(*p<=
*b)?*p++:*b++;while(p!=q)*a++=*p++;}else{for(l=a,r=b,p=t,q=t+16384;l!=b&&r!=c&&
p!=q;)*p++=(*l<=*r)?*l++:*r++;for(q=b,b=r;l!=q;)*--r=*--q;for(q=t;p!=q;)*a++=
*q++;}}}

格式化的。

static float* copy(const float* a, const float* b, float* out)
{   while ( a != b ) *out++ = *a++; return out;
}
static float* copy_backward(const float* a, const float* b, float* out)
{   while ( a != b ) *--out = *--b; return out;
}

static void ip_merge(float* a, float* b, float* c)
{
    /* 64K (the more memory, the better this performs). */
#define BSIZE (1024*64/sizeof(float))
    static float t[BSIZE];

    while ( a != b && b != c )
     {
       int n1 = b - a;
       int n2 = c - b;

       if (n1 <= n2 && n1 <= BSIZE)
        {
          float* p = t, * q = t + n1;
          /* copy [a,b] sequence. */
          copy(a, b, t);
          /* merge. */
          while ( p != q && b != c )
             *a++ = (*p <= *b) ? *p++ : *b++;
          /* copy remaining. */
          a = copy(p, q, a);
        }
       /* backward merge omitted. */
       else
        {
          /* there are slicker ways to do this; all require more support
           * code. */
          float* l = a, * r = b, * p = t, * q = t + BSIZE;
          /* merge until sequence end or buffer end is reached. */
          while ( l != b  && r != c && p != q )
             *p++ = (*l <= *r) ? *l++ : *r++;
          /* compact remaining. */
          copy_backward(l, b, r);
          /* copy buffer. */
          a = copy(t, p, a);
          b = r;
        }
     }
}

void sort(float* v, int n)
{
    if (n > 1)
     {
       int h = n/2;
       sort(v, h); sort(v+h, n-h); ip_merge(v, v+h, v+n);
     }
}

2

331 326 327 312个字符

基数一次排序8位。使用奇特的位hack来获取负浮点数以正确排序(从http://stereopsis.com/radix.html盗取)。它不是那么紧凑,但是确实非常快(比最快的prelim入门快8倍)。我希望速度胜过代码大小...

#define I for(i=n-1;i>=0;i--)
#define J for(i=0;i<256;i++)
#define R for(r=0;r<4;r++)
#define F(p,q,k) I p[--c[k][q[i]>>8*k&255]]=q[i]

void sort(float *a, int n) {
  int *A = a,i,r,x,c[4][257],B[1<<20];
  R J c[r][i]=0;
  I {
    x=A[i]^=A[i]>>31|1<<31;
    R c[r][x>>8*r&255]++;
  }
  J R c[r][i+1]+=c[r][i];

  F(B,A,0);
  F(A,B,1);
  F(B,A,2);
  F(A,B,3)^(~B[i]>>31|1<<31);
}

2

511 424字

原位基数

更新:切换为较小数组大小的插入排序(将基准性能提高了4.0倍)。

#define H p[(x^(x>>31|1<<31))>>s&255]
#define L(m) for(i=0;i<m;i++)
void R(int*a,int n,int s){if(n<64){float*i,*j,x;for(i=a+1;i<a+n;i++){x=*i;for(
j=i;a<j&&x<j[-1];j--)*j=j[-1];*j=x;}}else{int p[513]={},*q=p+257,z=255,i,j,x,t
;L(n)x=a[i],H++;L(256)p[i+1]+=q[i]=p[i];for(z=255;(i=p[z]-1)>=0;){x=a[i];while
((j=--H)!=i)t=x,x=a[j],a[j]=t;a[i]=x;while(q[z-1]==p[z])z--;}if(s)L(256)R(a+p[
i],q[i]-p[i],s-8);}}void sort(float* v,int n){R(v,n,24);}

格式化的。

/* XXX, BITS is a power of two. */
#define BITS 8
#define BINS (1U << BITS)
#define TINY 64

#define SWAP(type, a, b) \
    do { type t=(a);(a)=(b);(b)=t; } while (0)

static inline unsigned int floatbit_to_sortable_(const unsigned int x)
{   return x ^ ((0 - (x >> 31)) | 0x80000000);
}

static inline unsigned int sortable_to_floatbit_(const unsigned int x)
{   return x ^ (((x >> 31) - 1) | 0x80000000);
}

static void insertsort_(unsigned int* a, unsigned int* last)
{
    unsigned int* i;
    for ( i = a+1; i < last; i++ )
     {
       unsigned int* j, x = *i;
       for ( j = i; a < j && x < *(j-1); j-- )
          *j = *(j-1);
       *j = x;
     }
}

static void radixsort_lower_(unsigned int* a, const unsigned int size,
  const unsigned int shift)
{
    /* @note setup cost can be prohibitive for smaller arrays, switch to
     * something that performs better in these cases. */
    if (size < TINY)
     {
       insertsort_(a, a+size);
       return;
     }

    unsigned int h0[BINS*2+1] = {}, * h1 = h0+BINS+1;
    unsigned int i, next;

    /* generate histogram. */
    for ( i = 0; i < size; i++ )
       h0[(a[i] >> shift) % BINS]++;

    /* unsigned distribution.
     * @note h0[BINS] == h1[-1] == @p size; sentinal for bin advance. */
    for ( i = 0; i < BINS; i++ )
       h0[i+1] += (h1[i] = h0[i]);

    next = BINS-1;
    while ( (i = h0[next]-1) != (unsigned int) -1 )
     {
       unsigned int x = a[i];
       unsigned int j;
       while ( (j = --h0[(x >> shift) % BINS]) != i )
          SWAP(unsigned int, x, a[j]);
       a[i] = x;
       /* advance bins.
        * @note skip full bins (zero sized bins are full by default). */
       while ( h1[(int) next-1] == h0[next] )
          next--;
     }

    /* @note bins are sorted relative to one another at this point but
     * are not sorted internally. recurse on each bin using successive
     * radii as ordering criteria. */
    if (shift != 0)
       for ( i = 0; i < BINS; i++ )
          radixsort_lower_(a + h0[i], h1[i] - h0[i], shift-BITS);
}

void sort(float* v, int n)
{
    unsigned int* a = (unsigned int*) v;
    int i;

    for ( i = 0; i < n; i++ )
       a[i] = floatbit_to_sortable_(a[i]);

    radixsort_lower_(a, n, sizeof(int)*8-BITS);

    for ( i = 0; i < n; i++ )
       a[i] = sortable_to_floatbit_(a[i]);
}

真好!尝试标记原始答案。
2014年

@Mau:谢谢,一定会的。想提及基准测试代码中的错误。强制转换为void*in qsort(第88行)引发了指针算法的失败。
MojoJojoBojoHojo 2014年

1

121个 114 111字符

只是快速和肮脏的冒泡,具有递归。可能不是很有效。

void sort(float*v,int n){int i=1;float t;for(;i<n;i++)v[i-1]>(t=v[i])&&(v[i]=v[i-1],v[i-1]=t);n--?sort(v,n):0;}

或者,长版

void sort(float* values, int n) {
  int i=1;  // Start at 1, because we check v[i] vs v[i-1]
  float temp;
  for(; i < n; i++) {
    // If v[i-1] > v[i] is true (!= 0), then swap.
    // Note I am assigning values[i] to temp here. Below I want to use commas
    // so the whole thing fits into one statement, but if you assign temp there you will get sequencing issues (i.e unpredictable swap results)
    values[i - 1] > (temp = values[i]) && (
    // I tried the x=x+y,y=x-y,x=x-y trick, but using a temp
    // turns out to be shorter even if we have to declare the t variable.
      values[i] = values[i - 1], 
      values[i - 1] = temp);
  }

  // If n == 1, we are done. Otherwise, sort the first n - 1 elements recursively. 
  // The 0 is just because the third statement cannot be empty.
  n-- ? sort(values, n) : 0;
}

顺便说一句,我发现了一个非常有趣的算法在这里:rosettacode.org/wiki/Sorting_algorithms/Pancake_sort#C 但我可以把它压缩不足以击败114 :)
CompuChip

您的程序在某些情况下似乎无法完成,而在其他情况下则超出范围。
2014年

@Mau我手动在某些输入上对其进行了测试,并且似乎可以正常工作,但是由于时间不足,我没有非常认真地对其进行测试,因此我确信某个地方存在一些不良行为。您能张贴一个遇到麻烦的测试用例吗?
CompuChip 2014年

上面提供的测试程序:)
2014年

嗯,我尝试运行它,但在清理部分出现了munmap_chunk():无效指针错误,但是测试失败一事无成。但是,您是对的,这是一个错误的错误,而且我似乎遇到了一些排序问题(用逗号分隔的语句列表未达到我的期望)。我会尝试修复它。
CompuChip 2014年

1

221193172 字符

堆排序-不是最小的,而是就地的并保证O(n * log(n))行为。

static void sink(float* a, int i, int n, float t)
{
    float* b = a+i;

    for ( ; (i = i*2+2) <= n; b = a+i )
     {
       i -= (i == n || a[i] < a[i-1]) ? 1 : 0;

       if (t < a[i])
          *b = a[i];
       else
          break;
     }
    *b = t;
}

void sort(float* a, int n)
{
    int i;
    /* make. */
    for ( i = n/2-1; i >= 0; i-- )
       sink(a, i, n, a[i]);
    /* sort. */
    for ( i = n-1; i > 0; i-- )
     {
       float t = a[i]; a[i] = a[0];
       sink(a, 0, i, t);
     }
}

压缩的。

void sort(float* a,int n){
#define F(p,q,r,x,y) for(i=n/p;q>0;){t=a[i];r;for(j=x;(b=a+j,j=j*2+2)<=y&&(j-=(j==y||a[j]<a[j-1]),t<a[j]);*b=a[j]);*b=t;}
float*b,t;int i,j;F(2,i--,,i,n)F(1,--i,a[i]=*a,0,i)
}

您可以通过减去空格来保存一些字符。可能还有强制功能签名,但是由于有些条目已经计数,因此我要求发问者澄清是否应该计数。
乔纳森·范·马特雷

@ user19425:如果您使用TEST_COUNT= 3000 运行测试程序,则似乎至少一项测试失败。
2014年

1

154166个字符

好的,这是一个更长但更快的快速排序。

void sort(float*v,int n){while(n>1){float*j=v,*k=v+n-1,t=*j;while(j<k){while(j<k&&*k>=t)k--;*j=*k;while(j<k&&*j<t)j++;*k=*j;}*k++=t;sort(k,v+n-k);n=j-v;}}

这是对排序后的输入进行保留的更正。并格式化,因为空格不计算在内。

void sort(float*v, int n){
    while(n>1){
        float*j=v, *k=j+n/2, t=*k;
        *k = *j;
        k = v+n-1;
        while(j<k){
            while(j<k && *k>=t) k--;
            *j=*k;
            while(j<k && *j<t) j++;
            *k=*j;
        }
        *k++ = t;
        sort(k,v+n-k);
        n = j-v;
    }
}

在某些情况下,此版本似乎超出了范围,而在其他情况下并未终止。
2014年

PS:好的,它在排序集中非常慢。但是问题陈述说输入是随机的。
Florian F

值是随机的。我从没说过它们的顺序:-)但是,是的,有一些块覆盖了所有值的约10%的升序和另外10%的降序。
2014年

1
很公平。并且sort()应该对排序的输入起作用。我将更新提交的内容,然后...
弗洛里安F

1

150个字符

Shellsort(带Knuth间隙)。

void sort(float* v, int n) {
float*p,x;int i,h=0;while(2*(i=h*3+1)<=n)h=i;for(;h>0;h/=3)for(i=h;i<n;i++){x=v[i];for(p=v+i-h;p>=v&&x<*p;p-=h)p[h]=*p;p[h]=x;}
}

格式化的。

static void hsort(float* v, const int h, const int n)
{
    int i;
    for (i = h; i < n; i++) {
        float* p, x = v[i];
        for (p = v + i-h; p >= v && x < *p; p -= h)
            p[h] = *p;
        p[h] = x;
    }
}

void sort(float* v, int n)
{
    int i, h = 0;
    while (2*(i = h*3+1) <= n)
        h = i;
    for (; h > 0; h /= 3)
        hsort(v, h, n);
}

1

C 270(高尔夫球)

#define N 1048576
void sort(float*v,int n)
{
float f[N],g;
int m[N],i,j,k,x;
g=v[0];k=0;
for(i=0;i<n;i++){for(j=0;j<n;j++){if(m[j]==1)continue;if(v[j]<g){g=v[j];k=j;}}f[i]=g;m[k]=1;for(x=0;x<n;x++){if(m[x]==0){g=v[x];k=x;break;}}}
for(i=0;i<n;i++){v[i]=f[i];}
}

说明:一个空白数组用于存储每个连续的最小值。一个int数组是一个带0的掩码,指示尚未复制该数字。获得最小值后,mask = 1会跳过已使用的数字。然后将数组复制回原始。

我更改了代码以消除对库函数的使用。


0

144

我毫不客气地拿了约翰尼的代码,添加了微小的优化并以一种非常肮脏的方式压缩了代码。它应该更短,更快。

请注意,根据您的编译器,必须将sort(q,v + n- ++ q)替换为sort(++ q,v + nq)。

#define w ;while(
void sort(float*v, int n){
    w n>1){
        float *p=v-1, *q=v+n, x=v[n/2], t
        w p<q){
            w *++p<x )
            w *--q>x );
            if( p<q ) t=*p, *p=*q, *q=t;
        }
        sort(q,v+n- ++q);
        n = p-v;
    }
}

好吧,实际上,我开始编写代码并对其进行了优化,但是看起来Johnny已经做出了所有正确的选择。所以我最终得到了准他的代码。我没有想到goto技巧,但是我可以没有。


0

228个字元

Radixsort。

void sort(float* v, int n) {
#define A(x,y,z) for(x=y;x<z;x++)
#define B h[(a[i]^(a[i]>>31|1<<31))>>j*8&255]
    int m[1<<20],*a=v,*b=m,*t,i,j;
    A(j,0,4) {
        int h[256] = {};
        A(i,0,n) B++;
        A(i,1,256) h[i] += h[i-1];
        for (i = n-1; i >= 0; i--)
            b[--B] = a[i];
        t = a, a = b, b = t;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.