2010-06-15 11 views
7

Pour des raisons je ne suis pas du tout d'accord avec mais "Les Pouvoirs (de l'Anti-Utilisabilité)" continuent à décréter malgré mes objections, j'ai une routine de tri qui basique strcmp() compare à trier par son nom . Cela fonctionne très bien. il est difficile de se tromper. Cependant, à la onzième heure, il a été décidé que les entrées commençant par un nombre devraient être APRES les entrées qui commencent par une lettre, contrairement à l'ordre ASCII. Ils citent la norme EBCDIC qui comporte des chiffres à la suite de lettres, de sorte que l'hypothèse antérieure n'est pas une vérité universelle, et je n'ai aucun pouvoir pour gagner cet argument ... mais je m'égare.strcmp() mais avec 0-9 AFTER A-Z? (C/C++)

C'est là que réside mon problème. J'ai remplacé toutes les références appropriées à strcmp avec un nouvel appel de fonction nonstd_strcmp, et maintenant j'ai besoin d'implémenter les modifications pour accomplir le changement de tri. Je l'ai utilisé une source FreeBSD comme ma base: http://freebsd.active-venture.com/FreeBSD-srctree/newsrc/libkern/strncmp.c.html

if (n == 0) 
    return (0); 
do { 
    if (*s1 != *s2++) 
    return (*(const unsigned char *)s1 - 
    *(const unsigned char *)(s2 - 1)); 
    if (*s1++ == 0) 
    break; 
} while (--n != 0); 
return (0); 

Je suppose que je pourrais avoir besoin de prendre un certain temps loin de penser vraiment sur la façon dont cela devrait être fait, mais je suis sûr que je ne suis pas le seul celui qui a connu la mort cérébrale des changements de spécification juste avant la libération.

+5

La quantité de haine emballée dans cette question m'amuse –

+0

certainement rien à voir avec C++ –

+0

Quelle est votre question ? – sbi

Répondre

4

