Golf + Tri rapide en C


11

[ Dernière mise à jour: programme de référence et résultats préliminaires disponibles, voir ci-dessous]

Je veux donc tester le compromis vitesse / complexité avec une application classique: le tri.

Écrivez une fonction ANSI C qui trie un tableau de nombres à virgule flottante dans l' ordre croissant .

Vous ne pouvez utiliser aucune bibliothèque, appel système, multithreading ou ASM en ligne.

Entrées jugées sur deux composantes: la longueur du code et les performances. Notation comme suit: les entrées seront triées par longueur (journal de # caractères sans espace blanc, afin que vous puissiez conserver une mise en forme) et par performance (journal de # secondes sur une référence), et chaque intervalle [meilleur, pire] normalisé linéairement à [ 0,1]. Le score total d'un programme sera la moyenne des deux scores normalisés. Le score le plus bas l'emporte. Une entrée par utilisateur.

Le tri devra (éventuellement) être en place (c'est-à-dire que le tableau d'entrée devra contenir des valeurs triées au moment du retour), et vous devez utiliser la signature suivante, y compris les noms:

void sort(float* v, int n) {

}

Caractères à compter: ceux de la sortfonction, signature incluse, plus les fonctions supplémentaires appelées par celle-ci (mais sans inclure le code de test).

Le programme doit gérer toute valeur numérique floatet tableaux de longueur> = 0, jusqu'à 2 ^ 20.

Je vais brancher sortet ses dépendances dans un programme de test et compiler sur GCC (pas d'options fantaisistes). Je vais y alimenter un tas de tableaux, vérifier l'exactitude des résultats et le temps d'exécution total. Les tests seront exécutés sur un Intel Core i7 740QM (Clarksfield) sous Ubuntu 13.
Les longueurs de baies couvriront toute la plage autorisée, avec une densité plus élevée de baies courtes. Les valeurs seront aléatoires, avec une distribution de queue grasse (à la fois dans les plages positives et négatives). Des éléments dupliqués seront inclus dans certains tests.
Le programme de test est disponible ici: https://gist.github.com/anonymous/82386fa028f6534af263
Il importe la soumission en tant que user.c. Le nombre de cas de test ( TEST_COUNT) dans le benchmark réel sera de 3000. Veuillez fournir vos commentaires dans les commentaires de la question.

Délai: 3 semaines (7 avril 2014, 16h00 GMT). Je posterai le benchmark dans 2 semaines.
Il peut être conseillé de publier près de la date limite pour éviter de donner votre code aux concurrents.

Résultats préliminaires, à la date de publication du benchmark:
Voici quelques résultats. La dernière colonne montre le score en pourcentage, le plus élevé sera le mieux, plaçant Johnny Cage à la première place. Les algorithmes qui étaient des ordres de grandeur plus lents que les autres ont été exécutés sur un sous-ensemble de tests et extrapolés dans le temps. Le propre de C qsortest inclus pour comparaison (celui de Johnny est plus rapide!). Je ferai une comparaison finale à la fermeture.

entrez la description de l'image ici


3
Pouvez-vous fournir la référence? Différentes fonctions de tri fonctionnent différemment selon la nature des données. Par exemple, le tri à bulles est plus rapide que le tri rapide stdlib pour les petits tableaux. Nous aimerions peut-être optimiser votre référence.
Claudiu

@Claudiu J'ai vu une fois une belle version courte de quicksort, qui fonctionnait aussi bien que n'importe quelle autre sur des données où chaque élément était différent. Mais si certains éléments étaient les mêmes, il fonctionnait à un rythme d'escargot absolu. Je ne parle pas du problème connu de mauvais choix de pivot dans les tableaux triés / partiellement triés. Mes données de test ont été mélangées de manière complètement aléatoire. Cette version particulière n'aimait tout simplement pas les doublons. Bizarre, mais vrai.
Level River St

3
Bienvenue chez PPCG! Bien que nous n'interdisions pas les défis spécifiques à une langue, nous encourageons fortement la formulation de questions d'une manière indépendante de la langue chaque fois que possible. Considérez-le pour votre prochaine question et amusez-vous avec celle-ci!
Jonathan Van Matre

1
@steveverrill: Je ne suis pas. Peu importe votre unité, car vous la modifiez de 0 à 1 de toute façon. Si min est 1 heure et max est 3 heures, quelque chose qui prend 1,5 heure sera 0,25, que min soit 60 minutes, max 180 minutes et 90 minutes
Claudiu

1
OP a seulement dit qu'il n'y avait pas d'assemblage en ligne - il n'a rien dit sur l'intrinsèque.
Paul R

Réponses:


6

150 caractères

Tri rapide.

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

Comprimé.

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

Menant la course!
Mau

3

150 caractères (sans espaces blancs)

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

Génial, première entrée!
Mau

N'hésitez pas à poster une réponse avec SSE et je la listerai dans le tableau de bord, bien que je m'intéresse aux solutions «portables» pour le défi.
Mau

if(*w<*v) { t=v[++l]; v[l]=*w; *w=t; }peut êtreif(*w<*v) t=v[++l], v[l]=*w, *w=t;
ASKASK

3

67 70 69 caractères

Pas rapide du tout, mais incroyablement petit. C'est un hybride entre un tri par sélection et un algorithme de tri par bulles, je suppose. Si vous essayez de lire ceci, vous devez savoir que ++i-v-nc'est la même chose que ++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:0enregistre un caractère.
ugoren

Eh bien, ++i-v-nc'est la même chose ++i != v+nque dans un conditionnel, bien sûr.
wchargin

@ugoren je pense que vous avez posté ce commentaire sur la mauvaise réponse
ASKASK

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

êtes-vous sûr que c'est ainsi que fonctionne la préséance?
ASKASK

2

123 caractères (+3 sauts de ligne)

Un tri Shell standard, compressé.

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: découvert qu'il est toujours 10 fois plus lent que quicksort. Autant ignorer cette entrée.


Votre choix de lacunes pourrait être meilleur. C'est probablement pourquoi cela est beaucoup plus lent que quicksort. en.wikipedia.org/wiki/Shellsort#Gap_sequences
FDinoff

J'ai été étonné de découvrir à quel point la séquence des écarts affecte la vitesse. Avec une bonne séquence, cela se rapproche de quicksort mais reste plus lent dans mon expérience.
Florian F

Ne soyez pas trop dur avec vous-même. Tu es à la troisième place.
Kevin

2

395 caractères

Tri par fusion.

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

Formaté.

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 caractères

Radix trie-t-il 8 bits à la fois. Utilise un bithack fantaisie pour obtenir des flottants négatifs pour trier correctement (volé à http://stereopsis.com/radix.html ). Ce n'est pas si compact, mais il est vraiment rapide (~ 8 fois plus rapide que l'entrée préliminaire la plus rapide). J'espère que la taille du code sera plus rapide ...

#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 caractères

Radixsort sur place

Mise à jour: bascule sur le tri par insertion pour les tailles de tableau plus petites (augmente les performances de référence d'un facteur 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);}

Formaté.

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

Agréable! Essayez de signaler la réponse d'origine.
Mau

@Mau: Merci et fera l'affaire. Je voulais mentionner une erreur dans le code d'analyse comparative. La distribution à void*en qsort(ligne 88) est secouer le pointeur arithmétique.
MojoJojoBojoHojo

1

121 114 111 caractères

Juste un saut de bulles rapide et sale, avec récursivité. Probablement pas très efficace.

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

Ou, la version longue

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

En passant , j'ai trouvé un algorithme vraiment intéressant ici: rosettacode.org/wiki/Sorting_algorithms/Pancake_sort#C Mais je ne peux pas le compresser suffisamment pour battre 114 :)
CompuChip

votre programme semble ne pas réussir dans certains cas et écrire hors limites dans d'autres cas.
Mau

@Mau Je l'ai testé manuellement sur certaines entrées et semblait fonctionner correctement, mais en raison du manque de temps, je ne l'ai pas testé très attentivement, donc je suis sûr qu'il y a un mauvais comportement quelque part. Pourriez-vous publier un cas de test où vous avez rencontré des problèmes, s'il vous plaît?
CompuChip

programme de test disponible ci-dessus :)
Mau

Hmm j'ai essayé de l'exécuter, j'obtiens des erreurs `munmap_chunk (): pointeur invalide` dans la partie nettoyage, mais rien sur l'échec du test. Cependant, vous avez raison, il y a une erreur au coup par coup et je semble avoir des problèmes de séquencement (la liste d'instructions séparées par des virgules ne fait pas ce que j'attends). Je vais essayer de le réparer.
CompuChip

1

221 193 172 caractères

Heapsort - Pas le plus petit, mais en place et garantit un comportement 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);
     }
}

