Гольф + Швидке сортування в С


11

[ Останні оновлення: програма порівняння та попередні результати доступні, див. Нижче]

Тому я хочу перевірити компроміс швидкості / складності за допомогою класичного застосування: сортування.

Напишіть функцію ANSI C, яка сортує масив чисел з плаваючою точкою у порядку збільшення .

Ви не можете використовувати будь-які бібліотеки, системні дзвінки, багатопотокові чи вбудовані ASM.

Записи судяться за двома компонентами: довжиною коду та продуктивністю. Оцінка за наступним чином: записи будуть відсортовані за довжиною (журнал #characters без пробілу, так що ви можете зберегти деяке форматування) та за продуктивністю (журнал # секунди за еталоном), і кожен інтервал [найкращий, найгірший] лінійно нормалізується на [ 0,1]. Загальна оцінка програми буде середньою з двох нормованих балів. Виграє найнижчий рахунок. Один запис на користувача.

Сортування має бути (зрештою) на місці (тобто вхідний масив повинен містити відсортовані значення під час повернення), і ви повинні використовувати наступну підпис, включаючи імена:

void sort(float* v, int n) {

}

Символи, які слід підрахувати: ті, хто є у sortфункції, підпис включений, а також додаткові функції, викликані нею (але не включаючи код тестування)

Програма повинна обробляти будь-яке числове значення floatта масиви довжиною> = 0, до 2 ^ 20.

Я підключу sortйого та його залежності до програми тестування та компілюю на GCC (немає фантазійних варіантів). Я вставлю в неї купу масивів, перевірять правильність результатів та загальний час виконання. Тести будуть проводитися на Intel Core i7 740QM (Clarksfield) під Ubuntu 13. Довжина
масиву буде охоплювати весь дозволений діапазон, з більш високою щільністю коротких масивів. Значення будуть випадковими з розподілом жиру на хвости (як у позитивному, так і в негативному діапазонах). Дубліровані елементи будуть включені в деякі тести.
Тестова програма доступна тут: https://gist.github.com/anonymous/82386fa028f6534af263
Імпортує подання як user.c. Кількість тестових випадків ( TEST_COUNT) у фактичному орієнтирі становитиме 3000. Будь ласка, надайте будь-які відгуки у коментарях до питання.

Кінцевий термін: 3 тижні (7 квітня 2014 р., 16:00 GMT). Я опублікую тест через 2 тижні.
Можливо, доцільно буде розміщувати до крайнього строку, щоб уникнути передачі коду конкурентам.

Попередні результати щодо публікації еталону:
Ось деякі результати. В останньому стовпчику показник відображається у відсотках, чим вище, тим краще, ставлячи Джонні Кейдж на перше місце. Алгоритми, які були на порядок повільніші за решту, виконувались на підмножині тестів, а час екстраполювався. qsortДля порівняння включений власний C (Джонні швидший!). Я завершу підсумкове порівняння під час закриття.

введіть тут опис зображення


3
Чи можете ви надати орієнтир? Різні функції сортування виконують по-різному залежно від характеру даних. Наприклад, сортування бульбашок швидше, ніж stdlib quicksort для крихітних масивів. Ми можемо хотіти оптимізувати ваш показник.
Клавдіу

@Claudiu Я колись побачив прекрасну коротку версію quicksort, яка працювала так само добре, як і будь-яка інша в даних, де кожен елемент був різним. Але якщо деякі елементи були однаковими, то він бігав абсолютним темпом равлики. Я не говорю про відому проблему поганого вибору стрижня у відсортованих / частково відсортованих масивах. Дані моїх тестів були повністю випадковим чином переміщені. Ця конкретна версія просто не любила дублікатів. Дивно, але правда.
Річка Рівня Св.

3
Ласкаво просимо до PPCG! Хоча ми не забороняємо особливості, пов'язані з мовою, ми наполегливо рекомендуємо формулювати питання, коли це можливо, в мовно-агностичній формі. Розгляньте це для свого наступного питання, і розважтесь цим!
Джонатан Ван Матре

1
@steveverrill: Я не дотримуюся. Не має значення, яка ваша одиниця, тому що ви все одно масштабуєте її від 0 до 1. Якщо мінімум 1 година, а максимум - 3 години, то, що займає 1,5 години, буде 0,25, незалежно від того, чи мінімум 60 хвилин, максимум - 180 хвилин, і це займе 90 хвилин
Клавдіу

1
ОП лише сказав, що немає вбудованої збірки - він нічого не сказав про внутрішні характеристики.
Пол Р

Відповіді:


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;}
}

