OVH Cloud OVH Cloud

fonction split

9 réponses
Avatar
gabriel
bonsoir,

dans le cadre de mon apprentissage, j'essaie d'écrire la fonction
split(string, separateur);

je procède par étapes mais je galère pas mal...
La première étape qui me pose pb est de récupérer ce qu'il y avant le
séparateur. (je sais, je suis pas rendu pour faire mon split :)... )


char* string = "la=chaine=a=separer";

// une copie de travail
char* result = malloc(strlen(result));
strcpy(result, string);
void* oldPos = &result;

// init de la recherche
result = strchr(string, '=');


while (result != NULL && result != '\0')
{
// Affiche ce qui se trouve entre le précédent pointeur et le pointeur
renvoyé cette fois-ci
printf("%i\n", result-oldPos);

result = strchr(result, '='); // Cherche

// Stocke l'adresse du nouveau point de départ pour faire nos calculs
oldPos = &result;
}

et là mon compilo gcc me dit que j'ai pas le droit de faire la
différence entre mes 2 pointeurs :(

un petit coup de pouce svp ?

Merci bcp pour votre patience

9 réponses

Avatar
lhabert
gabriel :

dans le cadre de mon apprentissage, j'essaie d'écrire la fonction
split(string, separateur);


C'est-à-dire? Qu'est-ce qu'elle doit faire au juste?

char* string = "la=chaine=a=separer";

// une copie de travail
char* result = malloc(strlen(result));
tu veux dire « string », j'imagine


void* oldPos = &result;


Tu ne veux pas dire « char * oldPos=result; », plutôt?

// init de la recherche
result = strchr(string, '=');


while (result != NULL && result != '')
*result


{
// Affiche ce qui se trouve entre le précédent pointeur et le pointeur
renvoyé cette fois-ci
printf("%in", result-oldPos);


Tu soustrais un pointeur à l'autre, et tu demandes d'afficher ça comme un
entier, ça n'a rien à voir avec afficher le texte entre les deux. Le plus
simple serait :

*result=0; // Puisque tu as fais une copie de travail, autant en profiter.
puts(oldPos);
result++;

oldPos = &result;
pas de « & »


et là mon compilo gcc me dit que j'ai pas le droit de faire la
différence entre mes 2 pointeurs :(


La différence entre deux pointeurs, ça ne marche qu'entre des pointeurs vers
des objets de même type, or tu avais un void * et un char *. Ça n'aurait
aucun sens de faire la différence de pointeurs vers des objets de types
différents, car c'est fait pour donner la différence d'indices dans un
tableau : la différence numérique entre les pointeurs est divisée par la
taille des objets pointés.

Avatar
Pierre Maurette
bonsoir,

dans le cadre de mon apprentissage, j'essaie d'écrire la fonction
split(string, separateur);


Il faut prendre le temps de définir clairement un "scope of work". On
pourrait penser à renvoyer d'une façon ou d'une autre un tableau de
chaînes correspondant aux morceaux de la chaîne d'origine. Mais c'est
assez compliqué pour ce stade de l'apprentissage, puisqu'on aurait à
manipuler des tableaux de taille non connue à la compilation, obligeant
qui plus est à faire deux passes.
On va donc se contenter d'afficher.
Le séparateur est un caractère. Les sous-chaînes devront être affichées
une par ligne, et des lignes vides ne doivent pas apparaître même si le
sépaateur est multi-pliqué, et s'il est en tête ou en queue de chaîne.
On devra au moins tester pour les chaînes:
"la=chaine=a=separer"
"la chaine a separer"
"=la=chaine=a=separer="
"la==chaine=a===separer"
"==la=chaine=a=separer=="

je procède par étapes mais je galère pas mal...
La première étape qui me pose pb est de récupérer ce qu'il y avant le
séparateur. (je sais, je suis pas rendu pour faire mon split :)... )


char* string = "la=chaine=a=separer";

// une copie de travail
char* result = malloc(strlen(result));
strcpy(result, string);


strlen() renvoie le nombre de caractères avant le '' final. Le
malloc() est donc trop petit d'une unité.

Je ne comprends pas trop le reste. Votre difféence de pointeurs a déjà
été commentée dans un autre message. Vous pourriez potasser printf() et
sa chaîne de format, c'est un bon investissement, on retrouve ça dans
bien d'autres langages.


Je tiens pour acquis que les paramètres des fonctions sont honnêtes, en
particulier que les const char* pointent vers des chaîne de caractères
bien formées. En revanche, un malloc() peut échouer.

Première approche:
La chaine S égale à "bon=jour". Elle se présente comme {'b', 'o', 'n',
'=', 'j', 'o', 'u', 'r', ''}. Si on remplace brutalement les '=' par
des '', on obtient {'b', 'o', 'n', '', 'j', 'o', 'u', 'r', ''}.
Si on printf("%sn", S), on affiche "bon". Et si on printf("%sn", S +
4), on affiche "jour". On peut le tester, mais ça ne se comportera pas
comme souhaité avec toutes les chaînes de test. Il faudrait ajouter de
la tuyauterie (j'ajoute un strdup() dont je ne dispose pas, pas
standard):

char* strdup(const char* s)
{
char* pc;
return (pc = malloc(strlen(s) + 1)) == NULL ? NULL : strcpy(pc, s);
}

void split1(const char* s, const char separChar)
{
char* curseur = strdup(s);
if(curseur == NULL)
{
puts("<split1 alloc error>");
}
else
{
unsigned int nbChaines = 1;
char* copieCurseur = curseur;
int i;

while(*curseur != '')
{
if( *curseur == separChar)
{
*curseur = '';
nbChaines++;
}
curseur++;
}

for(i = 0; i < nbChaines; i++)
{
copieCurseur += printf("%sn", copieCurseur);
}
free(curseur);
}
}

Pas grand-chose à commenter, si ce n'est qu'on exploite la valeur de
retour du printf() pour se promener dans la chaîne.


Deuxième approche:
C'est dommage de dupliquer la chaîne. On peut simplement envoyer les
caractères un par un à l'écran, la chaîne n'est alors pas modifiée:

void split3(const char* s, const char separChar)
{
char dummy = *s;
do
{
dummy = *s++;
putchar(dummy == separChar ? 'n' : dummy == '' ? 'n' :
dummy);
}
while(dummy != '');
}

Même problème que le précédent, on va rajouter la tuyauterie pour
satisfaire les tests:


void split4(const char* s, const char separChar)
{
char dummy = *s;
int ToWrite = 0, IsBegining = 1;
do
{
dummy = *s++;
if(dummy == separChar)
{
if(!IsBegining)
ToWrite = 1;
}
else
{
if(ToWrite && dummy != '')
{
putchar('n');
ToWrite = 0;
}
IsBegining = 0;
putchar(dummy == '' ? 'n' : dummy);
}
}
while(dummy != '');
}

Ça ne se lit peut-être pas tout à fait intuitivement, mais ça semble
fonctionner.


Troisième approche:
On accepte d'utiliser les fonctions de la bibliothèque standard, de
laquelle strtok() semble faite exprès. Ça nous donne:


#define PUTS(S) (s = (S), s == NULL ? EOF : puts(s))

void split2(const char* s, const char* separStr)
{
char* chaine = strdup(s);
if(chaine == NULL)
{
puts("<split2 alloc error>");
}
else
{
PUTS(strtok(chaine, separStr));
while(PUTS(strtok(NULL, separStr)) != EOF){/* nada ! */}
free(chaine);
}
}

La macro ne sert qu'à faire renvoyer EOF, qui est le retour d'erreur de
puts(), si on envoie NULL.
Remarquez que le séparateur n'est pas const char mais const char*.
Cette chaîne n'est pas un pattern, mais une liste de caractères, qui
seront mises en OU pour la recherche. Voir le man de strtok().

--
Pierre Maurette

Avatar
gabriel
La macro ne sert qu'à faire renvoyer EOF, qui est le retour d'erreur de
puts(), si on envoie NULL.
Remarquez que le séparateur n'est pas const char mais const char*. Cette
chaîne n'est pas un pattern, mais une liste de caractères, qui seront
mises en OU pour la recherche. Voir le man de strtok().
J'avais commencé à réfléchir à strtok effectivement mais sans bcp de succès.


Une autre piste mais je ne sais pas ce qu'elle vaut :

je compte le nbre d'occurence de mon separateurs et j'alloue un tableau
de pointeurs.
je parse ma chaine caractère par caractère
si c'est un sep, alors je recule jusqu'au précedent et prend le bout de
chaine entre les 2, je cree un pointeur que je stocke dans mon tableau
et j'incrémente mon pointeur de tableau de pointeur.

Autre piste: une liste chainée des bouts de chaine que soit je renvoie,
soit je transforme en tableau

Votre avis ?

En attendant, je vais recreuser strtok.

Dans tous les cas, merci pour votre aide !

Avatar
Harpo
gabriel wrote:


je compte le nbre d'occurence de mon separateurs et j'alloue un
tableau de pointeurs.
je parse ma chaine caractère par caractère
si c'est un sep, alors je recule jusqu'au précedent


Il vaut mieux l'avoir stocké précédemment.

et prend le bout
de chaine entre les 2, je cree un pointeur que je stocke dans mon
tableau et j'incrémente mon pointeur de tableau de pointeur.


Si j'ai bien compris, ça donnera un tableau de pointeurs sur le début de
chaque mot, ça peut dépendre de ce que voulez faire mais ça n'a pas
toujours d'utilité, pace que à la fois le mot n'est pas terminé par un
0 et vous n'avez pas sa longueur (généralement on peut avoir plusieurs
séparateurs qui se suivent et soustraire du pointeur suivant ne peut
donner la longueur du mot).
Une possibilité est de construire de façon similaire un tableau dont
chaque poste contient l'adresse du mot et sa longueur (amha ça définit
une chaîne de manière plus propre qu'un pointeur sur des caractères
terminés par un 0).

