Golfato + ordinamento rapido in C


11

[ Ultimo aggiornamento: programma di riferimento e risultati preliminari disponibili, vedi sotto]

Quindi voglio testare il compromesso di velocità / complessità con un'applicazione classica: l'ordinamento.

Scrivi una funzione ANSI C che ordina una matrice di numeri in virgola mobile in ordine crescente .

Non è possibile utilizzare qualsiasi librerie, chiamate di sistema, il multithreading o ASM in linea.

Voci giudicate su due componenti: lunghezza del codice e prestazioni. Punteggio come segue: le voci verranno ordinate per lunghezza (registro di # caratteri senza spazi bianchi, quindi è possibile mantenere un po 'di formattazione) e per prestazioni (registro di # secondi su un benchmark), e ogni intervallo [migliore, peggiore] normalizzato linearmente su [ 0,1]. Il punteggio totale di un programma sarà la media dei due punteggi normalizzati. Il punteggio più basso vince. Una voce per utente.

L'ordinamento dovrà (eventualmente) essere in atto (ovvero l'array di input dovrà contenere valori ordinati al momento del ritorno), ed è necessario utilizzare la seguente firma, inclusi i nomi:

void sort(float* v, int n) {

}

Caratteri da contare: quelli nella sortfunzione, inclusa la firma, oltre a funzioni aggiuntive richiamate da esso (ma non incluso il codice di test).

Il programma deve gestire qualsiasi valore numerico di floate matrici di lunghezza> = 0, fino a 2 ^ 20.

Inserirò le sortsue dipendenze in un programma di test e compilerò su GCC (nessuna opzione di fantasia). Fornirò un mucchio di array, verificherò la correttezza dei risultati e il tempo di esecuzione totale. I test verranno eseguiti su un Intel Core i7 740QM (Clarksfield) con Ubuntu 13. Le
lunghezze di array copriranno l'intero intervallo consentito, con una maggiore densità di array corti. I valori saranno casuali, con una distribuzione adiposa (sia nell'intervallo positivo che negativo). Gli elementi duplicati saranno inclusi in alcuni test.
Il programma di test è disponibile qui: https://gist.github.com/anonymous/82386fa028f6534af263
Importa l'invio come user.c. Il numero di casi di test ( TEST_COUNT) nel benchmark effettivo sarà 3000. Fornire feedback nei commenti alle domande.

Scadenza: 3 settimane (7 aprile 2014, 16:00 GMT). Pubblicherò il benchmark tra 2 settimane.
Potrebbe essere consigliabile pubblicare post vicino alla scadenza per evitare di distribuire il codice ai concorrenti.

Risultati preliminari, a partire dalla pubblicazione di riferimento:
ecco alcuni risultati. L'ultima colonna mostra il punteggio in percentuale, maggiore è il migliore, mettendo Johnny Cage al primo posto. Gli algoritmi che erano ordini di grandezza più lenti degli altri venivano eseguiti su un sottoinsieme di test e il tempo estrapolato. Il C qsortè incluso per il confronto (quello di Johnny è più veloce!). Eseguirò un confronto finale all'ora di chiusura.

inserisci qui la descrizione dell'immagine


3
Potete fornire il benchmark? Diverse funzioni di ordinamento funzionano diversamente in base alla natura dei dati. Ad esempio, l'ordinamento a bolle è più veloce dello stdlib quicksort per array di piccole dimensioni. Potremmo voler ottimizzare per il tuo benchmark.
Claudiu,

@Claudiu Una volta ho visto una versione breve e adorabile di quicksort, che funzionava altrettanto bene come qualsiasi altro sui dati in cui ogni elemento era diverso. Ma se alcuni elementi erano uguali, correva a un ritmo assoluto di lumaca. Non sto parlando del noto problema della cattiva scelta del perno in array ordinati / parzialmente ordinati. I miei dati di test sono stati completamente mescolati casualmente. A questa particolare versione non piacevano i duplicati. Strano, ma vero.
Level River St

3
Benvenuti in PPCG! Sebbene non vietiamo le sfide specifiche della lingua, incoraggiamo fortemente a formulare domande in modo agnostico quando possibile. Consideralo per la tua prossima domanda e divertiti con questo!
Jonathan Van Matre,

1
@steveverrill: non seguo. Non importa quale sia la tua unità perché la ridimensioni da 0 a 1 comunque. Se min è 1 ora e max è 3 ore, qualcosa che impiega 1,5 ore sarà 0,25 indipendentemente dal fatto che min sia 60 minuti, max sia 180 minuti e impiega 90 minuti
Claudiu

1
OP non ha detto che nessun assemblaggio in linea - non ha detto nulla sugli intrinseci.
Paul R

Risposte:


6

150 caratteri

Quicksort.

