OVH Cloud OVH Cloud

Retour de la fonction bsearch

59 réponses
Avatar
candide
Bonjour,

Juste un avis perso : le retour de la fonction bsearch est peu exploitable.


Bien souvent, face à une liste croissante, on ne se préoccupe pas
essentiellement de savoir si un élément donné s'y trouve ou ne s'y
trouve pas, on cherche à savoir où il est placé dans la liste (et
éventuellement hors de la liste à gauche ou à droite). Or, quel que soit
l'algorithme utilisé par bsearch (recherche séquentielle ou
dichotomique), bsearch serait en mesure sans coût supplémentaire
excessif de donner cette position. Hélas bsearch se contente de donner
une réponse binaire. Et je trouve particulièrement agaçant d'avoir à me
recoder un truc aussi classique qu'une recherche dichotomique, pas vous ?

10 réponses

2 3 4 5 6
Avatar
Jean-Marc Bourguet
"Charlie Gordon" writes:

Primo, la recherche dichotomique dans un tableau en mémoire n'est pas une
plaie à coder, tout au plus un rapide échauffement des petits doigts pour
le petit déjeuner. Une implementation simple et de bon gout fait à peine
un vingtaine de lignes:

void *bsearch(const void *key, const void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *))
{
size_t a = 0;
size_t b = nmemb;

while (a < b) {
size_t m = (a + b) / 2;
const void *p = (const char *)base + m * size;
int cmp = (*compar)(key, p);
if (cmp < 0)
b = m;
else
if (cmp > 0)
a = m + 1;
else
return (void *)p;
}
return NULL;
}



Il ne faut pas coder avant le petit dejeuner :-) La preuve, tu nous a fait
une belle boucle infinie. (Et je ne vois pas l'utilite du +1 dans a = m
+1).

On peut dans ce cas améliorer légèrement l'efficacité de la recherche et
garantir de trouver le premier match en cas de doublons en remplacant les
deux tests sur cmp par un seul (laissé au lecteur à titre d'exercice ;-).



Rien n'empeche de faire cela ici aussi, il suffit de tester l'egalite avant
de retourner une valeur. C'est un gain meme si la valeur se trouve
toujours dans le tableau.

Troisièmement, la recherche dichotomique n'est pas le meilleur algorithme
pour le probleme posé (boggle solver). Il sera bien plus efficace de
stocker tous les mots possibles dans un tableau associatif (hash table)
convenablement dimensionné et avec une API de recherche efficace.



Il a l'air d'avoir besoin de prefixes (j'ai toujours pas ete voir le jeu),
auquel cas une hash table n'est pas particulierement adaptee.

A+

--
Jean-Marc
FAQ de fclc: http://www.isty-info.uvsq.fr/~rumeau/fclc
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
candide
Charlie Gordon a écrit :
P4) il faut se recoder une recherche dichotomique, quelle plaie !



Primo, la recherche dichotomique dans un tableau en mémoire n'est pas une
plaie à coder, tout au plus un rapide échauffement des petits doigts pour le
petit déjeuner. Une implementation simple et de bon gout fait à peine un
vingtaine de lignes:





C'est une plaie tout simplement parce que c'est une plaie de réinventer
la roue chaque matin au petit déjeuner ... Quant à la longueur, ce n'est
pas un argument.



void *bsearch(const void *key, const void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *))
{
size_t a = 0;
size_t b = nmemb;

while (a < b) {
size_t m = (a + b) / 2;
const void *p = (const char *)base + m * size;
int cmp = (*compar)(key, p);
if (cmp < 0)
b = m;
else
if (cmp > 0)
a = m + 1;
else
return (void *)p;
}
return NULL;
}





Et ce genre de code, il faut pas mal d'années de pratique pour arriver à
le pondre en 20 minutes et au saut du lit.


Secondo, la sémantique qui t'intéresse est triviale à obtenir en remplacant




C'est ce que je dis depuis le début mais je n'ai pas envie de recoder
bsearch pour lui rajouter une trivialité.


Troisièmement, la recherche dichotomique n'est pas le meilleur algorithme
pour le probleme posé (boggle solver). Il sera bien plus efficace de stocker
tous les mots possibles dans un tableau associatif (hash table)
convenablement dimensionné et avec une API de recherche efficace.



Oui, tu veux dire la structure (le /trie/) dont Jean-Marc a parlé
ci-dessus. Décidément les informaticiens vous êtes faits sur le même
moule, je parlais de ce problème avec un collègue informaticien et il
m'a répondu exactement la même chose que vous.


À problème simple j'aime bien essayer d'apporter une solution simple. Il
existe un algorithme de recherche de mot naïf et facile à implémenter
qui simule complètement la structure de données évoquées par JMB (sauf
que le parcours séquentiel des successeurs d'un noeud est remplacé par
une recherche dichotomique).

