Partage
  • Partager sur Facebook
  • Partager sur Twitter

Exercices pour débutants en C

Au menu : zSommeChiffres (nombres, algo)

29 juillet 2009 à 21:13:03

Citation : bluestorm

on est d'accord, c'est bizarre


C'est une bête astuce. Il n'est pas rare de la croiser, c'est d'ailleurs GuilOooo qui me l'a conseillée ici et je ne pense pas que ça soit une mauvaise technique. S'il existe plus simple et standard, je suis preneur, bien sûr.

Citation : bluestorm

Il a besoin de la taille parce qu'il alloue un gros tableau (char t[fsize(file)], iirk !) de cette taille là pour y coller tout le fichier.


Quoi "iirk", on a le droit de faire ce genre de chose en C99, sinon qu'on me prouve le contraire. Et euh, oui j'y colle tout le fichier (avec une fonction assez simple et linéaire en la taille du fichier, parce qu'à priori, il n'y a rien dans la bibliothèque standard qui fasse ça).
En fait, j'utilise une copie du fichier en mémoire vive parce que c'est pratique au niveau de deux choses notamment :
- Je peux modifier le contenu comme ça m'arrange et je le fais (cf effet de bord louche "*(--i) = '\0'") ;
- Ça m'évite de passer par un buffer pour accumuler les caractères de l'état 1 (le deuxième) pour ensuite les comparer avec un système über-lourd à argv[1] (dans mon code, un simple strcmp (ou même avec strncmp, ça aurait été possible)).

Citation : 21

et vous nous inondez de termes un peu inutile...


Comment ça inutile ? Qu'il a-t-il d'inutile aux automates à états ? En quoi une solution plus naïve mais moins efficace serait plus utile ? Fais une petite recherche avant de rejeter une technique pourtant fondamentale en informatique (ici par exemple). Les automates à états constituent une approche intéressante et parfaitement efficace pour solutionner le problème.
En soi, ce problème est formulé de manière simple et on va même dire si tu veux qu'il est simple. Mais ça n'induit pas forcément le fait que les différentes manières de le résoudre le sont.

Sinon je ne sais pas si tu as remarqué, 21, mais la solution que tu proposes est grossièrement (je dis bien "grossièrement") la même que la mienne (on vérifie : qu'avant il n'y ait rien d'alphanumérique, que pendant, la chaîne corresponde au mot cherché, qu'après, il n'y ait rien d'alphanumérique), sauf que tu n'as pas parlé d'états.

Sinon, merci bluestorm pour tes remarques ici, je n'ai pas fait dans la dentelle et je vais effectivement affiner certaines choses.
  • Partager sur Facebook
  • Partager sur Twitter
29 juillet 2009 à 21:28:48

salut,

@shareman:

Citation : Shareman


En quoi une solution plus naïve mais moins efficace serait plus utile


Désolé, mais en quoi, faire plus compliqué et moins efficace serait plus utile...
C'est la deuxième fois, que tu proposes un code difficilement compréhensible, on est d'accord ? Tu devrais, je crois, lorsque tu postes ce genre de code, prendre le temps d'expliquer ce que tu fais en détail... Le code de rz0 en comparaison est beaucoup plus digeste...
Qu'en penses tu ?


  • Partager sur Facebook
  • Partager sur Twitter
Zeste de Savoir, le site qui en a dans le citron !
Anonyme
29 juillet 2009 à 21:38:40

Je suis en partie d'accord avec GurneyH, mais c'est cool de voir des codes différents quand même.

shareman, quand je parlais de termes inutiles c'était un peu tout votre dialogue, perso je comprend rien à ce que vous dîtes... cependant j'ai quand même réussi le problème, donc si moi j'ai réussi à coder le truc (et je suis sur que mon code est simple à comprendre (et il fonctionne très bien)) sans comprendre tout votre jargon là c'est que finalement y'a peut être pas besoin de se prendre la tête comme vous, non ?

Enfin vous seriez du genre à dégouter de la prog limite quoi ^^
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 0:29:39

Bah non, c'est juste qu'ils emploient des méthodes plus générales pour traiter ce problème, méthodes qui permettent également d'en résoudre d'autres. C'est une application possible et c'est intéressant de voir que ce problème rentre dans la catégorie de ceux résolubles avec des automates à états.

C'est comme si en maths on s'interdisait d'utiliser des outils plus avancés et plus géneraux pour démontrer quelque chose alors qu'une solution plus "simple" (dans le sens faisant appel à des notions moins complexes) mais plus ciblée (dans le sens "avec cette methode, je ne peux résoudre que ce problème" en schématisant) existe. C'est juste une autre manière de voir la chose et ça n'en est pas moins inutile.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 0:37:16

Citation : 21

shareman, quand je parlais de termes inutiles c'était un peu tout votre dialogue, perso je comprend rien à ce que vous dîtes...



Si tu juges inutiles tout ce que tu ne comprends pas, ça c'est une autre histoire...

De plus que le prog qu'il propose n'est pas compliqué à comprendre... Même si tu ne sais pas ce que veut dire un automate à état, ce n'est pas bien grave, son code n'est pas assez complexe pour que cela gêne la compréhension.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 0:49:57

De toute façon, je posterai demain une version plus légère de mon code. :)
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
30 juillet 2009 à 0:52:06

Citation : Arthurus

Citation : 21

