7d.nz

Bilan des résultats des élèves

[2019-03-04 lun.]

  code fr

en C, Python, Go et Awk

Traduction en anglais

monk.png

Présentation

En fin d’année scolaire, le professeur d’une classe souhaite faire le bilan des résultats de ses élèves en pondérant les notes obtenues dans trois matières : français (poids 10), mathématiques (poids 8) et histoire-géographie (poids 6). La note maximale dans chaque matière étant 10, la note pondérée maximale est 240.

Comme résultat, il veut classer ses élèves en trois groupes : ceux qui ont au moins 180, ceux qui ont au moins 120 et les autres (qui ont donc moins de 120) et imprimer, pour chaque groupe, le nom de l’élève, les notes obtenues dans les matières concernées et la note moyenne pondérée (total pondéré divisé par 24).

Le professeur en question étant aussi un programmeur curieux, il décide d'écrire le programme dans quatre langages de programmation différents et de comparer les codes sources.

Le résultat obtenu nous montre que:

  • une solution écrite en C sera la plus longue puisque, la librarie standard étant peu fournie à ce niveau il faudra traiter soit-même l'entrée du tableau,
  • La plus abordable est en Python, qui présente l'avantage d'être lisible, facile et rapide.
  • Un peu plus verbeux et rigide sera le code écrit en Go;
  • Et bien que moins répandu, la solution la plus succinte est obtenue avec ob-awk, tout désigné pour ce genre de programme.

Tests C :noweb

On fait par la suite usage de :noweb1org#Noweb Reference Syntax1 pour faire les inclusions de sources. Quelques vérifications préalables s'imposent pour s'assurer que tout fonctionne

test 1

  int c;
c=42;
int test() {
 return 1;
}
printf("%i\n",c);
42
  #include <stdio.h>
printf("%i\n",43);
test();
  #include "stdlib.h"
#include "stdio.h"

<<test1>>

test 2

  void myfunc() {
	printf("print from function\n");
}
  int main(int argc,char **argv) {
	printf("Hello World\n");
	myfunc();
	exit(0);
}
  #include "stdlib.h"
#include "stdio.h"

<<srcMyfunc>>
<<srcMain>>

Données

Les données sont dans un fichier texte organisé en tableau come dans l'exemple suivant.

  | BAACH Leila          |  6 |  9 |  9 |
| BARREIRO Bruno       |  9 |  0 | 10 |
| COURAULT Eva         |  2 |  8 |  0 |
| DELL OVA Laura       |  3 |  0 |  3 |

Génération du fichier de données

A titre d'exemple, on génère à l'aide d'un script python un tableau dans un fichier comprenant une liste de noms avec pour chacun trois notes.

  from random import randint
elv = """\
	BAACH Leila
	BARREIRO Bruno
	COURAULT Eva
	DELL OVA Laura
	DUGUET Pierre
	ESCANDE Thomas
	GATTO Marianne
	GUIRAUD Sarah
	LHEUILLIER Margaux
	LONGO Tania
	MARTIN Caroline
	MARTY Lea
	MATTIA Louis
	MORIN Jules
	PASCALE Eva
	PAYSSERAND Laura
	RIBEIRO SOCORRO Jade
	ROMERO Bastien
	ROTY Elodie
	RUTIGLIANO Marine
	SAFOURCADE Morgane
	ZOUINI Nada"""
carac = 3

with open("/tmp/eleves.txt","w") as f:
		for e in elv.split('\n'):
				el = (("| %s |" + (" %i |"*carac))  % (
							e.strip(), *[randint(0,10) for x in range(carac)]))
				print(el)
				f.write(el+"\n")
  cat /tmp/eleves.txt

C

C n'est pas le langage le plus adapté à ce cas de figure, compte tenu qu'il faudra traiter soit mêmes les entrées/sorties.

Fonctions C pré-requises

Supprimer les espaces superflus dans les chaînes avant conversion en entier

exemple : la chaine " 42 " est convertie en l'entier 42.

  /*
	Supprimer les espaces superflus dans les chaînes
	avant conversion en entier
	exemple : la chaine "  42 " est convertie en l'entier 42.
*/
#include <string.h>

// les caractères considérés comme des espaces
#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')

