# Created 2024-02-04 dim. 23:56
#+title: 7d.nz
#+author: Phil. Estival
* • [2019-03-04 lun.] Bilan des résultats des élèves                :code:fr:
#+results: 

en C, Python, Go et Awk

Traduction en anglais

file:./img/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 :noweb[fn::org#Noweb Reference Syntax]
pour faire les inclusions de sources.
Quelques vérifications préalables
s'imposent pour s'assurer que tout fonctionne
*** test 1
#+name: test1
#+begin_src C
  int c;
  c=42;
  int test() {
   return 1;
  }
  printf("%i\n",c);
#+end_src

#+results: 
: 42


#+begin_src C
  #include <stdio.h>
  printf("%i\n",43);
  test();
#+end_src
#+begin_src C
  #include "stdlib.h"
  #include "stdio.h"

  <<test1>>
#+end_src

*** test 2
#+name: srcMyfunc
#+begin_src C
  void myfunc() {
    printf("print from function\n");
  }
#+end_src
#+name: srcMain
#+begin_src C
  int main(int argc,char **argv) {
    printf("Hello World\n");
    myfunc();
    exit(0);
  }
#+end_src

#+begin_src C
  #include "stdlib.h"
  #include "stdio.h"

  <<srcMyfunc>>
  <<srcMain>>
#+end_src

** Données
Les données sont dans un fichier texte organisé en tableau
come dans l'exemple suivant.
#+begin_src text
  | BAACH Leila          |  6 |  9 |  9 |
  | BARREIRO Bruno       |  9 |  0 | 10 |
  | COURAULT Eva         |  2 |  8 |  0 |
  | DELL OVA Laura       |  3 |  0 |  3 |
#+end_src

** 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.
#+name: TestSet
#+begin_src python
  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")
#+end_src

#+name: notes-eleves
#+begin_src sh
  cat /tmp/eleves.txt
#+end_src

** 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.
#+name: trimwhitespace
#+begin_src C
  /*
    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;
  }
#+end_src

**** Scinder une chaîne et récupérer les champs
#+name: champs
#+begin_src C
  /**
  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;
   }
#+end_src

*** test C
#+name: test
#+begin_src 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);
   }
#+end_src

#+results: test
#+begin_example
  BAACH Leila  7 8 6
  BARREIRO Bruno  6 9 4
  COURAULT Eva  2 6 9
  DELL OVA Laura  10 6 7
  DUGUET Pierre  6 0 8
  ESCANDE Thomas  3 10 7
  GATTO Marianne  7 1 4
  GUIRAUD Sarah  10 7 5
  LHEUILLIER Margaux  8 9 5
  LONGO Tania  5 4 7
  MARTIN Caroline  6 8 9
  MARTY Lea  3 3 2
  MATTIA Louis  3 10 1
  MORIN Jules  8 0 6
  PASCALE Eva  5 10 7
  PAYSSERAND Laura  0 0 3
  RIBEIRO SOCORRO Jade  8 0 3
  ROMERO Bastien  3 8 7
  ROTY Elodie  4 5 9
  RUTIGLIANO Marine  5 6 1
  SAFOURCADE Morgane  1 2 9
  ZOUINI Nada  4 8 5
#+end_example

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

#+name: BilanC
#+begin_src C
  <<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);
   }
#+end_src
#+attr: :align center

** Python
De manière plus concise :
#+name: BilanPython
#+begin_src python

  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)
#+end_src

** Go
#+name: BilanGo
#+begin_src 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)
          }
  }
#+end_src

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

Pour retirer les confirmations d'exécution :
#+begin_src elisp
  (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)))
#+end_src
*** awk-sh

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

#+name: resultats-eleves
#+begin_src sh
  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
#+end_src

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

#+results: 

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:
#+name: resultats-élèves
#+begin_src 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|%d\n", $2,$3,$4,$5, total, total/24}
#+end_src


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.

#+name: header-notes
#+begin_src awk
  printf "|Élève|Français|Maths|Hist.Géo|Total|Moyenne\n|-";
#+end_src
#+name: print-line
#+begin_src awk
  printf "|%s|%d|%d|%d|%d|%.1f\n", $2,$3,$4,$5, total, total/24
#+end_src
#+name: note-totale
#+begin_src awk
  total = 10*$3 + 8*$4 + 6*$5;
#+end_src
#+name: resultats-eleves>
#+begin_src awk
  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
  }
#+end_src

: #+Call: resultats-eleves>(170)


C-c ^ SPC~ pour le tri