shareman, quand je parlais de termes inutiles c'était un peu tout votre dialogue, perso je comprend rien à ce que vous dîtes...


Si tu juges inutiles tout ce que tu ne comprends pas, ça c'est une autre histoire...

Cité comme ça, ça me fait passer pour un crétin...
J'ai dit "inutile" dans le sens ou ce n'est pas utile de savoir tout ça pour pouvoir coder cet algorithme là (et plein d'autre je suppose).

Eusebus, oui je comprend.

EDIT: Tient le post ci dessous sera peut-être pris au sérieux lui...
J'suis sur que mon code n'est pas si mal finalement, vu les défauts mentionnés.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 2:30:58

Citation : shareman

Une solution (en C99 là) ressemblant de loin à celle d'rz0, utilisant un automate à états :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>

#define LSIZE 2000

int fsize(FILE* f) {
    fseek(f, 0, SEEK_END);
    long r = ftell(f);
    fseek(f, 0, SEEK_SET);
    return r;
}

void getcont(char* dest, FILE* file) {
    while(fgets(dest, LSIZE, file))
        dest += strlen(dest);
}

int main(int argc, char** argv) {
    if(argc < 3)
        return 1;

    FILE* file;
    if(!(file = fopen(argv[2], "r")))
        return 1;

    char t[fsize(file)], *begin, *i;
    int column = 0, row = 1, state = 0, occ = 0;

    getcont(t, file);

    for(i = t; *i != '\0'; ++i, ++column) {
        if(!state) {
            if(isalnum(*i))
                state = 1, begin = i;
            if(*i == '\n')
                ++row, column = -1;
        }
        else if(state == 1) {
            if(!isalnum(*i))
                state = 2;
        }
        else { // state == 2
            char ret = *(i-1);
            *(--i) = '\0';
            if(!strcmp(argv[1], begin))
                printf("Case %i : row %i, column %i \n", ++occ, row, column-strlen(begin));
            if(ret == '\n')
                ++row, column = 0;
            state = 0, --column;
        }
    }

    fclose(file);

    return 0;
}






Cette solution est totalement non satisfaisante.

1°) C'est bien beau de sortir des automates mais quand on voit le coût exhorbitant de l'algorithme, on se dit que tu ferais mieux de te concentrer sur des questions plus basiques. Qu'est-ce qui est exorbitant ? Réponse : ta ligne 48 que je rappelle ci-dessous

if(!strcmp(argv[1], begin))


Je ne parle même pas de ta gestion couteuse et compliquée des entrées. Ni de la limitation artificielle introduite par ton LSIZE.

2°) Ton code ne fonctionne pas sur un exemple très simple : la chaîne toto n'est pas reconnue dans le fichier :

xxxxxxx toto


3°) Ton code C est compliqué (rappel : il s'agit d'exercices pour débutants), en vrac et sans doute sans être exhaustif, j'ai noté :

*) tu abuses de l'opérateur virgule,
*) tes if imbriqués sont inutilement compliqués, cf. lignes 35 et 36 (utiliser plutot un &&),
*) l'instruction return 1; dans la fonction main() n'est pas portable,
*) tu utilises des tableaux de longueur variable qui sont peu utilisés en C avec pour conséquence par exemple que ton code ne compilera pas chez un utilisateur de Visual C++ (c'est courant chez les débutants et non débutants),
*) l'argument de isalnum() doit en principe être casté en unsigned char,
*) j'ai l'impression que les deux en-têtes #include <stdlib.h> et #include <fcntl.h> ne servent à rien.

Autre défaut mais de nature un peu différente : tu ne proposes pas une fonction, genre

int toto(char *texte, char *clef)


qui retourne le nombre d'occurrences de clef dans texte et affiche leur position ligne, colonne. Au passage, on pourrait travailler avec un pointeur sur une fonction qui envoie un flux d'octets plutôt qu'avec un tableau texte.
Personnellement il me semble indispensable de travailler avec des fonctions (qui résolvent le problème posé) plutôt qu'avec du code placé en vrac dans la focntion main(), ce qui rend souvent le code non réutilisable et/ou non autonome et/ou trop spécifique.





  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 4:56:19

Vu que la plupart des gens a fait des versions souples, légères, simples, optimisées...

Moi je ferai du compliqué histoire de changer un peu :D

Avis aux débutants qui ne sont pas au top de leurs formes :
Ne lisez pas ce qui suit, ça ne sert à rien...


Je propose une analyse descendante.

