Le « Plus / Moins » le plus horrible du siècle !

a marqué ce sujet comme résolu.

L'utilisation de setjmp et longjmp n'est pas recommandée pour la même raison que les goto, ils rendent les programmes difficiles à comprendre, de plus il est très facile de se retrouver avec des bugs vraiment pénibles à corriger.

Souksouk

Et encore, les goto sont utiles pour la gestion d'erreurs (en C bien sûr) et dans quelques très rares cas, alors que longjmp - setjmp n'ont pas vraiment d'intérêt en C, à la limite des coroutines / système d'exceptions mais bon…

+0 -0

Salut,

Un petit code en C (oui, encore un). Ce dernier utilise un appel récursif à la fonction main() et n'utilise que argc et argv comme variables. Les abus :

  • récursion avec la fonction main() (ce qui n'est pas interdit, mais pas franchement conseillé) ;
  • conversions entre pointeurs et entiers ;
  • utilisation des déclarations implicites de fonctions ;
  • utilisation d'une partie de l'adresse d'une variable comme argument de srand() ;
  • absence d'une valeur de retour pour la fonction main() ;
  • non vérifications en tout genre (et le programme foire si on lui fourni au moins un argument :p ).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int
main(int argc, char **argv)
{
    if (argv[1] == 0) {
        srand(*((unsigned char *)argv + 3));
        argv[1] = (char *)(rand() % 100);
    } else if (argc < (int)argv[1])
        printf("Plus grand !\n");
    else if (argc > (int)argv[1])
        printf("Plus petit !\n");
    else {
        printf("Bravo ! Vous avez trouvé le nombre mystère\n");
        exit(0);
    }

    printf("Entrez un nombre [0-99] : ");
    main((scanf("%d", &argc), argc), argv);
}
+2 -0

Je l'ai fait en fortran. Comme le faire en fortran 77 (les goto, c'est normal) aurait été tricher, je l'ai fait en fortran 2003, avec des objets. :P

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
module m_PM
    type comparateur
        integer :: nombre
        logical :: plus
        logical :: moins
    contains
            procedure :: est_ce_plus => est_ce_plus_ou_non
            procedure :: est_ce_moins
    end type

contains
    subroutine est_ce_plus_ou_non(a, b)
        class(comparateur), intent(inout) :: a
        integer, intent(inout) :: b
        
        a%plus = (a%nombre > b)
    end subroutine
    subroutine est_ce_moins(a, b)
        class(comparateur), intent(inout) :: a
        integer, intent(inout) :: b
        
        a%moins = (a%nombre<b)
    end subroutine


    subroutine init_random_seed()
        use iso_fortran_env, only: int64
        implicit none
        integer, allocatable :: seed(:)
        integer :: i, n, un, istat, dt(8), pid
        integer(int64) :: t
        
        call random_seed(size = n)
        allocate(seed(n))
        call system_clock(t)
        call date_and_time(values=dt)
        t = (dt(1) - 1970) * 365_int64 * 24 * 60 * 60 * 1000 &
            + dt(2) * 31_int64 * 24 * 60 * 60 * 1000 &
            + dt(3) * 24_int64 * 60 * 60 * 1000 &
            + dt(5) * 60 * 60 * 1000 &
            + dt(6) * 60 * 1000 + dt(7) * 1000 &
            + dt(8)
        pid = getpid()
        t = ieor(t, int(pid, kind(t)))
        do i = 1, n
            seed(i) = lcg(t)
        end do
        call random_seed(put=seed)
        contains
        ! This simple PRNG might not be good enough for real work, but is
        ! sufficient for seeding a better PRNG.
        function lcg(s)
            integer :: lcg
            integer(int64) :: s
            if (s == 0) then
                s = 104729
            else
                s = mod(s, 4294967296_int64)
            end if
            s = mod(s * 279470273_int64, 4294967291_int64)
            lcg = int(mod(s, int(huge(0), int64)), kind(0))
        end function lcg
    end subroutine init_random_seed