char *trimwhitespace(const char *str)
{
	char *end;

	// retrait des espaces avant
	while(isspace((unsigned char)*str)) str++;

	if(*str == 0)
		return str;

	// retrait des espaces après
	end = str + strlen(str) - 1;
	while(end > str && isspace((unsigned char)*end)) end--;

	// ajout du terminateur de chaîne
	end[1] = '\0';

	return str;
}

Scinder une chaîne et récupérer les champs

  /**
Scinder une chaîne en champs
**/
 #include <stdlib.h>
 #include <string.h>

 const char* champ(char* line, const char * c, int num)
 {
		char * tmp = strdup(line);
		const char* tok;
		for (tok = strtok(tmp, c);
				 tok && *tok;
				 tok = strtok(NULL, c))
				 {
					 if (!--num) {
							return tok;
					 }
				 }
				 return NULL;
 }

test C

  <<parse>>
<<trimwhitespace>>
#include <stdlib.h>
#include <stdio.h>

int franc, maths, higeo;

int main()
{
		FILE *fp;
		char str[60];
		fp = fopen("/tmp/eleves.txt" , "r");
		char line[1024];
		//while (fgets(line, 1024, stdin))
		while (fgets(line, 1024, fp))
		{
			char * tmp = strdup(line);
			char * nom = getfield(tmp, "|", 1);
			printf("%s ",nom);
			tmp = strdup(line); // NOTE strtok clobbers tmp
			franc = atoi(trimwhitespace(getfield(tmp, "|", 2)));
			tmp = strdup(line);
			maths = atoi(trimwhitespace(getfield(tmp, "|", 3)));
			tmp = strdup(line);
			higeo = atoi(trimwhitespace(getfield(tmp, "|", 4)));
			printf("%i %i %i\n", franc, maths, higeo);
			free(tmp);
		}
		fclose(fp);
 }

C Ansi99

Compte tenu du format de nos données, quelques fonctions sont préalablement nécéssaires.

  <<champs>>
<<trimwhitespace>>
#include <stdlib.h>
#include <stdio.h>

// Coefficients pour chaque matière
const struct Coeff {
	 int francais;
	 int maths;
	 int histgeo;
} coeff = {
	 .francais = 10,
	 .maths = 8,
	 .histgeo = 6,
};

// Total des coefficients
int total_coeff;

typedef struct Eleve {
	 char * nom;
	 int francais, maths, histgeo, total;
	 float moy;
} Eleve;


Eleve ** tab180;
Eleve ** tab120;
Eleve ** tabinf;

// l'unité de reservation mémoire pour les tableaux dynamiques
const VSZ = 16;

// affichage des tableaux d'élèves
void affiche_liste(Eleve ** eleves, int nbe) {
	 printf("|%-22s|%8s|%5s|%7s|%3s|%5s|\n",
						"Nom","français","maths","histgeo","moy","total");
	 printf("|-\n");
	 for (int i=0; i<nbe; i++){
		 printf("|%-22s|%8d|%5d|%7d|%.1f|%5d|\n",
				 eleves[i]->nom,
	 eleves[i]->francais,
	 eleves[i]->maths,
	 eleves[i]->histgeo,
	 eleves[i]->moy,
	 eleves[i]->total);
	 }
}