Autre piste: une liste chainée des bouts de chaine que soit je
renvoie, soit je transforme en tableau


Un peu gourmand si vous devez allouer chaque élément de la liste.

Votre avis ?

En attendant, je vais recreuser strtok.


Son défaut est de modifier la chaîne en place, cela peut ne pas être un
problème mais ça peut obliger à dupliquer la chaîne ce qui oblige à la
scanner une fois d'abord pour avoir sa longueur.

Regardez aussi strspn et strcspn, utilisés conjointement ils peuvent
peut-être vous servir.

A ce stade, il serait peut-être bon de définir plus exatement la
fonction, ses arguments et ce qu'elle retourne.

Généalement ce genre de fonction est utilisée dans un scanner, il y a
plusieurs approches, parmi elles l'appeler pour délivrer le prochain
token, soit c'est elle qui à chaque token appelle une fonction qui agit
comme un automate et faisant du token ce qu'il convient en fonction de
l'état de l'automate

--
http://patrick.davalan.free.fr/

Avatar
Charlie Gordon
"gabriel" wrote in message
news:44ac2541$0$615$

La macro ne sert qu'à faire renvoyer EOF, qui est le retour d'erreur de
puts(), si on envoie NULL.
Remarquez que le séparateur n'est pas const char mais const char*. Cette
chaîne n'est pas un pattern, mais une liste de caractères, qui seront
mises en OU pour la recherche. Voir le man de strtok().
J'avais commencé à réfléchir à strtok effectivement mais sans bcp de succès.