end module

program p_PM
    use ISO_FORTRAN_ENV
    use m_PM

    integer :: entier
    type(comparateur) :: objet
    real :: flottant_aleatoire
    integer, dimension(1) :: temps

    call init_random_seed()
    call random_number(flottant_aleatoire)
    objet%nombre = int(flottant_aleatoire * 100)
    
    do while (.true.)
        write(OUTPUT_UNIT,'("Entrez un nombre entre 0 et 100")')
        read(INPUT_UNIT,*) entier
        call objet%est_ce_plus(entier)
        call objet%est_ce_moins(entier)
        if (objet%plus) then
            write(OUTPUT_UNIT, '("C est trop petit")')
        end if
        if (objet%moins) then
            write(OUTPUT_UNIT, '("C est trop grand")')
        end if
        if (.not. (objet%moins) .and. .not. (objet%plus)) then
            stop "C'est ça !"
        end if
    end do
end program

C'est laid pour pas mal de raison :

  • le module et le programme dans le même fichier (c'est comme faire du C sans .h, avec toute les fonctions dans le même fichier) ;
  • procedure :: est_ce_plus => est_ce_plus_ou_non signifie que la classe a une procédure (c'est presque comme une fonction) qui s'appelle est_ce_plus MAIS qui sera définie ensuite sous le nom est_ce_plus_ou_non. Ça peut être utile dans le cas des surcharge (avec des interfaces et compagnie), mais dans ce contexte, c'est totalement hors de propos (la classe entière d'ailleurs) ;
  • end type, ce serai mieux de faire end type comparateur. Mais ce n'est pas très grave ;
  • les noms de variable (a, b) des subroutine, classique mais efficace ^^ ;
  • integer, intent(inout) :: b signifie que b devra être lu, et pourra être modifié. Sauf qu'il ne sera pas modifié. Donc j'aurai dû mettre in ;
  • Le % est normal. C'est équivalent à a.plus de presque tout les autres langages ;
  • toute la subroutine init_random_seed est normal ! J'en suis le premier sidéré, mais c'est la procédure recommandée par le manuel de gfortran pour faire une initialisation qui change à chaque exécution. Je n'ai pas trouvé de solution plus courte. J'en reste o_O ;
  • dans le programme, pas de déclaration implicit none ;
  • l'aléatoire est fait n'importe comment (tirage d'un flottant transformé en entier plutôt que d'un entier directement) ;
  • do while (.true.) et une boucle infinie, une !
  • write(OUTPUT_UNIT,'("TEXTE")') est équivalent à write(*,*) "TEXTE". Sauf formatage manuelle, et donc cas très spéciaux, on ne fait pas ça. Et ça empêche les apostrophes de passer correctement. Joie ;
  • on vérifie si c'est trop grand et si c'est trop petit, en utilisant un objet pour ça, alors qu'un simple test aurai suffit ;
  • stop "C'est ça !" fait quitter le programme de manière brutale. C'est normalement utilisé en cas de plantage, pas comme fermeture normal.

Je voulais faire une ou deux allocations inutiles, mais ça fait trop longtemps que je n'ai pas fait de fortran.

Fortran reste mon grand copain pour gérer des tableaux efficacement et rapidement, mais j'avais oublié à quel point il est lourd pour le reste (même sans le faire exprès).

+0 -0

ligne 13 : les import * sont à bannir, ca encombre la memoire pour rien

Folaefolc

Ça encombre la mémoire ?

ligne 39 : ne jamais faire de system("pause"), mais input() !

Folaefolc

On ne fait pas input non plus. On laisse le programme se terminer :)

Je suis fan du :

1
2
for _ in range ( c+1 ):
    r += [ _ ]

Je me suis arrêter devant ça pendant 30 secondes, en me demandant ce que ça faisait, si c'était une syntaxe que je ne connaissait pas ou autre chose.

Bah non, c'est juste l'un des pires noms de variables possibles. Mais _, sérieusement ?

+1 -0