Pour ne pas vous laisser la moindre chance de troller, je le dis franchement et clairement :
* C'est trop compliqué pour rien.
* Ça sert à rien dans cet exo.
* C'est n'importe quoi...
* C'est inutile.
* ... (Je rajouterai d'autres propositions selon la demande.)

Cependant cet exo m'a permis de m'entraîner moi aussi, et me rappeler un peu ce que j'ai vu en cours de théorie de langage.
Des remarques sur la grammaire, calcul de directeur, ou le code sont les bien venues.

La grammaire :



texte -> mot sep texte | rien

************************
/*mot -> car mot | car*/ (On factorise)
************************
mot-> car BB
BB-> mot | rien

******************************
/*sep -> charsep sep | charsep*/ (On factorise)
******************************
sep -> charsep AA
AA -> sep | rien

car -> alphanum
charsep -> !alphanum


Un bug tout con dans cette grammaire, c'est qu'un texte ne doit pas commencer par un séparateur...
Mais bon, il suffit de faire :
texte -> sep mot sep texte
au lieu de
texte -> mot sep texte
Mais il faut se débrouiller pour ne pas perdre le caractère LL(1) (La flemme de vérifier).

Calculs de directeurs :


Pour garder le caractère LL(1), il faut faire attention à EOF et !alphanum... (Aurais je dû utiliser isspace() et ispunct() )
Enfin bon. Les détails ont été traités dans le code.

dir(texte->mot sep texte) = alphanum
dir(texte->rien) = EOF

dir(mot-> car BB) = alphanum

dir(BB->mot) = alphanum
dir(BB->rien) = !alphanum

dir(sep->charsep AA) = !alphanum

dir(AA-> sep) = !alphanum
dir(AA-> rien) = alphanum UNION EOF

dir(car->alphanum) = alphanum

dir(charsep->!alphanum) = !alphanum


Code :


Je l'ai fait un peu à l'arrache...
Du coup j'ai utilisé des variables globales pour ne pas m'enquiquiner avec les attributs synthétisés et hérités.
On remarquera que je n'ai pas mis les contrôleurs d'erreur dans toutes les fonctions.
À la base c'était pour débugger... Mais j'en ai laissé quelques un pour que ça fasse joli :D

prog.h:

#ifndef _PROG_H
#define _PROG_H

int lig = 1, col = 0;
char cour;
FILE* fich;
char motcour[100];
char* motcherche;
int occ = 0;
int i=0;

void next(void);
void texte(void);
void mot(void);
void BB(void);
void sep(void);
void AA(void);
void car(void);
void charsep(void);

#endif



prog.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "prog.h"

void next(void) {
	cour = fgetc(fich);
	if (cour == '\n') {
		lig++;
		col=0;
	}
	col++;
}

void texte(void) {
	if (isalnum(cour)) {
		i=0;
		mot();
		motcour[i]='\0';
		if(!strcmp(motcour,motcherche)) {
			occ++;
			printf("Occ : %d, lig : %d, col : %d\n",occ,lig,col-strlen(motcour)-1);
		}
		sep();
		texte();
	} else if (cour != EOF) {
		printf("Erreur interne func texte\nlig : %d, col : %d\n",lig,col);
		exit(1);
	}
}

void mot(void) {
	if (isalnum(cour)) {
		car();
		BB();
	}
}

void BB(void) {
	if (isalnum(cour)) {
		mot();
	}
}

void sep(void) {
	if (!isalnum(cour)) {
		charsep();
		AA();
	}
}

void AA(void) {
	if ((cour != EOF) && !isalnum(cour)) {
		sep();
	}
}

void car(void) {
	if (isalnum(cour)) {
		motcour[i++]=cour;
		next();
	} else {
		printf("Erreur interne func car\nlig : %d, col : %d\n",lig,col);
		exit(1);
	}
}

void charsep(void) {	
	if (!isalnum(cour)) {
		next();
	} else {
		printf("Erreur interne func charsep\nlig : %d, col : %d\n",lig,col);
		exit(1);
	}
}

int main (int argc,char* argv[]) {
	if (argc != 3) {
		printf("Erreur de paramètre\n");
		exit(1);
	}
	fich=fopen(argv[2],"r");
	if (!fich) {
		printf("Erreur de lecture du fichier\n");
		exit(1);
	}
		
	motcherche = argv[1];
	next();
	texte();
	printf("%d occurence(s) trouvée(s)\n",occ);
	return 0;

}



Résultat :


arthurus@arthurus:~/exozero$ ./prog les foo
Occ : 1, lig : 3, col : 36
Occ : 2, lig : 4, col : 1
Occ : 3, lig : 4, col : 15
Occ : 4, lig : 5, col : 50
4 occurence(s) trouvée(s)
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 7:58:11

Voila ma contribution, aussi un code simple avec une utilisation de la mémoire on ne peux plus minimaliste me semble-t-il...

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

__attribute__((noreturn)) void die(const char* message) {

	fprintf(stderr, "Erreur ! %s\n", message);
	exit(EXIT_FAILURE);

}

int main(int argc, char** argv) {

	if (argc != 3)
		die("Mauvais nombre d'arguments.");

	char* zSearchedWord = argv[1];
	unsigned zWordMaxSize = strlen(zSearchedWord),
			zCount = 0, zWordSize = 0;

	{
		unsigned i;
		for(i = 0; i < zWordMaxSize; i++)
			if (!isalpha(zSearchedWord[i]))
				die("Argument avec un mauvais formattage.");
	}

	char* zFoundWord = calloc(zWordMaxSize+1, sizeof(char));

	if (zFoundWord == NULL)
		die("Impossible d'allouer de la mémoire.");

	FILE* zFile = fopen(argv[2], "r");

	if (zFile == NULL) {
		free(zFoundWord);
		die("Impossible d'ouvrir le fichier.");
	}

	printf("Recherche du mot `%s' dans le fichier `%s'.\n\n", zSearchedWord, argv[2]);

	unsigned zRow = 1, zCol = 1;

	while(1) {

		const int zChar = fgetc(zFile);

		if (zChar == EOF) {
			printf("\n%d occurence%s au total.\n", zCount, zCount < 2 ? "" : "s");
			break;
		}

		if (isalpha(zChar)) {
			if (zWordSize < zWordMaxSize)
				zFoundWord[zWordSize++] = (char)zChar;
		} else {
			if (zWordSize == zWordMaxSize && strcmp(zSearchedWord, zFoundWord) == 0)
				printf(
						"Occurence %d:\tLigne: %d\tColonne: %d\n",
						++zCount,
						zRow,
						zCol - zWordSize - 1
				);
			zWordSize = 0;
			if (zChar == '\n')
				zRow += (zCol = 1);
		}

		zCol++;

	}

	fclose(zFile);
	free(zFoundWord);

	return EXIT_SUCCESS;

}
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 8:30:08

Citation : 21

J'suis sur que mon code n'est pas si mal finalement, vu les défauts mentionnés.



C'est vrai, je l'ai regardé et il n'est pas mal (lien vers le code).


Quelques petits défauts sur la forme :
- il manque l'include string.h
- une des lignes de ta fonction main() dépasse largement les 80 colonnes recommandées pour la lisibilité
- tu fais un system("pause") qui n'est pas portable et est plus globalement du mauvais style
- à mon avis, tu sautes trop de lignes : ta fonction principale s'étend sur 40 lignes, soit près de deux écrans, ce qui est un peu trop long pour une fonction qui ne fait fondamentalement qu'une seule boucle; pour plus de lisibilité (quand tu définis une variable mais qu'on doit scroller d'un écran pour voir où elle est utilisée, c'est pas terrible) tu devrais favoriser des formes plus compactes (pas de lignes vides, et pourquoi pas éviter les accolades autour des simples instructions); ce point là est plutôt subjectif, mais ça repose sur un critère objectif : quand une fonction fait plus de 25 lignes de haut, tu devrais essayer de voir si tu peux la découper en plusieurs blocs, ou la rendre plus compacte.
- tu as un problème de conventions de nommage :
--- le nom "c2" n'est pas clair du tout (soit tu mets un comment pour indiquer ce qu'il fait, soit, encore mieux, tu lui trouves un nom parlant du genre "prev_c", "old_c" ou "c_precedent", je ne sais pas)
--- il y a une incohérence entre "nb_occurences" (s à la fin) et "nb_ligne/nb_colonne" (pas de s). En fait, "nb_" devrait être réservé à des variables qui comptent la globalité de quelque chose, c'est un indicateur de taille, pas de numéro ou de position; tu devrais plutôt parler de "colonne courante" ou de "numéro de la colonne actuelle", cur_colonne ou num_colonne, ou prendre une autre convention qui te plaît mais éviter nb_ ici.


Deux problèmes techniques :
- en fin de boucle tu affiches toutes les lettres, même l'EOF à la fin qui n'est pas un caractère légal et ne devrait pas être affiché. Tu devrais soit faire ce printf() sous la condition (if (c != EOF) printf("%c",c);) soit le mettre dans le while (while (c != EOF && printf("%c", c));, selon ce que tu trouves le plus lisible et le plus correct
- tu affiches le chevron ouvrant même quand ce n'est pas une vraie occurence (par exemple si je cherche "les" et mets les mots "lese majeste" il va sortir "<lese majeste", ce qui n'est pas très correct); pour corriger ce problème, il suffit de ne rien sortir tant que ok est dans l'état 1, et d'attendre l'état 2 pour décider ce que tu fais : si c'était vraiment une occurence, tu affiches < puis le mot (chaine), sinon tu affiches seulement le mot

Un problème majeur : l'utilisation que tu fais de tabOcc n'est vraiment pas satisfaisante. À supposer que tu souhaites vraiment stocker les occurences, il faudrait ici utiliser une liste chaînée, car le nombre d'occurences trouvées n'est pas connu à l'avance. Ton programme ne marche plus s'il trouve plus de 1000 occurrences; tu devrais redimensionner dynamiquement ton tableau pour éviter les dépassements, ou utiliser une liste chaînée. Par ailleurs, le redimensionnement final n'est pas nécessaire et dépense inutilement de la mémoire : il suffit de renvoyer le nombre d'occurrences trouvées (sa "taille effective" si tu veux), pour que l'utilisateur sache quelle partie du tableau contient des données intéressantes. D'ailleurs tu oublies de libérer le tableau renvoyé par la fonction.


Quelques remarques plus générales et moins précises :
- ta fonction principale mélange traitement des données et entrée/sortie; c'est généralement considéré comme une mauvaise chose, sauf dans le cas de traitements de flot (c'est ton cas ici); mais quitte à faire du traitement de flot, autant le faire entièrement comme rz0 : toi tu es à cheval entre du traitement de flot dans ta fonction principale et du post-traitement ensuite, ce qui donne un truc assez étrange
- ton traitement du retour de nb_occurences en première case du tableau est très maladroit : cette variable n'a pas sa place dans le tableau, tu devrais la renvoyer à part; soit tu renvoies une struct, soit (plus crédible) tu prends un pointeur en paramètre supplémentaire de ta fonction, pour y mettre nb_occurences (ça rejoint ma critique précédente sur l'inutilité de tabOcc2 quand on a la taille disponible); on ne met pas dans un même tableau en vrac des données qui n'ont rien à voir
- ton algorithme est en fait le même que celui de shareman (à la différence que, toi, tu as fait attention à ne pas gaspiller inutilement de la mémoire), et tu souffres des mêmes défauts :
-- le nom de tes états (0, 1 et 2) n'est pas clair; utilise des #define ou un enum pour leur donner des noms compréhensible, par exemple SKIP_WORD, GOOD_PREFIX et FOUND_WORD, ou les noms de ton choix du moment que c'est plus explicite que 2.
-- je trouve que le if (ok) ... n'est pas du tout sémantique : c'est bien si tu manipules ta variable comme un booléen (0 ou 1, ou plus généralement "0 est la valeur correcte/spécifique et tout le reste est une valeur d'erreur/traitement"), mais ici ce sont des états dont aucun n'est plus spécial que les autres, il faut utiliser (ok == 0) : sinon, le lecteur s'attend à une interprétation booléenne dans le reste du code et ça nuit à la compréhension
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 10:07:36