// programme principal
int main()
{
		FILE *fp; // le descripteur du fichier
		fp = fopen("/tmp/eleves.txt" , "r");

		char line[128]; // tampon pour la lecture des lignes du fichier

		// allocation des tableaux
		tab180 = malloc(VSZ*sizeof(void*));
		tab120 = malloc(VSZ*sizeof(void*));
		tabinf = malloc(VSZ*sizeof(void*));
		int v180 = 1, v120 = 1, vinf = 1;
		int n180=0, n120=0,ninf=0;

		total_coeff = coeff.francais + coeff.maths + coeff.histgeo;

		while (fgets(line, 128, fp)) // lecture d'une ligne du fichier
		{
			Eleve * eleve = malloc(sizeof(Eleve));
			eleve->nom = trimwhitespace(champ(line, "|", 1));
			eleve->francais = atoi(trimwhitespace(champ(line, "|", 2)));
			eleve->maths = atoi(trimwhitespace(champ(line, "|", 3))),
			eleve->histgeo = atoi(trimwhitespace(champ(line, "|", 4))),
			eleve->total =
					 eleve->francais * coeff.francais
				 + eleve->maths * coeff.maths
				 + eleve->histgeo * coeff.histgeo;

			eleve->moy =(float) eleve->total / (float)total_coeff;

			if(eleve->total >= 180) {
				 tab180[n180] = eleve;
	 n180++;
			}else if(eleve->total >= 120) {
	 tab120[n120] = eleve;
				 n120++;
			}else {
	 tabinf[ninf] = eleve;
				 ninf++;
			}

			if(n180 > VSZ*v180) {
				 realloc(tab180, v180 * VSZ * sizeof(void*));
	 v180++;
			}
			if(n120 > VSZ*v120) {
				 realloc(tab120, v120 * VSZ * sizeof(void*));
	 v120++;
			}
			if(ninf > VSZ*vinf) {
				 realloc(tabinf, vinf * VSZ * sizeof(void*));
	 vinf++;
			}
		}

		printf("\ntab180\n");
		affiche_liste(tab180,n180);
		printf("\ntab120\n");
		affiche_liste(tab120,n120);
		printf("\ntabinf\n");
		affiche_liste(tabinf,ninf);
		fclose(fp);
 }

Python

De manière plus concise :

  
coeff = {
		"français":10,
		"maths":8,
		"histgeo":6
}
# le total des coefficients est 24
total_coeffs = sum(coeff.values())

# initialisation des trois tableaux
tab180=[]
tab120=[]
tabinf=[]

# ouverture et lecture du fichier eleves
with open("/tmp/eleves.txt") as f:
		L = f.readline()
		while L:
				# la ligne est découpé selon le délimiteur du tableau
				_,nom,francais,maths,histgeo,_ = L.split('|')
				# chaînes -> entiers
				francais,maths,histgeo = int(francais), int(maths), int(histgeo)
				eleve = {
						"nom":nom,
						"français": francais,
						"maths": maths,
						"histgeo": histgeo,
				}

				total = 0
				# calcul du score total
				for k in coeff.keys():
						total  += coeff[k] * eleve[k]
						# calcul de la moyenns
						moy = float(total) / float(total_coeffs)

				eleve.update( {
						"moy": moy,
						"total": total,
				})

				# tri des élèves selon les scores
				if(total>=180):
						tab180.append(eleve)
				elif(total >= 120):
						tab120.append(eleve)
				else:
						tabinf.append(eleve)

				# lecture de ligne suivante
				L = f.readline()


# une fonction d'affichage des listes
def afficher_liste(li):
		print(
				"|{0:22s}|{1:9s}|{2:5s}|{3:7s}|{4:3s}|{5:5s}|".format(
						"Nom","français","maths","histgeo","moy","total"
				))
		print("|-")
		for eleve in li:
				 print(
						 "|{0:22s}|{1:8d}|{2:5d}|{3:7d}|{4:.1f}|{5:5d}|".format(
								 eleve["nom"],
								 eleve["français"],
								 eleve["maths"],
								 eleve["histgeo"],
								 eleve["moy"],
								 eleve["total"],
						 ))
				 # affichage des trois listes
print("\ntab180")
afficher_liste(tab180)
print("\ntab120")
afficher_liste(tab120)
print("\ntabinf")
afficher_liste(tabinf)

Go

  import (
				"fmt"
				"strings"
				"io/ioutil"
				"strconv"
)

/* sépare une chaîne en un tableau de chaîne selon
	 un caractère */
func split(s string, r rune) []string {
				return strings.FieldsFunc(s,
								func(c rune) bool { return c == r })
}

type Eleve struct
{
				nom string
				notes map[string]int
				total int
				moy float32
}

var Coeff map[string]int

var tab180, tab120, tabinf []Eleve

func trim_int(s string) int {
				i, err := strconv.Atoi(strings.TrimSpace(s))
				if(err!=nil) { panic(err) }
				return i
}