/* 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
     }
}

Compressa.

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

Alla guida della gara!
Mau,

3

150 caratteri (senza spazi bianchi)

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

Fantastico, primo ingresso!
Mau,

Sentiti libero di pubblicare una risposta con SSE e la elencherò nel quadro di valutazione, anche se sono interessato a soluzioni "portatili" per la sfida.
Mau,

if(*w<*v) { t=v[++l]; v[l]=*w; *w=t; }può essereif(*w<*v) t=v[++l], v[l]=*w, *w=t;
ASKASK il

3

67 70 69 caratteri

Per niente veloce, ma incredibilmente piccolo. Immagino sia un ibrido tra un ordinamento di selezione e un algoritmo di ordinamento a bolle. Se stai effettivamente cercando di leggere questo, allora dovresti sapere che ++i-v-nè lo stesso di ++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:0salva un carattere.
ugoren,

Bene, ++i-v-nè lo stesso che ++i != v+nsolo in un condizionale, ovviamente.
mercoledì

@ugoren Penso che tu abbia pubblicato quel commento sulla risposta sbagliata
ASKASK,

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

sei sicuro che funzioni così la prescedenza?
CHIEDERE il

2

123 caratteri (+3 nuove righe)

Un ordinamento Shell standard, compresso.

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: scoperto che è ancora 10 volte più lento di quicksort. Potresti anche ignorare questa voce.


La tua scelta di lacune potrebbe essere migliore. Questo è probabilmente il motivo per cui questo è molto più lento di quicksort. en.wikipedia.org/wiki/Shellsort#Gap_sequences
FDinoff

Sono stato sorpreso di scoprire quanto la sequenza di gap influenza la velocità. Con una buona sequenza si avvicina a quicksort ma rimane più lento nella mia esperienza.
Florian F

Non essere troppo duro con te stesso. Sei al terzo posto.
Kevin,

2

395 caratteri

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

Formattato.

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 caratteri

Radix ordina 8 bit alla volta. Utilizza un bithack di fantasia per ottenere correttamente i float negativi (ordinati da http://stereopsis.com/radix.html ). Non è così compatto, ma è davvero veloce (~ 8 volte più veloce della voce prelim più veloce). Spero che la dimensione del codice di briscola della velocità ...

#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 carattere

Inplace radixsort

Aggiornamento: passa all'ordinamento di inserzione per array di dimensioni inferiori (aumenta le prestazioni del benchmark di un fattore 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);}

Formattato.

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

Bello! Prova a contrassegnare la risposta originale.
Mau,

@Mau: grazie e lo farà. Volevo menzionare un errore nel codice di benchmarking. Il cast su void*in qsort(linea 88) sta gettando via l'aritmetica del puntatore.
MojoJojoBojoHojo

1

121 114 111 caratteri

Solo un gioco di bolle rapido e sporco, con ricorsione. Probabilmente non molto efficiente.

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

Oppure, la versione lunga

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

A parte questo, ho trovato un algoritmo davvero interessante qui: rosettacode.org/wiki/Sorting_algorithms/Pancake_sort#C Ma non riesco a comprimerlo abbastanza da battere 114 :)
CompuChip

il tuo programma sembra non riuscire a completarsi in alcuni casi e scrivere fuori dai limiti in altri casi.
Mau,

@Mau L'ho testato su alcuni input manualmente e sembrava funzionare bene, ma a causa della mancanza di tempo non l'ho testato con troppa pazienza, quindi sono sicuro che ci sia qualche cattivo comportamento da qualche parte. Potresti pubblicare un caso di prova in cui hai avuto problemi, per favore?
CompuChip

programma di test disponibile sopra :)
Mau

Hmm ho provato a eseguirlo, sto ricevendo alcuni errori `munmap_chunk (): pointer pointer non valido` nella parte cleanup, ma nulla del test fallito. Comunque hai ragione che c'è un errore off-by-one e mi sembra che abbia alcuni problemi di sequenziamento (l'elenco delle istruzioni separate da virgole non fa quello che mi aspetto). Proverò a ripararlo.
CompuChip

1

221 193 172 caratteri

Heapsort - Non il più piccolo, ma sul posto e garantisce un comportamento 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);
     }
}

Compressa.

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

È possibile salvare alcuni personaggi deducendo lo spazio bianco. E forse anche la firma della funzione obbligatoria, ma poiché ci sono alcune voci che contano, ho chiesto all'interrogatore di chiarire se dovesse essere contato.
Jonathan Van Matre,

@ user19425: se si esegue il programma di test con TEST_COUNT= 3000, sembra non riuscire almeno un test.
Mau,

1

154 166 caratteri

OK, ecco un quicksort più lungo ma più veloce.

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

Ecco una correzione per sopravvivere agli input ordinati. E formattato poiché lo spazio bianco non conta.

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

Questa versione sembra fuori dai limiti in alcuni casi, non terminando in altri.
Mau,

PS: OK, è molto lento su un set ordinato. Ma la dichiarazione del problema dice che l'input è casuale.
Florian F,

I valori sono casuali. Non ho mai detto nulla sull'ordine in cui sarebbero stati :-) Ma sì, ci sono blocchi che coprono circa il 10% di tutti i valori ordinati in ordine crescente e un altro 10% in ordine decrescente.
Mau,

1
Giusto. E un ordinamento () dovrebbe funzionare sull'input ordinato. Aggiornerò la mia richiesta, quindi ...
Florian F,

1

150 caratteri

Shellsort (con divario 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;}
}

Formattato.

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 (golfato)

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

Spiegazione: un array vuoto viene utilizzato per memorizzare ogni numero minimo successivo. Un array int è una maschera con 0 che indica che il numero non è stato ancora copiato. Dopo aver ottenuto il valore minimo, una maschera = 1 salta i numeri già utilizzati. Quindi l'array viene copiato nuovamente nell'originale.

Ho modificato il codice per eliminare l'uso delle funzioni di libreria.


0

144

Ho spudoratamente preso il codice di Johnny, ho aggiunto una piccola ottimizzazione e compresso il codice in un modo molto sporco. Dovrebbe essere più corto e più veloce.

Si noti che a seconda del compilatore, l'ordinamento (q, v + n- ++ q) deve essere sostituito dall'ordinamento (++ 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;
    }
}

Bene, in realtà, ho iniziato a formare il mio codice e l'ho ottimizzato, ma sembra che Johnny abbia già fatto tutte le scelte giuste. Quindi ho finito con quasi il suo codice. Non ho pensato al trucco, ma potrei farne a meno.


0

228 caratteri

Radix sort.

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;
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.