Citation : candide

Autre défaut mais de nature un peu différente : tu ne proposes pas une fonction, genre

int toto(char *texte, char *clef)



qui retourne le nombre d'occurrences de clef dans texte et affiche leur position ligne, colonne. Au passage, on pourrait travailler avec un pointeur sur une fonction qui envoie un flux d'octets plutôt qu'avec un tableau texte.
Personnellement il me semble indispensable de travailler avec des fonctions (qui résolvent le problème posé) plutôt qu'avec du code placé en vrac dans la focntion main(), ce qui rend souvent le code non réutilisable et/ou non autonome et/ou trop spécifique.



Mouais, ça je ne suis pas d'accord. Le but de l'exo n'est pas de faire quelque chose de générique mais bel et bien un petit programme qui effectue une tâche simple. Faire une fonction qui récupère toutes les occurrences pour traitement postérieur est un problème sensiblement différent : il y a les considérations de structures de données ou de délégation d'entrée/sortie, et comme j'en ai déjà parlé avant, on ne réfléchit certainement pas pareil quand on sait que l'on travaille en interne pour être réutilisé par une autre partie du programme, ou que l'on sait que l'on est au niveau le plus haut. Bref, à cela s'ajoute que le code réutilisable ultime n'existe pas, et qu'écrire un composant en sachant doser adroitement la part de généricité et celle d'efficacité n'est pas une mince affaire, et à défaut, le débutant croira avoir écrit un code générique, mais qui en réalité sera inutilisable. En somme, il faudrait un énoncé différent, et des débutants capables de réfléchir et d'expliquer les implications de leurs codes : pourquoi un tableau ? pourquoi une liste chaînée ? pourquoi lire directement en entrée ? pourquoi utiliser un pointeur de fonction en entrée ? pourquoi pas en sortie ? quels arguments devrait-il prendre ? et si je voulais pouvoir interrompre le processus ? etc. les questions liées à la réutilisation sont sans fin et vouloir les introduire dans des exos simples pour programmeurs novices serait, à mon avis, très optimiste ; pourquoi pas quelques exos axés sur ces questions, dans ce cas-là, mais il faudrait y réfléchir. Après, c'est mon avis.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 11:04:53