Ведуча гонки!
Мау

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);
    }
}

Дивовижний, перший запис!
Мау

Не соромтеся опублікувати відповідь у SSE, і я перерахую її на табло, хоча я зацікавлений у «портативних» рішеннях проблеми.
Мау

if(*w<*v) { t=v[++l]; v[l]=*w; *w=t; }можнаif(*w<*v) t=v[++l], v[l]=*w, *w=t;
ЗАПИТАТИ

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

Ну, ++i-v-nце те саме, що ++i != v+nтільки в умовному, звичайно.
wchargin

@ugoren Я думаю, що ви опублікували цей коментар щодо невірної відповіді
ЗАПИТАТИ

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

Ви впевнені, що так працює прецедент?
ЗАПИТАТИ

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: виявив, що це все-таки в 10 разів повільніше, ніж у швидкості. Ви можете також проігнорувати цей запис.


Ваш вибір прогалин може бути кращим. Ось, мабуть, тому це набагато повільніше, ніж швидкості. en.wikipedia.org/wiki/Shellsort#Gap_sequences
FDinoff

Я був здивований, дізнавшись, наскільки послідовність розриву впливає на швидкість. З гарною послідовністю він наближається до швидкості, але на моєму досвіді залишається повільнішим.
Флоріан Ф

Не будьте занадто жорсткі до себе. Ти на третьому місці.
Кевін

2

395 персонажів

Mergesort.

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 біт одночасно. Використовує фантазійний bithack, щоб отримати негативні поплавці для сортування правильно (вкрадено з http://stereopsis.com/radix.html ). Це не так компактно, але це дуже швидко (~ 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 рази).

#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]);
}

Приємно! Спробуйте позначити оригінальну відповідь.
Мау

@Mau: Дякую і зроблю. Хотів згадати про помилку в коді бенчмаркінгу. Передача void*в qsort(рядок 88) викидає арифметику вказівника.
MojoJojoBojoHojo

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

Ваша програма, здається, в деяких випадках не може завершити і виписати межі в інших випадках.
Мау

@Mau Я перевірив це на деяких введеннях вручну і, здавалося, спрацював нормально, але через брак часу я не перевіряв це дуже ретельно, тому я впевнений, що десь є погана поведінка. Чи можете ви опублікувати тестовий випадок, коли у вас виникли проблеми?
CompuChip

Тестова програма доступна вище :)
Мав

Хм, я спробував запустити його, я отримую деякі помилки `munmap_chunk (): недійсний вказівник` у частині очищення, але нічого не виходить із пробного тесту. Однак ви маєте рацію в тому, що є помилка по одному, і, здається, у мене є деякі проблеми з послідовністю (список висловлень, розділених комами, не робить того, на що я її очікую). Я спробую це виправити.
CompuChip

1

221 193 172 символи

Гіпсорт - Не найменший, але на місці і гарантує поведінку 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, здається, не вдалося хоча б одного тесту.
Мау

1

154 166 символів

Гаразд, ось триваліший, але швидший швидкий вибір.

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;
    }
}

Ця версія, здається, виписує межі в деяких випадках, а не закінчується в інших.
Мау

PS: Гаразд, це дуже повільно на сортованому наборі. Але заява проблеми говорить, що введення є випадковим.
Флоріан F

Значення випадкові. Я ніколи нічого не говорив про те, в якому порядку вони будуть знаходитись :-) Але так, є шматки, які охоплюють близько 10% усіх значень, відсортованих у порядку зростання та ще 10% у порядку зменшення.
Мав

1
Досить справедливо. І sort () повинен працювати на впорядкованому вході. Я оновлю своє подання, тоді ...
Флоріан F

1

150 символів

Шеллсорт (з / проміжок Кнута).

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, яка вказує на те, що число ще не скопійовано. Після отримання мінімального значення маска = 1 пропускає вже використані числа. Потім масив копіюється назад в оригінал.

Я змінив код, щоб усунути використання функцій бібліотеки.


0

144

Я безсоромно взяв код Джонні, додав крихітну оптимізацію і дуже брудно стиснув код. Він повинен бути коротшим і швидшим.

Зауважте, що залежно від компілятора, сортування (q, v + n- ++ q) має бути замінено на сортування (++ 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;
    }
}

Ну, власне, я почав формувати свій код і оптимізував його, але, схоже, Джонні вже зробив правильний вибір. Так я закінчив квазі його код. Я не думав про трюк гото, але міг обійтися і без цього.


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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.