func main() {

				Coeff := make(map[string]int)
				Coeff["français"] = 10
				Coeff["maths"] = 8
				Coeff["histgeo"] = 6

				var CoeffTotal = 0
				for k := range Coeff {
								CoeffTotal +=  Coeff[k]
				}

				list, _ := ioutil.ReadFile("/tmp/eleves.txt")
				line := split(string(list), '\n')
				for i := range(line) {
								fields := split(line[i], '|')
								elv := Eleve{
												nom: strings.TrimSpace(fields[0]),
												notes: map[string]int{
																"français": trim_int(fields[1]),
																"maths": trim_int(fields[2]),
																"histgeo": trim_int(fields[3])},
												moy:0,
												total:0 }

								for k := range elv.notes {
												elv.total += Coeff[k] * elv.notes[k]
								}
								elv.moy = float32(elv.total) / float32(CoeffTotal)

								if (elv.total >= 180) {
												tab180 = append(tab180, elv)
								}else if(elv.total >= 120) {
												tab120 = append(tab120, elv)
								}else{
												tabinf = append(tabinf, elv)
								}
				}

				fmt.Println("tab180")
				Affiche_Tableau(tab180)
				fmt.Println("tab120")
				Affiche_Tableau(tab120)
				fmt.Println("tabinf")
				Affiche_Tableau(tabinf)
}


func Affiche_Tableau(eleves []Eleve) {
				fmt.Printf( "|%-22s|%8s|%5s|%7s|%3s|%5s|\n",
								"Nom","français","maths","histgeo","moy","total" )
				for i := range eleves {
								fmt.Printf("|%-22s|%8d|%5d|%7d|%.1f|%5d|\n",
												eleves[i].nom,
												eleves[i].notes["français"],
												eleves[i].notes["maths"],
												eleves[i].notes["histgeo"],
												eleves[i].moy,
												eleves[i].total)
				}
}

Awk

La solution la plus succinte et la mieux adaptée au cas.

Pour retirer les confirmations d'exécution :

  (defun my-org-confirm-babel-evaluate (lang body)
	(not  (or
	 (string= lang "python")
	 (string= lang "awk")
	 (string= lang "sh"))))

(setq org-confirm-babel-evaluate 'my-org-confirm-babel-evaluate)
(org-babel-do-load-languages
 'org-babel-load-languages
 '((awk . t)))

awk-sh

En appelant awk via le shell, le résultat tient en ces quelques lignes.

  awk '
BEGIN {printf "|Nom|Français|Maths|Histoire-géo|total|moyenne|\n|-"}
 { total= 10*$3 + 8*$4 + 6*$5;
	 if (total > '$critere')
		 printf "|%s|%d|%d|%d|%d|%.1f\n", $2, $3, $4, $5, total, total/24}'\
 FS="|" $file

La numérotation du premier champ commence à 2, car les données fournissent le séparateur "|" en début de ligne. On obtient déjà une solution satisfaisante qui puisse être paramétrée

#+call: resultats-eleves(180, "/tmp/eleves.txt")

ob-awk

Ici on peut tirer parti des possibilités de babel pour indiquer les paramètres, la source de données et avoir une coloration syntaxique. Le résultat tient en 5 lignes:

  BEGIN { printf "|Nom|Français|Maths|Histoire-géo|total|moyenne|\n|-" }
 { total= 10*$3 + 8*$4 + 6*$5;
	 if (total > critere)
		 printf "|%s|%d|%d|%d|%d|%d\n", $2,$3,$4,$5, total, total/24}

Qui peut-être appelé ainsi

#+CALL: resultats-eleves(critere=155)

Pour plus de flexibilité, on peut découper et assembler le programme avec ob-awk + noweb. Le découpage avec noweb ne sera visible que dans les sources org, pas dans les exports html, qui procède déjà à l'assemblage.

  printf "|Élève|Français|Maths|Hist.Géo|Total|Moyenne\n|-";
  printf "|%s|%d|%d|%d|%d|%.1f\n", $2,$3,$4,$5, total, total/24
  total = 10*$3 + 8*$4 + 6*$5;
  BEGIN { printf "|Élève|Français|Maths|Hist.Géo|Total|Moyenne\n|-"; }
{ total = 10*$3 + 8*$4 + 6*$5;
	if (total >= critere) printf "|%s|%d|%d|%d|%d|%.1f\n", $2,$3,$4,$5, total, total/24
}
#+Call: resultats-eleves>(170)

C-c ^ SPC~ pour le tri