Les exercices pour les novices ne devraient pas avoir besoin d'être écrits de sorte à ce que le code soit générique, car leur simplicité doit faire qu'il n'y a pas lieu d'écrire un code réutilisable. Il devrait toujours être fait en sorte que la consigne soi assez précise pour que l'apprenti programmeur ne soit pas perdu et qu'il voit clairement son but; ainsi il écrira son code avec toujours ce but en tête. Son programme sera ainsi fait pour répondre à la consigne donnée et pas à une qui pourrait être donnée dans un futur plus ou moins éloigné.

Néanmoins, un code réutilisable n'est absolument pas critiquable, mais il me semble que pour un exercice de ce type, il n'y a rien qui peut être générique...
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 11:44:41

Citation : candide

C'est bien beau de sortir des automates mais quand on voit le coût exhorbitant de l'algorithme, on se dit que tu ferais mieux de te concentrer sur des questions plus basiques.


Je n'ai jamais dit que mon algo était super en terme de complexité mémoire (en complexité temporelle, c'est linéaire) et comme j'en ai discuté avec bluestorm et rz0 sur IRC, j'ai bien précisé que j'allais poster une version plus légère. Mais d'autres codes postés ici sont beaucoup plus "lourd" en terme de mémoire que le mien, je ne vois pas pourquoi faire une fixation sur mon code.

Citation : candide

*) tes if imbriqués sont inutilement compliqués, cf. lignes 35 et 36 (utiliser plutot un &&),


Ben non, j'ai décidé de structurer ça clairement : un bloc if par état. On m'a conseillé un switch, technique que je vais utiliser dans la nouvelle version de mon code. Et si j'avais fait ça comme tu l'entends, ça aurait été encore beaucoup moins clair, et tu m'aurais certainement reproché ça.

Citation : candide

*) l'instruction return 1; dans la fonction main() n'est pas portable,



C'est portable sur la plupart des systèmes mais effectivement, il y en a où ce n'est pas le cas, par exemple VMS, (return 0 non plus d'ailleurs n'est pas "strictement" portable) en fait si.

Citation : candide

*) j'ai l'impression que les deux en-têtes #include <stdlib.h> et #include <fcntl.h> ne servent à rien.