Oui on utilise les _ en Python pour une variable (retour de fonction généralement) qu'on ne va pas utiliser.

Par contre pour les from bidule import *, su c'est déconseillé ça n'a rien à voir avec la mémoire. Dans tous les cas le module est importé et chargé totalement.

Si c'est déconseillé c'est que ça importe plein de chose dans l'espace de nom global que tu peux écraser par inadvertance ou écraser des trucs built-in sans que tu le sache.

C'est uniquement pour ces questions de pollution de l'espace de nom qu'on le déconseille, ça n'a rien à voir avec la mémoire.

Non mais ça c'est parfaitement normal. Ce que je voulais dire et que Kje a ensuite expliqué, c'est que un from toto import x ne consomme pas moins de mémoire qu'un from toto import *. Les objets sont chargés dans tous les cas.

Le cas du import toto est encore différent, je crois, car l'évaluation du contenu du module a lieu plus tard. Edit: je me suis un peu emmêlé les pinceaux avec le import simple, le module est évalué tout de suite, la différence se ressent juste lors d'imports circulaires (si on essaie d'importer des fonctions depuis un module en cours d'import).

lignes 8 et 9 : on pourrait générer la liste comme ceci : [i for i in range(c+1)]

Si tu veux vraiment faire une liste, list(range(c+1)) est encore plus simple. Mais ici, c'est même inutile, prendre directement le range est encore plus simple et bouffe moins de mémoire (même si dans le cas présent avec un c relativement petit c'est risible ^^ ). Au lieu d'avoir une bête liste, tu as un objet range mathématiquement complètement définit (et en plus stocké en mémoire et manipulé) par trois nombres : les deux bornes et le pas. Si c est de l'ordre de 10000, ça fait déjà un facteur 3000 en terme de mémoire par rapport à une liste. :p

La mémoire est même pas le seul ni le meilleur argument en faveur de range. Si tu as besoin de faire des manip' du genre number in range(a,b,c), un seul test est effectué et coutera la même chose quelque soient les valeurs des bornes. Si tu t'amuse à faire une liste et que $\dfrac{b-a}{c}$ est très grand, le moindre in va prendre une plombe.

De manière générale, utiliser une liste en python devrait être relativement rare et réservé aux cas où on en a vraiment besoin. Dans la plupart des cas, ce qu'on veut, c'est juste un itérable, peu importe la structure de données derrière.

+6 -0

En me repanchant un peu sur ma participation, voici la même, mais sans un appel à srand() et rand() (j'utilise l'adresse de la variable argc à la place) et avec une utilisation abusive de l'opérateur ternaire (edit: et de l'opérateur ,) et de la récursion de sorte que le corps de la fonction main() ne comporte en fait q'une seule grosse expression-instruction de type void.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int
main(int argc, char **argv)
{
    (argv[1] == 0)
        ? argv[1] = (char *)((unsigned)&argc % 100), (void)main(-1, argv)
        : (argc == -1)
            ? printf("Entrez un nombre [0-99] : "), scanf("%u", &argc), (void)main(argc, argv)
            : (argc == (int)argv[1])
                ? printf("Bravo ! Vous avez trouvé le nombre mystère\n"), exit(0)
                : (argc < (int)argv[1])
                    ? printf("Plus grand !\n"), (void)main(-1, argv)
                    : printf("Plus petit !\n"), (void)main(-1, argv);
}
+2 -0

C'ets vrai que c'est moche, mais je pense que c'est que c'est illégal d'appeler main depuis le programme.

Davidbrcz

À ma connaissance, c'est parfaitement « légal » dans le sens où la norme ne l'interdit pas et où il ne s'agit pas d'un comportement indéfini. D'ailleurs, il n'y a pas de raison spécifique pour que cela soit interdit, puisqu'il s'agit finalement d'une fonction comme les autres, mise à part qu'elle est le point d'entrée du programme. Après, ce n'est clairement pas considéré comme une bonne pratique. :-°

+2 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte