C'est pour un sondage ?
Oui, que maintenant Flodelarab a donné la solution pour calculer la moyenne
------
EDIT: déjà la page N° 3 et j'ai pas encore vu la moindre once de code :roll:
Version imprimable
Si y a égalité, on prend le premier de la liste du groupe. :(
Ca ressemble vachement à un mec qui attend qu'on lui code sa solution. Donc oui, j'ai une proposition. Je propose que tu décortiques ce qu'on t'a dit (comme tu l'as annoncé récemment). Puis ensuite je propose que tu nous montres ton code (un code qui donne le bon résultat évidemment, ou au pire qui ne donne pas forcément le bon résultat mais qui montre au-moins que tu sais récupérer l'info qui va bien de ta ligne de fruits et la soustraire à la moyenne précédemment calculée). Et là enfin on te proposera des possibilités de correction/optimisation/simplification.
Bonjour ;), voici
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #moyennes.txt #1 7.8 #2 4.75 #!/bin/bash grep '1 ' groupe.txt > temp2 awk '{ print $2 } ' temp2 > temp3 #temp3 # 1 8 Orange # 1 6.5 Banane # 1 6.2 Pomme # 1 12 Abricot # 1 7 Mure a=($(cat temp3 )) b=($(cat moyennes.txt )) minim=$(expr ${a[0]} - ${b[1]}) # comparaison de la première différence avec la moyenne for i in $a; do # pour chaque valeur de la liste if [ i - "${b[1]}" <= $minim ]; then # si la différence de i avec la moyenne est inférieur à la première différence alors minim devient le difference testée $minim=i fi echo $i done
non.
pas grep puis awk, surtout sur des fichiers temporaires.
expr est une programme externe inutile en bash.
on ne fait pas une boucle for sur le contenu d'un fichier; un fichier se lit avec une boucle while.
pour mettre le contenu d'un fichier dans un tableau, il y a mapfile (aka readarray).
tu n'as évidemment pas testé ton script
$a n'est pas le contenu du tableau, ce n'en est que le premier élément, équivalent à "${a[0]}",
ne peut pas fonctionner :Code:[ i - "${b[1]}" <= $minim ]
- il n'y a pas de contexte arithmétique, donc valX - valY ne donnera pas le résultat espéré,
- i ne sera pas développé come pourrait l'être $i,
- <= n'est pas une opérateur arithmétique,
- pourquoi uniquement ${b[1]} ?
Bon, au moins c'est un code. Mais il n'y a pas grand chose à en tirer. Mais j'admets que c'est de ma faute, j'ai demandé un code, pas un code qui fonctionne (petit détail qui fait toute la différence).
Tu ne voudrais pas d'abord apprendre le bash? Je t'assure, pour programmer en bash ce serait un plus...
Déjà les fichiers temporaires sont rarement nécessaires. Mais s'ils le sont, on les met dans "/tmp" qui est justement "le" répertoire qui leur est dédié. Ca évite d'en foutre de partout. Mais ça c'était juste le peanut par rapport au gros de ce programme.
Prenons par exemple a=($(cat temp3 )). Que contient "a"? Le résultat d'une commande, ici cat temp3. Donc visiblement tu sais mettre une commande dans une variable.
Et que contient "temp3"? Lui aussi une commande, ici awk '{ print $2 } ' temp2 >temp3. Tu aurais pu donc la mettre directement dans "a".
Et que contient "temp2"? Lui aussi une commande, ici grep '1 ' groupe.txt >temp2. Tu le sens où je veux en venir?
Et vu que "temp2" n'est pas réutilisé, il peut disparaitre. "temp3" peut donc s'écrire grep '1 ' groupe.txt |awk '{ print $2 } ' >temp3.
Mais vu que "temp3" sert uniquement à remplir "a", lui aussi il peut disparaitre. Et "a" devient alors a=($(grep '1 ' groupes.txt |awk '{ print $2 }')). Et si tu apprends le langage "awk" (ok, un autre langage à apprendre mais c'est inévitable) alors tu peux lui insérer l'instruction qui remplace le grep. Remarque vu qu'elle n'est pas évidente à retenir si tu laisses le grep je n'en ferai pas une maladie. S'il n'y avait que ça qui n'allait pas dans ton script...
Pour le reste... for i in $a s'écrira plutôt for i in ${a[@]} (ben oui, tu as créé un tableau, maintenant il faut t'en servir). Puisque tu sais qu'une variable s'obtient en lui mettant "$" devant on se demande pourquoi tu ne le fais pas pour "i" quand tu calcules la différence ; qu'un calcul étant une expression ben il doit s'écrire dans la syntaxe des expressions ; qu'on ne compare pas une inégalité avec "<=" mais "-le" ; et qu'on ne met pas de "$" devant une variable quand on lui affecte une valeur (tu ne l'as pas fait pour "a", on se demande pourquoi tu le fais pour "minim")
Que veux-tu qu'on fasse? Qu'on te réécrive ici tout un cours de bash? En plus il y a justement un lien vers un tutoriel bash dans ma signature (ok il est un peu vieux et les dernières technologies n'y sont pas mais déjà si tu ne fais que le lire tu auras fait quand-même un immense bon en avant). Sinon t'es pas obligé d'utiliser le mien mais t'es quand-même un petit peu obligé d'en utiliser un quel qu'il soit.
Sve@r, si tu ne veux pas m'aider, c'est ok. Mais si c'est pour m'engueuler comme si t'étais mon père, laisses carrément tomber.
Je fais un effort et on me prette de mauvaises intentions.
Y a d'autres problèmes sur le forum très certainement plus intéressantes que celui-ci...
On n'a visiblement pas la même conception du verbe "aider". Parce qu'avec toutes les infos que que je t'ai donné sur comment lire un fichier ligne par ligne, comment extraire une info particulière d'une ligne, comment éviter les fichiers temporaires, l'opérateur à utiliser pour comparer deux nombres, venir dire que je ne veux pas t'aider c'est carrément de la mauvaise foi. Maintenant si pour toi "aider" signifie "te faire ton code" évidemment on ne s'entendra pas (ni avec moi, ni avec personne) :?
Je t'ai pas engueulé, je t'ai dit de lire un tuto. Tu crois que c'est déshonorant? Tu crois que moi je n'en lis pas chaque fois que je veux apprendre un truc? Tu penses vraiment que la syntaxe shell va monter en toi par capilarité?
Le reste (mettre parfois dollar quand c'est inutile, ne pas le mettre quand c'est utile) c'était juste te mettre le nez dans tes incohérences. Tu aurais voulu quoi à la place? Que je te dise "ok c'est super" alors que ton programme ne s'exécute même pas tellement il est rempli d'erreurs de syntaxe à chaque ligne??? Je ne suis pas là pour faire du social, mais pour te faire progresser en shell. Je te remercie pour le -1, j'apprécie. Fais du bien à Bertrand, te répond en caguant. Heureusement N_BaH (:chin:) a rétabli l'équilibre.
Toi laisse carrément tomber si tu ne veux pas apprendre.
Justement, c'est là l'erreur; il faut en faire beaucoup. Tu as décortiqué les posts précédents comme tu l'avais annoncé ?
:koi:
c'est pas de l'aide ça ?Citation:
Prenons par exemple a=($(cat temp3 )). Que contient "a"? Le résultat d'une commande, ici cat temp3. Donc visiblement tu sais mettre une commande dans une variable.
Et que contient "temp3"? Lui aussi une commande, ici awk '{ print $2 } ' temp2 >temp3. Tu aurais pu donc la mettre directement dans "a".
Et que contient "temp2"? Lui aussi une commande, ici grep '1 ' groupe.txt >temp2. Tu le sens où je veux en venir?
Et vu que "temp2" n'est pas réutilisé, il peut disparaitre. "temp3" peut donc s'écrire grep '1 ' groupe.txt |awk '{ print $2 } ' >temp3.
Mais vu que "temp3" sert uniquement à remplir "a", lui aussi il peut disparaitre. Et "a" devient alors a=($(grep '1 ' groupes.txt |awk '{ print $2 }')). Et si tu apprends le langage "awk" (ok, un autre langage à apprendre mais c'est inévitable) alors tu peux lui insérer l'instruction qui remplace le grep. Remarque vu qu'elle n'est pas évidente à retenir si tu laisses le grep je n'en ferai pas une maladie. S'il n'y avait que ça qui n'allait pas dans ton script...
Pour le reste... for i in $a s'écrira plutôt for i in ${a[@]} (ben oui, tu as créé un tableau, maintenant il faut t'en servir). Puisque tu sais qu'une variable s'obtient en lui mettant "$" devant on se demande pourquoi tu ne le fais pas pour "i" quand tu calcules la différence ; qu'un calcul étant une expression ben il doit s'écrire dans la syntaxe des expressions ; qu'on ne compare pas une inégalité avec "<=" mais "-le" ; et qu'on ne met pas de "$" devant une variable quand on lui affecte une valeur (tu ne l'as pas fait pour "a", on se demande pourquoi tu le fais pour "minim")
remercie Sve@r, c'est lui qui l'a écrite.
mais ${b[@]} ne contient pas de moyennes, simplement les valeurs du groupe 1. :weird:
${b[1]} issue de [b=($(cat moyennes.txt )) est la première moyenne.
ah, oui ! :oops:
par contre, moyenne.txt n'est pas explicité dans le script, d'où ma confusion.
mais le premier élément d'un tabeau n'est pas 1, mais 0.
Valeur du tableau v pour la clé i, multiplié par 1.
mais pourquoi ? 8O
Il existe deux divisions.
- La division entière. Ou division euclidienne.
- La division réelle.
7 divisé par 2 donne 3 dans le premier cas et 3.5 dans le second cas. (la division euclidienne donne 7 divisé par 2 égale 3 reste 1, pour être précis).
Par suite, il y a 3 sortes de langages de programmation:
- Les langages qui ont compris qu'il y avait un problème et qui ont explicitement deux opérateurs.
- Les langages typés qui font la division entière avec les entiers et la division réelle avec les nombres réels.
- Les langages qui n'ont rien compris et qui vous laisse dans la mouïse.
Tu peux être sûr que des tas de logiciels sont bugués car le programmeur croit faire une division alors qu'il fait l'autre.
Dans le second cas, celui des logiciels qui sélectionnent de façon implicite la division, il faut multiplier par 1.0 avant de diviser pour basculer dans les nombres à virgule, donc assortis de la division réelle. Sinon, il fait l'entière.
Comme awk est du genre à faire les choses implicitement, même si je ne me souviens plus de la division présente, il ne coûte rien de multiplier par 1,0.
D'ailleurs, awk est un sacré piège pour l'implicite entre le texte et les nombres. Je me suis déjà fait avoir plusieurs fois.
J'ose espérer que la majorité des programmeurs (de ceux qui ont le niveau adéquat pour travailler sur des logiciels) connait ce souci d'une part et sait comment le langage qu'il utilise gère la division d'autre part. Sans compter les tests unitaires sur les fonctions qui permettent généralement de repérer ce genre d'erreur.
En réalité sur ce point précis il fait les choses bien.
Code:
1
2
3
4
5
6
7
8 ~$ awk 'BEGIN {n=7/2; print n}' 3,5 ~$ awk 'BEGIN {n=int(7)/2; print n}' 3,5 ~$ awk 'BEGIN {n=7/int(2); print n}' 3,5 ~$ awk 'BEGIN {n=int(7)/int(2); print n}' 3,5
Merci beaucoup, mais je pense changer de stratégie est prendre la valeur la plus basse pour chaque groupe
tant que j'en suis à donner des solutions :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 #!/bin/bash fGroups=groupes.txt evalMoyN() { bc -l <<eof define abs(digit){ if (digit<0) return -digit; return digit } abs($1-$2) < abs(${3:-0}-$2) eof } #calcul des totaux declare -ai nb while read gr vl fr do [[ $gr != '#'* && $gr != '' ]] && { vl=${vl//,/.} nb[$gr]+=1 groups[$gr]=$(bc -l <<<"${groups[gr]:-0}+$vl") } done <"$fGroups" # calcul des moyennes for gr in ${!groups[@]} do moyN[$gr]=$(bc -l <<< "scale=2;${groups[gr]}/${nb[gr]}") done #calcul de la valeur la plus proche de la moyenne while read gr vl fr do [[ $gr != '#'* && $gr != '' ]] && { vl=${vl//,/.} (( $(evalMoyN $vl ${moyN[gr]} ${k[gr]}) )) && { k[$gr]="$vl" K[$gr]="$gr $vl(/${moyN[gr]}) $fr" } } done <"$fGroups" #affichage des résultats printf '%s\n' "${K[@]}"
tant que j'en suis à proposer des solutions :Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 #!/bin/bash fGroups=groupes.txt # tel que défini ici : https://www.developpez.net/forums/d2135755/systemes/linux/shell-commandes-gnu/extraire-moyenne-bash/#post11861435 evalMoyN() { bc -l <<eot define abs(digit){ if (digit<0) return -digit; return digit } abs($1-$2) < abs(${3:-0}-$2) eot } #calcul des totaux declare -ai nb while read gr vl fr do [[ $gr != '#'* && $gr != '' ]] && { vl=${vl//,/.} nb[$gr]+=1 groups[$gr]=$(bc -l <<<"${groups[gr]:-0}+$vl") } done <"$fGroups" # calcul des moyennes for gr in ${!groups[@]} do moyN[$gr]=$(bc -l <<< "scale=2;${groups[gr]}/${nb[gr]}") done #calcul de la valeur la plus proche de la moyenne while read gr vl fr do [[ $gr != '#'* && $gr != '' ]] && { vl=${vl//,/.} (( $(evalMoyN $vl ${moyN[gr]} ${k[gr]}) )) && { k[$gr]="$vl" K[$gr]="$gr $vl(/${moyN[gr]}) $fr" } } done <"$fGroups" #affichage des résultats printf '%s\n' "${K[@]}"
Le titre dit explicitement en bash et à ma connaissance bc n'est pas du bash :aie:
EOF (End Of File) c'est pour un fichier. Moi je préconise "EOT" (End Of Text) et en majuscules plus des underscores pour que ça ressorte bien en évitant toute confusion possible => bc <<_EOT_ 8-)
T'as raison :mrgreen:
:wow:Code:
1
2
3
4
5
6
7
8 #!/bin/bash cherche() { awk -v g=$1 'BEGIN {n=0} {if ($1 != g) next; v[n]=$2; f[n]=$3; n++} END {m=0; for (i=0; i < n; i++) m+=v[i]; c=m; m/=n; for (i=0; i < n; i++) {d=v[i]-m; if (d<0) d=-d; if (d < c) {c=d; r=f[i]}}; print r}' } for g in "$@"; do grep "^[^#]" "fic" |cherche $g done
Testé sur ce fichier (nommé "fic" chez-moi)
Je l'ai remis car j'ai rectifié une petite erreur sur le fichier d'origine qui était écrit 1 6, 2 Pomme avec un espace à la con devant le "2" (ou alors ce n'est pas une erreur et le script doit "en plus" la détecter et la corriger...:?)Code:
1
2
3
4
5
6
7
8
9
10
11 #Groupe Valeur Fruit 1 8 Orange 1 6,5 Banane 1 6,2 Pomme 1 12 Abricot 1 7 Mure 2 4 Pomme 2 6 Banane 2 6 Abricot 2 3 Mure
Résultat
:king:Code:
1
2
3
4 ~$ ./x.sh 1 Orange ~$ ./x.sh 2 Pomme
@Sve@r
ah, non ! pas grep |awk !
EOF -> EOT : modifié.
@diesdorgue
bc : j'avais prévenu que ce que ne peut pas faire bash sans tortures est une transgression acceptable.
mais, dura lex sed lex, je dois pouvoir adapter ce que j'ai fait pour la valeur la plus basse.
Ce n'est pas tout à fait grep |awk, c'est grep |une_fonction. Et si on respecte le principe d'opacité des fonctions, on n'est pas censé savoir qu la fonction fait un "awk" (d'ailleurs ça peut changer demain).
En fait j'ai voulu écrire la fonction comme pouvant traiter n'importe quel flux en provenance de stdin pour plus de souplesse (ainsi elle ne se restreint pas à un fichier mais à n'importe quoi en input). D'ailleurs ma première version de la boucle était cat "fic" |cherche $g et ça fonctionne puisque le awk ne cherche que les lignes contenant le groupe demandé.
Puis je me suis dit que pour ce cas précis, si le flux entrant était déjà nettoyé au préalable des lignes en commentaire ce serait un plus.
Ah, ça c'est gentil :chin:
et allez ! de la mauvaise par-dessus.
8O :cfou: :rouleau:Citation:
[...]ma première version de la boucle était cat "fic" |cherche $g
ça t'apprendra peut-être à soutenir les intégristes de bash ::mouarf: :ptdr:Citation:
Envoyé par Sve@r
:chin:Citation:
:chin:
:oops: `connais pas.Citation:
Et si on respecte le principe d'opacité des fonctions
C'est ce qu'on nomme "boite noire" (toutefois c'est plutôt appliqué en POO). On te donne un objet, on te dit "il prend X en entrée et donne Y en sortie" et tu n'as pas besoin d'en savoir plus ni comment c'est codé en interne, et surtout pas y fourrer ton nez. Ainsi le concepteur de l'objet a toute latitude pour le faire évoluer comme il le sent en fonction des avancées technologiques sans avoir à te rendre des comptes ; tant qu'il respecte ses obligations d'accepter X en entrée et donner Y en sortie.
Ensuite de ton côté, ne sachant pas comment le truc fonctionne, tu peux être tenté de vouloir l'aider en lui fournissant juste ce dont tu penses qu'il aura besoin, d'où le grep. Remarque dans ce cas, un grep "^ $g " "fic" était encore plus efficace. Mais bon, j'ai fait ça à la "va vite" (déjà rien que mes noms de variables dans le awk...) dans l'humour, parce que disedorgue demandait un bash pur, j'ai trouvé marrant de le lui mettre un awk (et j'ai même envisagé Python) 8-)
ah, oui. je connais le concept de "boîte noire", mais ça ne me serait pas venu à l'idée de l'appliquer ici, parce que je vois très bien ce qu'il y a à l'intérieur. :)
Je suis complètement en ligne derrière N_BaH. J'ai eu la même réaction. Sve@r, tu recrées de façon alambiquée la séparation entre le filtre (grep) et l'action (awk), alors que awk sait faire les deux. :)
Quant à la boîte noire, les scripts shell sont tout, sauf ça. On n'est plutôt dans un grand terrain vague. Les variables ne sont pas locales. si j'appelle var=toto ./monscript.bash et si monscript.bash appelle lui-même ./fils.bash, alors fils.bash peut utiliser la variable $var qui n'est définie nulle-part dans les scripts (père ou fils). D'autre part, si tu fais de la récursivité, tu vas avoir des problèmes si tu présupposes les variables indépendantes pour chaque occurrence de la fonction.
Comme je l'ai dit, le grep reste facultatif (ça fonctionne aussi sans mais comme le disait quelqu'un, si cela va sans dire, cela va encore mieux en le disant ;)) et surtout c'était un code humoristique. Personnellement j'essaye toujours d'éviter awk qui est assez consommateur (mais ce n'est pas pour ça que je me l'interdis, s'il le faut, il le faut). Et encore plus personnellement, ça fait longtemps que je ne fais plus de bash car mon travail m'a orienté vers d'autres technos (Python/PyQt/Postgres). J'en fais encore bien entendu, mais à un niveau très basique. Par exemple si j'avais eu ce travail à faire, jamais je ne l'aurais fait en bash (Python me semble plus adéquat) et s'il faut vraiment le faire en shell, je n'hésiterais pas à utiliser les outils disponibles dans le monde Linux (grep, cut, etc).
Je connais cette subtilité (j'ai effectivement tenté la récursivité et rapidement compris le souci)
Code:
1
2
3
4
5
6
7
8
9
10
11
12 fct1() { ( ... (toute variable définie ici est indépendante)... ) } fct2() { local x=1 local y=2 local z=3 ... }
Et c'est moi l'intégriste : https://www.developpez.net/forums/d2.../#post11866063
:ptdr:
:chin:
En plus dans la vraie vie, je fais du python/sql/perl mais pas de shell...:mrgreen:
Les "intégristes" awk peuvent aussi s'amuser… 8-)
https://github.com/patsie75/awk-demo
deuxième service !edit : déplacement de local sep dans rendreEntier()Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 #!/bin/bash fGroups=groupes.txt abs() { echo $(($1<0?-$1:$1)) } rendreEntier() { local n=$1 sep=${n//[0-9]/} test -z "${sep:=,}" && n="$n,0" case $sep in .) LC_NUMERIC=C printf -v n '%0.3f' $n;; ,) printf -v n '%0.3f' $n;; esac echo ${n//$sep} } evalMoyN() { echo $(( $(abs $(($1-$2))) < $(abs $(($3-$2))) )) } #calcul des totaux declare -ai nb groups while read gr vl fr do [[ $gr != '#'* && $gr != '' ]] && { vl=${vl//,/.} nb[$gr]+=1 groups[$gr]+=$(rendreEntier $vl) } done <"$fGroups" #calcul des moyennes for gr in ${!groups[@]} do moyN[$gr]=$(( ${groups[gr]}/${nb[gr]} )) done #calcul de la valeur la plus proche de la moyenne while read gr vl fr do [[ $gr != '#'* && $gr != '' ]] && { vl=${vl//,/.} (( $(evalMoyN $(rendreEntier $vl) ${moyN[gr]} $(rendreEntier ${k[gr]:-0})) )) && { k[$gr]="$vl" K[$gr]="$gr $vl(/${moyN[gr]:: -3},${moyN[gr]: -3}) $fr" } } done <"$fGroups" #affichage des résultats printf '%s\n' "${K[@]}"
Sans être intégriste :ptdr::ptdr::ptdr: je trouve ça beau
:hola:
:question: A quoi sert le local sep dans la fonction evalMoyN :question:
A créer une variable "sep" locale à la fonction :mrgreen:
Nan, je déconne, moi aussi cette ligne m'a surpris mais je pense que c'est une erreur de copier/coller. En revanche je ne pige pas pourquoi cette autre variable "sep" n'est pas mise en local dans la fonction "rendreEntier".