Ah oui, j'avais oublié d'effacer ces include parce que je croyais en avoir besoin un moment (d'ailleurs, je vais avec besoin de stdlib.h pour les valeurs de retour du main()).

Merci pour ton commentaire, candide.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 11:49:10

Citation : shareman

Mais d'autres codes postés ici sont beaucoup plus "lourd" en terme de mémoire que le mien, je ne vois pas pourquoi faire une fixation sur mon code.


Euh, les quels ?
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
30 juillet 2009 à 13:57:54

bluestorm, oui tu as raison, merci pour la patience pour tout ces commentaires intéressant.

C'est vrai que mon tableau est un peu spécial : [nb_occurences][c][l][c][l]...

Et tu as très raison, pour le fait que j'aurais du utiliser realloc dans la boucle ce qui, en plus, m'aurait éviter de retailler le tableau à la fin.

Si je le retaillais ce tableau c'est pour libérer la mémoire inutile du grand tableau (la place qui n'aurait pas été utilisé).

Mais finalement qu'est ce qui le plus lent entre :
Faire un grand malloc au début, et retailler à la fin
ou faire plein de petit realloc au fil de la fonction ?

Je sais pas j'ai jamais tester mais je pense que c'est plus rapide comme tu dis et en tout cas moins gourmand en mémoire et forcément plus prudent au cas ou on dépasse la limite.

Et sinon c'est vrai que j'ai mis plein de parenthèses inutile mais en fait j'ai oublié de les enlever, pendant que je programme je l'ai met pour, au cas ou, rajouter d'autre instructions dedans, mais là j'ai oublier de les enlever, et oui y'a plein de lignes sauté pour rien aussi.

Et pour la convention de nommage je n'y ai jamais vraiment fais attention...

Pour le system(pause) c'est juste pour bien voir à la fin, des fois je fais ça :
#define stop for(;;);
C'est ce que j'ai du faire d'ailleurs pour tester le code de rz0 qui en fait faisait comme si que quelqu'un tapait sans cesse sur le clavier ce qui rendait system(pause) inutile.

Et pour le < c'est pas grave vu que tout ça n'est pas censé se voir théoriquement j'ai mis ça pour bien coder le truc au départ.
A la fin je devais enlever tout les printf de la boucle.

Merci pour cette analyse ;)
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 14:29:39

Citation

Et tu as très raison, pour le fait que j'aurais du utiliser realloc dans la boucle ce qui, en plus, m'aurait éviter de retailler le tableau à la fin.



Attention, n'utilise pas realloc comme Al3xx, c'est super mauvais comme façon de faire.

C'est quoi le problème avec les listes chaînées ? Je comprends pas pourquoi les programmeurs en C se feraient dévorer vivants plutôt que d'utiliser cette structure de donnée omniprésente et très importante. C'est pas parce que vous en avez pas built-in dans le langage qu'il faut faire comme si ça n'existait pas !
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
30 juillet 2009 à 14:34:26