Effectivement, c'est une mauvaise piste.

Une autre piste mais je ne sais pas ce qu'elle vaut :

je compte le nbre d'occurence de mon separateurs et j'alloue un tableau
de pointeurs.


Bonne approche, il faut juste une convention pour que la taille du tableau
puisse être transmise à l'appelant.
On peut par exemple allouer le tableau de 1 element de plus que le nombre de
sous-chaines, et stocker NULL en dernière position : c'est le même principe que
pour les chaines de caractères.

je parse ma chaine caractère par caractère
si c'est un sep, alors je recule jusqu'au précedent et prend le bout de
chaine entre les 2, je cree un pointeur que je stocke dans mon tableau
et j'incrémente mon pointeur de tableau de pointeur.


Il faut mieux garder sous le coude le pointeur sur le debut de la sous-chaine
que de "reculer" dans le tableau : c'est plus efficace, moins dangereux, plus
lisible...

D'autre part on ne "crée" pas un pointeur : on peut allouer un bloc de memoire
pour recopier la sous-chaine et stocker un pointeur sur ce bloc dans le tableau.
Une autre possibilité serait de modifier la chaine à "splitter" et de stocker
des pointeurs sur les sous-chaines dans le tableau alloué. La premiere approche
est plus régulière. Cependant, dans un vrai programme, il faudra prévoir de
libérer le tableau ainsi alloué quand il ne sert plus.

Autre piste: une liste chainée des bouts de chaine que soit je renvoie,
soit je transforme en tableau


C'est plus lispien, allouer des bouts de liste en C est moins idiomatique... tu
peux quand meme utiliser cette approche pour te familiariser avec les "flexible
array members du c99" : il s'agit du dernier element d'une structure qui peut
être défini comme un tableau de taille non specifiée, et alloué de la bonne
taille pour recevoir les éléments adéquats pour chaque instance. Voici un
exemple.

typdef struct string_chain string_chain;
struct string_chain {
string_chain *next;
char str[];
};

string_chain *new_string_chain(const char *str) {
size_t len = strlen(str);
string_chain *scp = malloc(sizeof(*scp) + len + 1);
if (scp) {
scp->next = NULL;
memcpy(scp->str, str, len + 1);
}
return scp;
}

