en C, Python, Go et Awk
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 :noweb
1org#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