Ah bah si j'aurais fais comme lui, pourquoi quel est le problème avec cette technique ?
et je ne connais pas les listes chainées (enfin peut-être que si mais je n'ai pas donné de nom dessus).
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 14:49:48

Citation : 21

Ah bah si j'aurais fais comme lui, pourquoi quel est le problème avec cette technique ?



Il fait une reallocation par occurrence trouvée, et une reallocation coûte cher.

Citation : blueblue

C'est quoi le problème avec les listes chaînées ? Je comprends pas pourquoi les programmeurs en C se feraient dévorer vivants plutôt que d'utiliser cette structure de donnée



Stop diffamation généralisante § :-,
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 15:06:37

Bonjour,

Ça faisait longtemps que je n'étais plus passé par la... Merci pour cet exo bien sympathique :) .

J'ai été naturellement tenté de stocker le fichier dans une chaine (traitement plus simple), mais j'ai finalement fait un traitement directement sur le fichier.

Mon code :

#include <stdio.h>
#include <ctype.h>



unsigned grep (FILE* fp, const char* search)
{
    const char* s;
    unsigned ret = 0, co, li;
    int c, prev = ' ';
    for (co=1, li=1; (c = fgetc(fp)) != EOF; co++)
    {
        if (c == '\n')
            li++, co = 0;
        if ( c == *search && isspace(prev) )
        {
            s = search;
            while ( (c = fgetc(fp)) == *++s);
            if (!*s && isspace(c))
                printf("Occurrence %u -> ligne %u, colonne %u\n", ++ret, li, co);
        }
        prev = c;
    }
    return ret;
}

int main(int argc, char** argv)
{
    FILE* fp = NULL;
    if (argc == 3)
        if ( (fp = fopen(argv[2], "rb")) != NULL )
        {
            printf("Trouve %u fois.", grep(fp, argv[1]));
            fclose(fp);
        }
    return 0;
}


EDIT:
- oubli du fclose.
- introduction de fgetc() dans le for.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 15:18:43

21 > quand la mémoire est utilisée, realloc ne va pas forcément trouver de place "juste après" le tableau existant; il va donc allouer de la mémoire autre part, et y déplacer les éléments existants.

Ça veut dire que pour un tableau de taille N, tu as un nombre de déplacements dans le pire des cas proportionnels à N (on note O(N)). Donc si tu fais un realloc sur un tableau de taille 1, puis un realloc sur un tableau de taille 2, puis 3, ..., puis N, tu vas avoir 1 + 2 + 3 + ... + N recopies au total, soit environ N * N / 2 : c'est un algorithme qui fait un nombre d'opérations dans le pire cas proportionnel à N * N : pour faire 10000 opérations, il est 1 000 000 fois plus lent (dans le pire cas) que pour faire 100 opérations.

En pratique, ce scénario catastrophe n'est pas forcément très fréquent, mais ça reste une très mauvaise manière de faire. Il y a deux méthodes :
- utiliser un tableau dynamique amorti, c'est à dire allouer beaucoup de mémoire dès que tu en as besoin, pour allouer moins souvent : si tu doubles la taille de ton tableau à chaque redimensionnement, tu le redimensionneras beaucoup moins souvent et tu n'auras pas ce problème
- utiliser une liste chaînée, c'est à dire mettre ton élément à un endroit libre de la mémoire, pas forcément à côté des éléments, et stocker son adresse pour pouvoir y accéder; ajouter un élément à une liste est très rapide et ne demande pas de toucher aux éléments précédents


Il y a une présentation des listes chaînées, comparées au tableau, dans le tutoriel d'algorithmique, qui traite plus généralement de ce genre de questions : comment calculer grossièrement le nombre d'opérations de telle ou telle méthode, pour choisir la plus efficace pour ce que je veux faire ?
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
30 juillet 2009 à 15:32:30

Ah oui je comprend, je connaissais le fonctionnement de realloc car je l'avais recodé sans savoir que ça existait...

Oui donc on en alloue 1000 et puis des que ça dépasse on en réalloue mille de plus, beaucoup moins lent à ce moment.

Faut toujours trouver le juste milieu entre vitesse de calcul et la mémoire, la manière d'alexx est la mieux pour la mémoire, mais la pire pour le calcul donc.

yoch, je vois deux boucles imbriqués, est ce que dés que tu trouve le premier élément de la chaine à rechercher tu fais un while pour savoir si tout le reste est correct ?
En fait ce while fait en sorte que l'on peut se passer des états alors.

Mais toi tu délimite juste avec les espaces alors que normalement il devrait être possible de détecter la chaine 'les' dans '_les_' mais ça se n'est qu'un détail.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 15:48:33

Citation

Faut toujours trouver le juste milieu entre vitesse de calcul et la mémoire, la manière d'alexx est la mieux pour la mémoire, mais la pire pour le calcul donc.


Non, une liste chaînée fera aussi bien en mémoire : en pratique, elle alloue un peu plus puisqu'il faut conserver la position des éléments dispersés, mais c'est du même ordre de grandeur. De plus, sa méthode nécessite de trouver des éléments adjacents en mémoire, ce qui n'est pas toujours possible : plus il y a d'occurences, plus il y a de risque que le système d'exploitation t'envoies chier parce que tu demandes une place libre trop grande; les listes chainées n'ont pas ce problème : tant qu'il y a de la mémoire libre, même si c'est des petits bouts morcelés tout partout, tu peux ajouter des éléments.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 15:50:11

Mon code (avec un système de flag et une structure).
Je précise que je n'ai pas utilisé les arguments du main() (et je serais impérméable à tout reproche, après tout si je fais l'exo c'est pour moi...)

#include <stdio.h>
#include <ctype.h>
#include <string.h>

typedef struct
{
	FILE *f;

	char car;

	char chn[10];

	int lig,
	    col;

	int cpt,
	    size,
	    flag;

} Structure;

void aff_occ(Structure *S)
{
	printf("Occurence ligne %d et colonne %d \n",
	                  S->lig,     S->col - S->size);
}

void test_spc(Structure *S)
{
	(S->col)++;

	if('\n' == (S->car))
	{
		(S->lig)++;
		(S->col) = 0;
	}
}

void test_occ(Structure *S)
{
	if(S->car == S->chn[S->cpt])
		S->cpt++;

	else
	{
		S->cpt  = 0;
		S->flag = 0;
	}

	if(S->cpt == S->size)
	{
	   aff_occ(S);
	   S->cpt = 0;
	}
}

int main(void)
{
    Structure S = {fopen("txt.txt", "r"), ' ', "les", 1, 0, 0, 0, 1};

    strncat(S.chn, " ", 1);

    S.size = strlen(S.chn);

	while((S.car = fgetc(S.f)) != EOF)
	{
		test_spc(&S);

		if(S.flag)
		   test_occ(&S);

		if(!S.flag)
		    S.flag = isspace(S.car);
	}

	fclose(S.f);

    return 0;
}


PS : @ yoch > Avec le fichier texte suivant ton programme affiche un peu ce qu'il veut :
les lesives les les lesles sales     
 les les


EDIT :

Citation

il devrait être possible de détecter la chaine 'les' dans '_les_'


J'avais oublié ça j'editerais plus tard.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 15:51:41

Citation : 21

yoch, je vois deux boucles imbriqués, est ce que dés que tu trouve le premier élément de la chaine à rechercher tu fais un while pour savoir si tout le reste est correct ?


Oui, exactement. J'ajoute simplement une vérification que le caractère précèdent l'occurrence trouvée est bien un espace.

Citation : 21

En fait ce while fait en sorte que l'on peut se passer des états alors.


De toute manières, il me semble que mon code reprend le principe d'automate.


Citation : Camp0us

PS : @ yoch > Avec le fichier texte suivant ton programme affiche un peu ce qu'il veut :


Merci ! Je regarde ca...

EDIT:

Code corrigé :

#include <stdio.h>
#include <ctype.h>



unsigned grep (FILE* fp, const char* search)
{
    const char* s;
    unsigned ret = 0, co, li, nb_col;
    int c, prev = ' ';
    for (co=1, li=1; (c = fgetc(fp)) != EOF; co++)
    {
        if (c == '\n')
            li++, co = 0;
        if ( c == *search && isspace(prev) )
        {
            s = search;
            for (nb_col=1; (c = fgetc(fp)) == *++s; nb_col++);
            if (c == '\n')
                li++, co = 0;
            if ( !*s && (isspace(c) || c == EOF) )
                printf("Occurrence %u -> ligne %u, colonne %u\n", ++ret, li, co);
            co += nb_col;
        }
        prev = c;
    }
    return ret;
}

int main(int argc, char** argv)
{
    FILE* fp = NULL;
    if (argc == 3)
        if ( (fp = fopen(argv[2], "rb")) != NULL )
        {
            printf("Trouve %u fois.", grep(fp, argv[1]));
            fclose(fp);
        }
    return 0;
}
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 18:34:40

Citation : yoch

Citation : 21

En fait ce while fait en sorte que l'on peut se passer des états alors.


De toute manières, il me semble que mon code reprend le principe d'automate.



Hum, moi je vois ça plutôt comme une autre manière de traiter le problème, mais c'est équivalent oui ; ceci dit, ya pas de quoi se focaliser sur les termes « automate » et « états », vu comme nos programmes ne sont que vaguement ressemblant à ces modèles ; c'est juste bon à savoir que cela « se ramène à ça ». Puis certains trouvent ça rigolo de chercher ce genre de correspondances (pas moi particulièrement, je précise). :]
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 19:01:16

