in C, Python, Go and Awk
We are comparing 4 programming languages for the following problem :
On your marks
By the end of the year, a teacher wishes to evaluate the results of his pupils by weighting the marks obtained in three disciplines: french (weight 10), maths (weight 8), history & geography (weight 6). Marked over 10, the maximum sum of the weighted marks is 240.
As a result, the pupils are classified in three groups: those with at least 180, those with at least 120 and the others, who have less that 120. Is printed for each group, the name of the pupil, the marks obtained in each discipline and the average weighted mark (weighted sum divided by 24)
And here's what we can tell when comparing the 4 code variants:
- The C program is the longest as we'll have to manage data input with low-level functions.
- the easiest is Python, because Python is easy, readable and fast to program
- and the winner is
ob-awk
: most elegant, shortest and suitable solution for that kind of program.
Data
The data are in text-file, CSV-like, organized in a table like so:
| BAACH Leila | 6 | 9 | 9 |
| BARREIRO Bruno | 9 | 0 | 10 |
| COURAULT Eva | 2 | 8 | 0 |
| DELL OVA Laura | 3 | 0 | 3 |
Generating the data file
We generate the dataset with the following script
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"""
with open("/tmp/students.txt","w") as f:
for e in elv.split('\n'):
el = ("| %s | %i | %i | %i |" % (
e, *[randint(0,10) for x in range(3)]))
print(el)
f.write(el+"\n")
cat /tmp/students.txt
Plain ol'C
Pre required C functions
delete unneeded spaces in strings before integer cast
example : string " 42 " is converted to integer 42
/*
delete unneeded spaces in strings before integer cast
example : string " 42 " is converted to integer 42
*/
#include <string.h>
// characters considerer as space (there could be more than that)
#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
char *trimwhitespace(const char *str)
{
char *end;
// trim prefix
while(isspace((unsigned char)*str)) str++;
if(*str == 0)
return str;
// trim siuffix
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
// add null terminator
end[1] = '\0';
return str;
}
Split string and retrieve fields
It's non optimal for the problem at hand, but makes code more readable. A correct solution would run the tokenizer once per line.
/**
Split string into fields
**/
#include <stdlib.h>
#include <string.h>
const char* field(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;
}
C Ansi 99
The resulting program using the previous functions (the program in weaved and tangled once after an export to html).
/*
delete unneeded spaces in strings before integer cast
example : string " 42 " is converted to integer 42
*/
#include <string.h>
// characters considerer as space (there could be more than that)
#define isspace(c) ((c)==' ' || (c)=='\t' || (c)=='\n')
char *trimwhitespace(const char *str)
{
char *end;
// trim prefix
while(isspace((unsigned char)*str)) str++;
if(*str == 0)
return str;
// trim siuffix
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
// add null terminator
end[1] = '\0';
return str;
}
#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
coeff = {
"français":10,
"maths":8,
"histgeo":6
}
# total coefficients is 24
total_coeffs = sum(coeff.values())
# initialize 3 arrays
tab180=[]
tab120=[]
tabinf=[]
# open and read the arrays
with open("/tmp/students.txt") as f:
L = f.readline()
while L:
fields = {"nom":""}
# line is split according the array delimiter
nom,francais,maths,histgeo = L.split('|')[1:-1]
#nom,francais,maths,histgeo = L.split('|')[1:-1]
# marks are cast to int
francais,maths,histgeo = int(francais), int(maths), int(histgeo)
eleve = {
"nom":nom,
"français": francais,
"maths": maths,
"histgeo": histgeo,
}
total = 0
# total score
for k in coeff.keys():
total += coeff[k] * eleve[k]
# compute means
moy = float(total) / float(total_coeffs)
eleve.update( {
"moy": moy,
"total": total,
})
# sort according the scores
if(total>=180):
tab180.append(eleve)
elif(total >= 120):
tab120.append(eleve)
else:
tabinf.append(eleve)
# read next line
L = f.readline()
# display
def display_list(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")
display_list(tab180)
print("\ntab120")
display_list(tab120)
print("\ntabinf")
display_list(tabinf)
Go
import (
"fmt"
"strings"
"io/ioutil"
"strconv"
)
// split a string according to a delimiter in a array
func split(s string, r rune) []string {
return strings.FieldsFunc(s, func(c rune) bool { return c == r })
}
type Student struct
{
name string
marks map[string]int
total int
mean float32
}
var Coeff map[string]int
var tab180, tab120, tabinf []Student
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["french"] = 10
Coeff["maths"] = 8
Coeff["histgeo"] = 6
var CoeffTotal = 0
for k := range Coeff {
CoeffTotal += Coeff[k]
}
list, _ := ioutil.ReadFile("/tmp/students.txt")
line := split(string(list), '\n')
for i := range(line) {
fields := split(line[i], '|')
stud := Student{
name: strings.TrimSpace(fields[0]),
marks: map[string]int{
"french": trim_int(fields[1]),
"maths": trim_int(fields[2]),
"histgeo": trim_int(fields[3])},
mean:0,
total:0 }
for k := range stud.marks {
stud.total += Coeff[k] * stud.marks[k]
}
stud.mean = float32(stud.total) / float32(CoeffTotal)
if (stud.total >= 180) {
tab180 = append(tab180, stud)
}else if(stud.total >= 120) {
tab120 = append(tab120, stud)
}else{
tabinf = append(tabinf, stud)
}
}
fmt.Println("tab180")
Affiche_Tableau(tab180)
fmt.Println("tab120")
Affiche_Tableau(tab120)
fmt.Println("tabinf")
Affiche_Tableau(tabinf)
}
func Affiche_Tableau(eleves []Student) {
fmt.Printf( "|%-22s|%8s|%5s|%7s|%3s|%5s|\n",
"Name","french","maths","histgeo","moy","total" )
for i := range eleves {
fmt.Printf("|%-22s|%8d|%5d|%7d|%.1f|%5d|\n",
eleves[i].name,
eleves[i].marks["français"],
eleves[i].marks["maths"],
eleves[i].marks["histgeo"],
eleves[i].mean,
eleves[i].total)
}
}
Awk
add awk to ob-babel,
and run without asking for confirmation when pressing C-c C-c
(defun my-org-confirm-babel-evaluate (lang body)
(not (or
(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
Here the variable $criteria
will make
a problematic travel as org > sh > awk.
180 ends hard coded.
awk '
BEGIN {printf "|Name|French|Maths|Hist.geo|total|moyenne\n|-"}
{ total= 10*$3 + 8*$4 + 6*$5;
if (total > 180)
printf "|%s|%d|%d|%d|%d|%d\n", $2, $3, $4, $5, total, total/24}'\
FS="|" OFS="|" /tmp/students.txt
Here is it cleaner, with IFS and OFS variable as preamble
BEGIN {printf "|Nom|French|Maths|Hist.geo|total|moyenne\n|-"}
{
total= 10*$2 + 8*$3 + 6*$4;
if (total > critere)
printf "|%s|%d|%d|%d|%d|%d\n", $1, $2, $3, $4, $5, total, total/24
}
Ob-awk
With ob-awk
and noweb
,
here's how to assemble the program in a proper way.
Declare the header
printf "|Student|French|Maths|Hist.Geo|Total|Average\n|-\n";
Declare field separator
FS="\t";OFS="|"
The display function for one line
printf "|%s|%d|%d|%d|%d|%.1f\n", $1,$2,$3,$4, total, total/24
Total computation
total = 10*$2 + 8*$3 + 6*$4;
And the final program, modularized with noweb
and with a default variable.
It can also be called like so #+Call: upper(120)
BEGIN { printf "|Student|French|Maths|Hist.Geo|Total|Average\n|-\n"; FS="\t";OFS="|" }
{ total = 10*$2 + 8*$3 + 6*$4;
if (total >= limit) printf "|%s|%d|%d|%d|%d|%.1f\n", $1,$2,$3,$4, total, total/24
}
Here's an other variant with an interval
BEGIN { printf "|Student|French|Maths|Hist.Geo|Total|Average\n|-\n"; FS="\t";OFS="|" }
{ total = 10*$2 + 8*$3 + 6*$4;
if (total > 120 && total < 180) printf "|%s|%d|%d|%d|%d|%.1f\n", $1,$2,$3,$4, total, total/24
}
C-c ^ SPC
to sort
Checking the results
Finally, resulting arrays should be checked to ensure the results are correct.