Comprimé.

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

Vous pouvez enregistrer certains caractères en déduisant les espaces. Et peut-être aussi la signature de fonction obligatoire, mais comme il y a des entrées qui ont compté, j'ai demandé à l'intervieweur de clarifier s'il devait être compté.
Jonathan Van Matre

@ user19425: Si vous exécutez le programme de test avec TEST_COUNT= 3000, il semble échouer au moins un test.
Mau

1

154 166 caractères

OK, voici un tri rapide plus long mais plus rapide.

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

Voici une correction pour survivre aux entrées triées. Et formaté car l'espace blanc ne compte pas.

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

Cette version semble écrire hors limites dans certains cas, sans se terminer dans d'autres.
Mau

PS: OK, c'est très lent sur un ensemble trié. Mais l'énoncé du problème dit que l'entrée est aléatoire.
Florian F

Les valeurs sont aléatoires. Je n'ai jamais dit dans quel ordre ils seraient :-) Mais oui, il y a des morceaux couvrant environ 10% de toutes les valeurs triées par ordre croissant et 10% par ordre décroissant.
Mau

1
C'est suffisant. Et un sort () devrait fonctionner sur une entrée triée. Je mettrai à jour ma soumission, alors ...
Florian F

1

150 caractères

Shellsort (avec écart 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;}
}

Formaté.

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 (golfé)

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

Explication: un tableau vide est utilisé pour stocker chaque nombre minimum successif. Un tableau int est un masque avec 0 indiquant que le nombre n'a pas encore été copié. Après avoir obtenu la valeur minimale, un masque = 1 ignore les numéros déjà utilisés. Ensuite, le tableau est recopié dans l'original.

J'ai changé le code pour éliminer l'utilisation des fonctions de bibliothèque.


0

144

J'ai sans vergogne pris le code de Johnny, ajouté une petite optimisation et compressé le code d'une manière très sale. Il devrait être plus court et plus rapide.

Notez que selon votre compilateur, sort (q, v + n- ++ q) doit être remplacé par 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;
    }
}

Eh bien, en fait, j'ai commencé à former mon code et à l'optimiser, mais il semble que Johnny ait déjà fait les bons choix. Je me suis donc retrouvé avec quasi son code. Je n'ai pas pensé au truc goto, mais je pourrais m'en passer.


0

228 caractères

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;
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.