Citation : yoch

J'ajoute simplement une vérification que le caractère précèdent l'occurrence trouvée est bien un espace.



Très bonne idée (bien que presque évidente) que de mémoriser le dernier caractère, ça simplifie pas mal le code (par rapport à ce que j'avais commencé à écrire en tous cas).

Pour être plus conforme à l'énoncé, change isspace(toto) en !isalnum(toto).

Ton algorithme (de même que celui de rz0) est optimal, en O(1), on fait tout à la volée.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 19:51:06

Salut,

Citation : candide


Ton algorithme (de même que celui de rz0) est optimal, en O(1), on fait tout à la volée.


J'ai peur de ne pas comprendre...
La fichier est parcouru entièrement une fois non ?
Ce n'est pas O(N), avec N le nombre du caractère du fichier ?

Je vais regarder ces codes attentivement ce week-end, mais il me semble qu'ils ont la même complexité en temps que "l'algo naïf".

Ou est mon erreur...

a+
  • Partager sur Facebook
  • Partager sur Twitter
Zeste de Savoir, le site qui en a dans le citron !
30 juillet 2009 à 19:54:05

Citation : GurneyH

Citation : candide


Ton algorithme (de même que celui de rz0) est optimal, en O(1), on fait tout à la volée.


J'ai peur de ne pas comprendre...
La fichier est parcouru entièrement une fois non ?
Ce n'est pas O(N), avec N le nombre du caractère du fichier ?


Je pense que candide parlait de la complexité mémoire.
  • Partager sur Facebook
  • Partager sur Twitter
30 juillet 2009 à 20:55:47

:-° Je comprends encore moins alors !

Si on ne stocke rien, on a une complexité mémoire en O(1), non ?

Mon code, corrigé plusieurs fois je l'admet volontier...
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

int zGrep(FILE *src, char const *needle)
{
    int nbOcc = 0;

    size_t row = 0;
    size_t col = 0;

    size_t lenWord = 0;
    size_t lenNeedle = strlen(needle);

    char *word = malloc(lenNeedle * sizeof *word);
    if (word != NULL)
    {
        int cmpOk = 1;

        int c;
        while ((c = fgetc(src)) != EOF)
        {
            ++col;
            if (isalnum(c) && cmpOk)
            {
                word[lenWord++] = (char)c;
                cmpOk = !(lenWord > lenNeedle || word[lenWord - 1] != needle[lenWord - 1]);
            }
            else
            {
                if (lenWord == lenNeedle && cmpOk)
                {
                    printf("1 occurrence trouvee a la ligne %u colonne %u\n\n",
                           row + 1, col - lenWord);
                    ++nbOcc;
                }

                if (c == '\n')
                {
                    ++row;
                    col = 0;
                }
                lenWord = 0;
                cmpOk = 1;
            }
        }
    }
    if (word)
        free(word), word = NULL;

    return nbOcc;
}



void showUsage(void)
{
    puts
    (
        "zGrab usage \n"
        "1er arg : Nom du fichier a analyser. \n"
        "2nd arg : Chaine a rechercher dans le fichier. \n"
    );
}



int main(int argc, char *argv[])
{
    int exitCode = EXIT_SUCCESS;
    if (argc != 3)
    {
        showUsage();
        exitCode = EXIT_FAILURE;
    }
    else
    {
        FILE *in = fopen(argv[1], "r");
        if (in == NULL)
        {
            perror("Erreur d'ouverture du fichier ");
            exitCode = EXIT_FAILURE;
        }
        else
            zGrep(in, argv[2]);

        fclose(in);
    }

    return exitCode;
}

Je lui accordais une complexité en temps O(N) et une complexité mémoire O(1)...
Si je me trompe, ce serait sympa de m'expliquer...

a+
  • Partager sur Facebook
  • Partager sur Twitter
Zeste de Savoir, le site qui en a dans le citron !