Il est vrai néanmoins que cette structure de données est bien adaptée au
problème posée et qu'elle diminuera la taille des données à consulter et
que l'exécution sera sensiblement plus rapide. Mais le binary search est
aussi assez rapide surtout si tu l'aides en lui donnant les adresses des
lettres initiales (ce sont elles qui nécessitent le traitement le plus
long). Moi j'ai l'impression que les temps sont assez comparables.

Ce qui ne m'incite pas à choisir la structure de données c'est qu'il
faut s'en taper l'implémentation puis la routine de recherche dans le
/trie/, conceptuellement assez facile, techniquement moins facile. Mais
avec plus de pratique peut-être effectivement que je ferais comme vous.
Avatar
candide
Charlie Gordon a écrit :
J'ai peur que cette dernière remarque soit à prendre au premier degré...
Faut-il que les étudiants soient bien intoxiqués de langages fonctionnels
pour imaginer systématiquement des solutions recursives aux problèmes les
plus simples ?



Je ne connais aucun langage fonctionnel. La récursivité ici est assez
naturelle et le code est, je trouve, plus lisible. Et pour moi, la
lisibilité, ça compte.

Pourquoi pour qsort on implémente récursif et on ne le ferait pas pour
bsearch après tout ?
Avatar
espie
In article <gc2i75$d3d$,
Charlie Gordon wrote:
J'ai peur que cette dernière remarque soit à prendre au premier degré...
Faut-il que les étudiants soient bien intoxiqués de langages fonctionnels
pour imaginer systématiquement des solutions recursives aux problèmes les
plus simples ?



Parce que, dans ce cas precis, c'est de la recursion terminale, et que donc
tout compilateur decent saura faire de la tail-recursion optimization,
et donc que c'est strictement equivalent a la solution iterative ?
Avatar
candide
Jean-Marc Bourguet a écrit :

(...) tu nous a fait
une belle boucle infinie.



Exemple ?


(Et je ne vois pas l'utilite du +1 dans a = m
+1).



Quelque chose doit m'échapper car ça me parait clair pourtant : on ne va
pas tester le cas a=m puisque cmp !=0 donc on commence au premier qui
suit i.e. m+1.
Avatar
-ed-
On 1 oct, 12:39, candide wrote:
Bonjour,

Juste un avis perso : le retour de la fonction bsearch est peu exploitabl e.

Bien souvent, face à une liste croissante, on ne se préoccupe pas
essentiellement de savoir si un élément donné s'y trouve ou ne s'y
trouve pas, on cherche à savoir où il est placé dans la liste (et
éventuellement hors de la liste à gauche ou à droite). Or, quel que soit
l'algorithme utilisé par bsearch (recherche séquentielle ou
dichotomique), bsearch serait en mesure sans coût supplémentaire
excessif de donner cette position. Hélas bsearch  se contente de donn er
une réponse binaire. Et je trouve particulièrement agaçant d'avoir à me
recoder un truc aussi classique qu'une recherche dichotomique, pas vous ?



J'ai lu la suite et j'ai fini par comprendre ce que tu voulais. Non
bsearch() sait pas faire. Il sait juste dire si un élément d'une liste
triée est présent ou non. Pour faure ce que tu veux faire, il fait une
interface un peu différente (à définir) et recoder une fonction de
recherche génerique, ce qui n'est pas bien difficile.

Je rappelle que le C est un langage de base. Il fournit certains
outils tous faits qui couvrent des cas simples. Par contre il fourni
tout ce qui est nécessaire pour réaliser les cas compliqués.

On ne peut pas exiger d'un langage simple de bas niveau des fonctions
complexes de haut niveau. Si c'est ce que tu cherches, le C++ est fait
pour toi.
Avatar
Charlie Gordon
"Marc Espie" a écrit dans le message de news:
gc2rma$20ac$
In article <gc2i75$d3d$,
Charlie Gordon wrote:
J'ai peur que cette dernière remarque soit à prendre au premier degré...
Faut-il que les étudiants soient bien intoxiqués de langages fonctionnels
pour imaginer systématiquement des solutions recursives aux problèmes les
plus simples ?



Parce que, dans ce cas precis, c'est de la recursion terminale, et que
donc
tout compilateur decent saura faire de la tail-recursion optimization,
et donc que c'est strictement equivalent a la solution iterative ?



Cela ne m'a pas échappé : je l'avais même expliqué dans la partie du message
que tu as coupée.
Cependant je ne suis convaincu ni de l'élégance supérieure de la solution
récursive, ni de la décence supposée des compilateurs courants.