Dans ce cas particulier avec seulement majuscules (comme mentionné par l'OP dans les commentaires) et les chiffres 0-9, vous pouvez également omettre la table de commande et multiplier les deux caractères différents par 4 et comparer les résultats modulo 256. La plage des chiffres ASCII (48 à 57) ne dépassera pas 8 bits (57 × 4 = 228), mais la plage de lettres majuscules (65 à 90) sera (65 × 4 = 260). Lorsque l'on compare les valeurs multipliées modulo 256, la valeur de chaque lettre sera inférieure à celle d'un chiffre: 90 × 4% 256 = 104 < 192 = 48 × 4

Le code pourrait ressembler à:

int my_strcmp (const char *s1, const char *s2) { 
    for (; *s1 == *s2 && *s1; ++s1, ++s2); 
    return (((*(const unsigned char *)s1) * 4) & 0xFF) - \ 
      (((*(const unsigned char *)s2) * 4) & 0xFF); 
} 

Bien sûr, la solution de table de commande est beaucoup plus polyvalente car elle permet de définir un ordre de tri pour chaque caractère - cette solution n'est sensée que pour ce cas particulier avec majuscules lettres/chiffres. (Par exemple, sur les plates-formes de microcontrôleurs, économiser même la petite quantité de mémoire utilisée par la table peut être un réel avantage.)

+0

Solution incroyablement intelligente Arkku! C'est, en fait, sur un système embarqué (bien que nous ne sommes pas étroitement contraints par la mémoire). J'ai utilisé votre implémentation et il semble réussir les tests initiaux. J'ai également ajouté un équivalent strncmp où les seuls changements sont pour (; * s1 == * s2 && * s1 && n; ++ s1, ++ s2, --n); if (! N) renvoie n; –

5

Vous pouvez utiliser une table de consultation pour traduire ASCII à EBCDIC lorsque l'on compare les caractères ;-)

+0

Son contenu est ASCII et non EBCDIC, mais une table de consultation sera généralement plus rapide et sera plus simple à modifier quand il décidera de réordonner d'autres caractères. +1 – EvilTeach

+0

+1: alors que la conversion en EBCDIC en tant que telle n'a probablement aucun sens, une table de recherche est presque certainement la bonne solution. –

+0

Il a dit que EBCDIC a les numéros après les lettres. Mais n'importe quelle table de recherche qui met les nombres après les lettres fonctionnera. – progrmr

16

Ce que vous devez faire est de créer une table de commande pour chaque caractère. C'est aussi la façon la plus simple de faire des comparaisons insensibles à la casse. Sachez que les caractères peuvent être signés. Dans ce cas, l'index de votre table peut devenir négatif. Ce code est Signed seulement: caractères

int raw_order_table[256]; 
int * order_table = raw_order_table + 128; 
for (int i = -128; i < 128; ++i) 
    order_table[i] = (i >= '0' && i <= '9') ? i + 256 : toupper(i); 
+0

Bon ... c'est logique. C'est l'étincelle dont j'avais besoin pour relancer mon cerveau! Merci! –

+0

Utiliser un int signifie que la table est inutilement grande. Notez que cet algorithme n'est nécessaire que lorsque '' 9 '<' A'', et les entrées sont uniquement [0-9A-Z]. Par conséquent, vous pouvez stocker '(i> = '0' && i <= '9')? 26 + i: i - 'A'', et la table n'a besoin d'allouer de la mémoire que pour les indices' 0 'à' Z '. – MSalters

+0

@MSalters, oui un 'int' est plus grand que nécessaire; un 'short' suffirait, ou même un' char' si l'on est prudent avec les valeurs. Puisque 'int' est censé être la taille la plus naturelle, c'est ce que je suis allé avec. Même si ce projet restreint l'entrée à [0-9A-Z], je ne le coderais pas de cette façon car je voudrais qu'il soit réutilisable pour un autre projet sans ces restrictions. –

8

Si vos pouvoirs en être sont comme tous les autres puissances qui-être que j'ai rencontré, vous pouvez le faire une option (même si il est caché):

Ordre de tri:

o Nombres après lettres

o lettres après Nombres

ou pire encore, ils pourraient comprendre qu'ils veulent que les nombres soient triés numériquement (par ex. "A123" vient après "A15"), alors il peut être

o Nombres après Lettres

o Lettres après Nombres

o Nombres intelligents après les lettres

o Lettres après Numéros intelligents

Cela permet de diagnostiquer le vrai problème, pas le symptôme. Je parie qu'il y a une petite chance qu'ils puissent changer d'avis à la 11e heure et à la 59e minute.

+1

Les numéros intelligents sont appelés «tri naturel». http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html –

+2

+1 pour le codage antipatoire. –

+0

Jeu de mots non intentionnel Je suppose - vouliez-vous dire anticipation ou antipathie? – MSalters

2

Voici ce qui devrait être une très bonne implémentation de la comparaison de chaînes similaire à celle décrite par d'autres publications.

static const unsigned char char_remap_table[256] = /* values */ 

#define char_remap(c) (char_remap_table[(unsigned char) c]) 

int nonstd_strcmp(const char * restrict A, const char * restrict B) { 
    while (1) { 
      char a = *A++; 
      char b = *B++; 
      int x = char_remap(a) - char_remap(b); 
      if (x) { 
       return x; 
      } 
      /* Still using null termination, so test that from the original char, 
      * but if \0 maps to \0 or you want to use a different end of string 
      * then you could use the remapped version, which would probably work 
      * a little better b/c the compiler wouldn't have to keep the original 
      * var a around. */ 
      if (!a) { /* You already know b == a here, so only one test is needed */ 
       return x; /* x is already 0 and returning it allows the compiler to 
          * store it in the register that it would store function 
          * return values in without doing any extra moves. */ 
      } 
    } 
} 

Au-dessus et au-delà que vous pouvez généraliser la fonction de prendre le char_remap_table comme paramètre qui vous permettra d'utiliser facilement différentes correspondances plus tard si vous avez besoin de.

int nonstd_strcmp(const char * restrict a, const char * restrict b, const char * restrict map); 
3

Tout en accord général avec les réponses ci-dessus, je pense qu'il est stupide de faire des recherches pour chaque itération de la boucle, à moins que vous pensez que la plupart des comparaisons auront des premiers caractères, quand vous pourriez plutôt faire

char c1, c2; 
while((c1 = *(s1++)) == (c2 = *(s2++)) && c1 != '\0'); 
return order_table[c1] - order_table[c2]; 

aussi, je recommande la construction du order_table avec un initialiseur statique, ce qui améliorera la vitesse (pas besoin de générer à chaque fois - ou jamais) et peut-être aussi la lisibilité

+0

Il s'agit d'une bonne optimisation intelligente, sauf que vous voulez vous assurer que l'incrément du pointeur arrive au bon moment - l'exemple donné ne comparera pas le premier caractère des chaînes (et errera à la fin d'une chaîne si une ou les deux chaînes sont vides). –

+1

Cela ne fonctionne également pas correctement dans tous les cas si deux caractères différents peuvent avoir la même valeur dans 'order_table' (par exemple, tri insensible à la casse). – Arkku

+0

@Michael wow bonne prise, ne voit pas du tout (stupide moi). De toute façon je l'ai édité et ça devrait être bon maintenant. @Arkku: La recherche sensible à la casse spécifiée par OP (ou plutôt insensible à la casse n'est pas nécessaire) donc je ne vois pas pourquoi order_table aurait besoin que plusieurs caractères partagent la même valeur de commande –