Tu peux ensuite chainer une liste de telles structures par le champ next dans la
fonction d'analyse de chaine qui repère les séparateurs en une seule passe. Je
t'en laisse l'écriture à titre d'exercice, mais si tu postes le code, il sera
revu et commenté sur cette liste.

Si ton compilateur ne supporte pas les "flexible array members", il suffit de
déclarer le tableau str de la structure avec une taille de 1, voire de 0
(extension gcc).

Votre avis ?

En attendant, je vais recreuser strtok.


Uniquement pour apprendre quelles fonctions il faut éviter d'utiliser : strtok a
deux défauts majeurs, d'un part elle modifie le tableau passé en argument, ce
qui encourage de mauvais choix algorithmiques, d'autre part, et plus grave, elle
garde un pointeur statique sur le tableau pour continuer l'analyse à l'appel
suivant (quand on passe NULL au lieu du tableau). Cela rend strtok inutilisable
dans un programme avec plusieurs threads d'éxecution, et cause des bugs
difficiles à trouver dans des programmes simples ou strtok serait utilisée dans
les fonctions imbriquées. On dit que strtok() n'est pas réentrante. Il existe
une version modifiée, strtok_r(), qui corrige le problème de réentrance.

Dans la même veine, il faut connaitre mais NE PAS utiliser gets() ni strncpy().
On se méfiera aussi de realloc(), scanf(), feof()...
On préfèrera snprintf() à sprintf()...


Dans tous les cas, merci pour votre aide !


My pleasure ;-)

Chqrlie.


Avatar
Denis Leger
Le Thu, 6 Jul 2006 10:00:33 +0200

Dans la même veine, il faut connaitre mais NE PAS utiliser gets() ni
strncpy().


Pourquoi ne pas utiliser strncpy() ?


--
Denis Léger

Avatar
Denis Leger
Le Thu, 6 Jul 2006 23:05:50 +0200

"Denis Leger" wrote in message
news:
Le Thu, 6 Jul 2006 10:00:33 +0200

Dans la même veine, il faut connaitre mais NE PAS utiliser gets()
ni > strncpy().


Pourquoi ne pas utiliser strncpy() ?


ce sujet a été maintes fois débattu sur ce forum, voir l'article de
Todd Miller :

http://www.courtesan.com/todd/papers/strlcpy.html

Tu peux faire le test : lis en détail le manuel pour strncpy, tu sera
surpris que cette fonction ne fait pas ce que 99% des programmeurs
intuitent quand ils l'utilisent ou en voient une utilisation dans du
code. En conséquence, l'énorme majorité des utilisations de cette
fonction contiennent des bugs plus ou moins graves.


Je crois bien que je faisait partie des 99%...

Ceci dit, j'ajoutais toujours un '' en fin de chaîne après un
strncpy(), mais je n'avais pas fait attention au fait que strncpy()
ajoute de lui-même plein de '' à la fin de la chaîne.

Je crois que je vais me mettre à utiliser strlcpy() maintenant.



--
Denis Léger
MP Maths -- Brest



Avatar
espie
In article <44aee8d7$0$9932$,
Charlie Gordon wrote:
Même en prenant ces précautions, c'est une mauvaise idée : d'autres que toi
liront ce code et penseront naivement que cette fonction fait ce qu'ils
imaginent. Il est tellement rare que strncpy soit la bonne solution que même
dans ces cas improbables, il faudrait mieux expliquer le besoin et avoir recours
à une périphrase avec memset et memcpy.


le seul cas ou presque ou strncpy est justifie, c'est pour remplir des
entrees d'utmp et autres machins dans le meme genre...

et encore, ca donne du code pas toujours tres clair malgre tout.

Avatar
Charlie Gordon
"Marc Espie" wrote in message
news:e8rsk6$1niq$
In article <44aee8d7$0$9932$,
Charlie Gordon wrote:
Même en prenant ces précautions, c'est une mauvaise idée : d'autres que toi
liront ce code et penseront naivement que cette fonction fait ce qu'ils
imaginent. Il est tellement rare que strncpy soit la bonne solution que même
dans ces cas improbables, il faudrait mieux expliquer le besoin et avoir
recours


à une périphrase avec memset et memcpy.


le seul cas ou presque ou strncpy est justifie, c'est pour remplir des
entrees d'utmp et autres machins dans le meme genre...

et encore, ca donne du code pas toujours tres clair malgre tout.


Donc même pour ça, on évitera strncpy.

Chqrlie.