--
Chqrlie.
Avatar
Charlie Gordon
"candide" a écrit dans le message de news:
48e4df54$0$5901$
Charlie Gordon a écrit :
J'ai peur que cette dernière remarque soit à prendre au premier degré...
Faut-il que les étudiants soient bien intoxiqués de langages fonctionnels
pour imaginer systématiquement des solutions recursives aux problèmes les
plus simples ?



Je ne connais aucun langage fonctionnel. La récursivité ici est assez
naturelle et le code est, je trouve, plus lisible.



C'est un point de vue qui se défend.

Et pour moi, la lisibilité, ça compte.



Tout-à-fait d'accord ! La lisibilité est essentielle. Mais comme tu l'as
remarqué dans une autre réponse, ma solution est assez claire.

Pourquoi pour qsort on implémente récursif et on ne le ferait pas pour
bsearch après tout ?



Qsort est souvent implémenté avec une double récursion, qui n'est qu'en
partie simplifiee par certains compilateurs. Faire une implementation non
récursive n'est pas si facile. D'ailleurs il faut prendre des précautions
subtiles pour éviter que dans des cas pathologiques la récursion ne se fasse
trop violemment. En effet, avec un pivot mal choisi et une récursion
systematique, on peut se retrouver à recurser sur une profondeur de l'ordre
de la taille du tableau. La solution pour éviter cela est de ne recurser
que sur la moitie la moins grande et de boucler explicitement sur l'autre.
On n'évite pas nécessairement la complexité quadratique, mais on se garde
d'exploser la pile. Voilà un cas concret ou le compilateur ne peut pas
deviner quelle recursion terminale simplifier.

Je finirai sur une mise en garde : bsearch est considérablement plus simple
que qsort. Il est encore facile de faire des erreurs d'implementation quand
on code bsearch pour le petit dej, mais en ce qui concerne qsort, il est
difficile de ne pas en faire. Ecrire un qsort sur une feuille blanche, qui
fonctionne correctement du premier coup est une prouesse remarquable.

--
Chqrlie.
Avatar
Charlie Gordon
"Jean-Marc Bourguet" a écrit dans le message de news:

"Charlie Gordon" writes:

Primo, la recherche dichotomique dans un tableau en mémoire n'est pas une
plaie à coder, tout au plus un rapide échauffement des petits doigts pour
le petit déjeuner. Une implementation simple et de bon gout fait à peine
un vingtaine de lignes:

void *bsearch(const void *key, const void *base, size_t nmemb, size_t
size,
int (*compar)(const void *, const void *))
{
size_t a = 0;
size_t b = nmemb;

while (a < b) {
size_t m = (a + b) / 2;
const void *p = (const char *)base + m * size;
int cmp = (*compar)(key, p);
if (cmp < 0)
b = m;
else
if (cmp > 0)
a = m + 1;
else
return (void *)p;
}
return NULL;
}



Il ne faut pas coder avant le petit dejeuner :-) La preuve, tu nous a fait
une belle boucle infinie.



Ah bon ? Peux-tu être plus explicite ?

(Et je ne vois pas l'utilite du +1 dans a = m + 1).



Cela accélère la convergence : la clé est > au pivot donc l'element cherché
est forcément strictement au delà du pivot.

On peut dans ce cas améliorer légèrement l'efficacité de la recherche et
garantir de trouver le premier match en cas de doublons en remplacant les
deux tests sur cmp par un seul (laissé au lecteur à titre d'exercice ;-).



Rien n'empeche de faire cela ici aussi, il suffit de tester l'egalite
avant
de retourner une valeur. C'est un gain meme si la valeur se trouve
toujours dans le tableau.



Non, ce n'est pas loisible : dans le cas général on a plus de comparaisons
avec *key != *p, donc il est préférable de tester ces cas en premier.

Troisièmement, la recherche dichotomique n'est pas le meilleur algorithme
pour le probleme posé (boggle solver). Il sera bien plus efficace de
stocker tous les mots possibles dans un tableau associatif (hash table)
convenablement dimensionné et avec une API de recherche efficace.



Il a l'air d'avoir besoin de prefixes (j'ai toujours pas ete voir le jeu),
auquel cas une hash table n'est pas particulierement adaptee.



Effectivement, si l'algorithme repose sur une recherche de prefixes, le hash
est inadapté.
Le probleme de Boggle a peut-etre une complexité supérieure au Scrabble,
pour lequel un hash des combinaisons de lettres est a la base de programmes
qui jouent des parties entières optimales en duplicate en une fraction de
seconde.

--
Chqrlie.
Avatar
candide
Charlie Gordon a écrit :

difficile de ne pas en faire. Ecrire un qsort sur une feuille blanche, qui
fonctionne correctement du premier coup est une prouesse remarquable.




Certainement même si l'algo n'est pas très compliqué en soi. Je trouve
plus ennuyeux encore un tri fusion.
2 3 4 5 6