Partage

[Exercices] Venez vous entraîner !

Ce mois: Parseur de fonctions mathématiques

16 juillet 2008 à 21:31:27

Woow ça l'air d'être de le super-exo-trop-dur-qui-tue o_O !

Ok je sors ->[]
16 juillet 2008 à 22:07:17

Citation : chab_lack

- Apparamment les octects sont des caractères, donc on peut afficher un warning si le code source tente de faire passer la valeur d'un octet en négatif ?
- De même on peut afficher un warning si le code source tente d'accéder à l'octet -1 ?


Citation : nanoc

Si le pointeur sort du tableau, on le remet à l'autre bout.

il n'ya pas de warning, parceque c'est la "norme du langege" ( :lol: ) qui le dit.
16 juillet 2008 à 22:19:01

Merci, mais ca ne répond qu'à ma deuxième question
17 juillet 2008 à 8:45:56

Bachi a raison. Les dépassements à gauche et à droite sont "dans la norme", on retourne à l'autre bout. C'est la même chose pour les octets. 255 + 1 = 0 et ainsi de suite.
Cela veut dire en fait que l'on pourrait supprimer deux instructions. On peut remplacer - par 255 + et < par 29999 > :)
Ce n'est par contre pas très pratique.

Pour le warning, disons que normalement quand il y a une erreur, le programme ne pourra pas continuer ou en tout cas, il ne pourras pas faire ce qu'on attend de lui. Habituellement les interpréteurs (quelque soit le langage) s'arrêtent à la première erreur rencontrée et donnent le numéro de la ligne qui cause l'erreur. On peut imaginer faire la même chose ici, en indiquant quel caractère a causé l'erreur.
de toute façon, vu la "pauvreté" du langage, il n'y a pas 50'000 erreurs différentes possibles.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
17 juillet 2008 à 11:50:18

Petite question sur les [ et les ]:
Peuvent-ils être imbriqués?
[ 
qqchose
[
qqchose
]
qqchose
]

Avec le premier [ qui renvoie au 2e ], et le 2e [ qui renvoie au premier ] ?

A+
17 juillet 2008 à 11:54:01

oui bien sûr. Regarde le code BF que j'ai proposé.

C'est justement une des difficultés de ce programme.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
17 juillet 2008 à 16:08:48

Question, c'est fait exprès le coup du '255' ? (et pas 256) je viens de perdre 30 minutes avant de comprendre qu'il fallait quand même vérifier à chaque fois qu'on ajoute ou soustrait :-° (edit: non, petite arreur dans mon code :lol: )

Sinon, on peut faire quand même avec des classes, si on préfère ?
17 juillet 2008 à 16:15:46

parce que meme la case 0 ça compte :)
17 juillet 2008 à 16:19:04

dans un char, tu peux mettre que 0 à 255 (=2^8-1).
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
17 juillet 2008 à 16:22:17

Oui, je viens de voir, en fait ca venait d'une erreur dans ma classe qui gere plusieurs - d'un coup :lol:

ma 2ème question tient toujours, ca gêne si on fait des classes ?
17 juillet 2008 à 16:25:14

Citation : Nanoc

C'est la même chose pour les octets. 255 + 1 = 0 et ainsi de suite.



cela veut donc dire que l'on considère les octets comme des unsigned ?
17 juillet 2008 à 18:05:56

Oui unsigned char.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
17 juillet 2008 à 18:30:49

Solution du deuxième exercice du mois de juin 2008



Bonjour tout le monde ! Il est temps que je dévoile une solution pour l'exercice du mois de juin . Vous avez été 4 à m'envoyer une solution. Ce qui est moins que d'habitude, mais l'exercice était plus corsé.
Toutes les solutions fonctionnaient correctement, mais étaient très longues (plus de 150 lignes ajoutées) alors qu'en réalité très peu étaient nécessaire.

Partie recherche d'information



Dans un premier temps, il fallait se renseigner un peu sur l'opérateur virgule. La chose importante à remarquer est que l'opérateur virgule est celui avec la préséance la plus faible. L'expression :

tableau = 2,3,4,5;


est donc lu par le compilateur comme :

(((tableau = 2),3),4),5;


Il faudra donc utiliser deux choses différentes. L'opérateur = renverra un objet "Remplisseur" à qui on aura surchargé l'opérateur virgule.

Programmation



Ecrivons donc cet opérateur =. Rien de plus simple. Il doir mettre l'entier reçu dans la première case (si elle existe) et renvoyer un "Remplisseur".

Remplisseur operator=(int x)  //On prend le premier entier après la virgule
    {
        at(0) = x;                //On le met dans la premiere case
        return Remplisseur(*this,1);  //Et on initialise un remplisseur a partir de la deuxieme case
    }


Vous remarquerez qu'on utilise at() comme ça on a pas besoin de gérer sois-même le cas où le tableau est vide. On laisse ça au vector qui lançera une exception le cas échéant.

Regardons de plus près le remplisseur.

Il doit écrire dans les cases du tableau. Il faut donc le lui donner à la construction. (D' où le *this dans le code). Il doit également connaître la position à partir de laquelle il doit écrire dans le tableau (vous verrez pourquoi plus loin).

On a donc qqch comme :

class Remplisseur
{
public:
    Remplisseur(vector<int>& vec,unsigned int pos)
            : m_ref(vec)  //On initalise la reference
    {
        m_position = pos;    //Et on enregistre la position
    }

private:
    vector<int>& m_ref;
    static size_t m_position;
};


Notez la référence sur le tableau pour éviter la copie et le static qui permet de partager la position entre toutes les instances qu'il faut d'ailleur inialiser en-dehors de la classe. (le static n'est pas nécessaire pour le niveau 1)

size_t Remplisseur::m_position=0;


Quand on a ce code :

(((tableau = 2),3),4),5;


Il devient après l'exécution de l'opérateur = :

(((Remplisseur,3),4),5;


Il faut donc surcharger l'opérateur virgule du Remplisseur pour ajouter le nombre suivant. Et pour réutiliser le même code, on va le faire renvoyer un nouveau remplisseur avec la position suivante

Remplisseur operator,(int x)     //On prend l'entier après la virgule
    {
        m_ref.at(m_position)=x;                 //On le met à la position qu'il faut dans le Tableau
        return Remplisseur(m_ref,m_position+1); //Et on cree un nouveau Remplisseur qui s'occupera de la case suivante
    }


Remarquez à nouveau le at() pour laisser la gestion de l'exception au vector pour le cas où l'utilisateur veut mettre trop d'éléments dans le vector.

Et voilà, c'est tout pour le niveau 1.

Niveau 2


Pour aller plus loin, il fallait gérer les cas où on affecte un seul chiffre à tout le tableau, ou le cas où il n'y a pas assez d'éléments.

Pour faire cela, il fallait penser que les objets Remplisseurs meurent (sont détruits) à la fin de la ligne. On pourrait donc tester dans le destructeur combien vaut m_position. Afin de savoir si il y a eu assez de virgules. Ce qui donne simplement :

~Remplisseur()
    {
        if (m_position==1) //Cas ou on fait l'affectation tab=1;
        {
            m_ref.assign(m_ref.size(),m_ref.at(0));
            m_position=0;
        }
        else if(m_position!=m_ref.size()) //Cas si il y a pas assez de virgules.
        {
            m_position=0;
            throw std::out_of_range("Tableau:: Too much or not enough arguments after comma");
        }
    }


Si il y a eu qu'un seul Remplisseur de créé, m_position vaut 1 et donc on est dans le cas où on remplit tout le tableau avec une seule valeur. On utilie assign de vector pour le faire.
Si m_position est différent de la taille du tableau, c'est qu'il y a pas eu assez d'appel. On lance donc une exception (standard).
Il faut pas oublier de remettre m_position à 0.

Le code complet est donc:
#include <vector>
#include <iostream>
#include <stdexcept>
using namespace std;

class Remplisseur
{
public:
    Remplisseur(vector<int>& vec,unsigned int pos)
            : m_ref(vec)  //On initalise la reference
    {
        m_position = pos;    //Et on enregistre la position
    }

    Remplisseur operator,(int x)     //On prend l'entier après la virgule
    {
        m_ref.at(m_position)=x;                 //On le met à la position qu'il faut dans le Tableau
        return Remplisseur(m_ref,m_position+1); //Et on cree un nouveau Remplisseur qui s'occupera de la case suivante
    }

    ~Remplisseur()
    {
        if (m_position==1)
        {
            m_ref.assign(m_ref.size(),m_ref.at(0));
            m_position=0;
        }
        else if(m_position!=m_ref.size())
        {            
            m_position=0;
            throw std::out_of_range("Tableau:: Too much or not enough arguments after comma");
        }
    }

private:
    vector<int>& m_ref;
    static size_t m_position;
};

size_t Remplisseur::m_position=0;

class Tableau: public vector<int>
{
public:
    Tableau(unsigned int i=0)    //Construit un tableau de longueur i
            :vector<int>(i,0)
    {}

    Remplisseur operator=(int x)  //On prend le premier entier après la virgule
    {
        at(0) = x;                //On le met dans la premiere case
        return Remplisseur(*this,1);  //Et on initialise un remplisseur a partir de la deuxieme case
    }

};

ostream& operator<<(ostream& flux,const Tableau& tab)  //Affiche le contenu du tableau
{
    if (!tab.empty())
    {
        for (unsigned int i(0);i<tab.size()-1;++i)
            flux << tab[i] << ",";          //On separe les elements par des virgules
        flux << tab[tab.size()-1];
    }
    return flux;
}

int main()
{
    Tableau tab(10);

    tab=0,1,2,3,4,5,6,7,8,9;

    cout << tab << endl;

    return 0;
}


Niveau 3



Le code de niveau 3 ajoute juste des templates là où il faut :

#include <vector>
#include <iostream>
using namespace std;

template<typename T>
class Remplisseur
{
public:
    Remplisseur(vector<T>& vec,unsigned int pos)  //Initialise juste les attributs
            : ref(vec)
    {
        m_pos = pos;
    }

    Remplisseur operator,(const T& x)     //On prend l'entier après la virgule
    {
        ref.at(m_pos)=x;               //On le met à la position qu'il faut dans le Tableau
        return Remplisseur(ref,m_pos+1); //Et on cree un nouveau Remplisseur qui s'occupera de la case suivante
    }

    ~Remplisseur()
    {
        if (m_pos==1)
        {
            ref.assign(ref.size(),ref.at(0));
            m_pos=0;
        }
        else if(m_position!=m_ref.size())
        {
            m_position=0;
            throw std::out_of_range("Tableau:: Too much or not enough arguments after comma");
        }

    }

private:
    vector<T>& ref;
    static size_t m_pos;
};

template<typename T>
size_t Remplisseur<T>::m_pos=0;

template<typename T>
class Tableau: public vector<T>
{
public:
    Tableau(unsigned int i=0)    //Construit un tableau de longueur i
            :vector<T>(i)
    {}

    Remplisseur<T> operator=(int x)  //On prend le premier entier après la virgule
    {
        vector<T>::at(0) = x;            //On le met dans la premiere case
        return Remplisseur<T>(*this,1);  //Et on initialise un remplisseur a partir de la deuxieme case
    }

};

template<typename T>
ostream& operator<<(ostream& flux,const Tableau<T>& tab)  //Affiche le contenu du tableau
{
    if (!tab.empty())  //Si il est non-vide
    {
        for (unsigned int i(0);i<tab.size()-1;++i)
            flux << tab[i] << ",";          //On separe les elements par des virgules
        flux << tab[tab.size()-1];
    }
    return flux;
}


int main()
{
    Tableau<int> tab(10);

    tab=2;

    cout << tab << endl;

    return 0;
}


Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
17 juillet 2008 à 21:19:31

Avoir, en mode Release, une trentaine de tests d'effectués pour l'assignation de seulement 10 éléments, ça me semble excessif, surtout quand on sait très bien qu'ils seront tous faux.
De plus, le membre static est une mauvaise idée: qu'est ce qui se passe si deux threads essayent d'initialiser un tableau en même temps?
Il y a d'autres détails qui serait améliorables (copie d'objet inutile par exemple) mais qui ne sont pas vraiment génant ainsi.
17 juillet 2008 à 21:41:59

Pourquoi tu t'embêtes avec la position? Il suffit d'un appel à push_back.

Version vite pondue, et perfectible.
#include <vector>
template <typename Container>
struct ComaInserter
{
    typedef typename Container::value_type value_type;

    ComaInserter(Container & container) : m_container(container) {}

    ComaInserter const& operator,(value_type const& v) const {
	m_container . push_back(v);
	return *this;
    }
private:
    Container & m_container;
};

template <typename Container>
ComaInserter<Container> operator+=(Container & container, typename Container::value_type const& v)
{
    return ComaInserter<Container>(container), v;
}

#include <iostream>
#include <algorithm>
#include <iterator>

int main (int argc, char **argv)
{
    std::vector <int> v;
    v += 1, 2, 45;

    std::copy(v.begin(),v.end(), std::ostream_iterator<int>(std::cout, ", "));
}

C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
17 juillet 2008 à 21:45:51

Citation : lmghs

Pourquoi tu t'embêtes avec la position? Il suffit d'un appel à push_back.

Version vite pondue, et perfectible.
#include <vector>
template <typename Container>
struct ComaInserter
{
    typedef typename Container::value_type value_type;

    ComaInserter(Container & container) : m_container(container) {}

    ComaInserter const& operator,(value_type const& v) const {
	m_container . push_back(v);
	return *this;
    }
private:
    Container & m_container;
};

template <typename Container>
ComaInserter<Container> operator+=(Container & container, typename Container::value_type const& v)
{
    return ComaInserter<Container>(container), v;
}

#include <iostream>
#include <algorithm>
#include <iterator>

int main (int argc, char **argv)
{
    std::vector <int> v;
    v += 1, 2, 45;

    std::copy(v.begin(),v.end(), std::ostream_iterator<int>(std::cout, ", "));
}



C'est ce que j'avais d'abord essayé de faire :) , et ça avait tout à fait marché.
17 juillet 2008 à 22:48:27

Citation : Spaz

ca gêne si on fait des classes ?



Moi j'ai 5 classes différentes (4 si on compte qu'une devrait être imbriquée).
Je divise chaque tâche pour permettre un maximum d'amélioration et une facilité dans la maintenance.
17 juillet 2008 à 22:55:15

Citation : Chlab_lak

Citation : Spaz

ca gêne si on fait des classes ?



Moi j'ai 5 classes différentes (4 si on compte qu'une devrait être imbriquée).
Je divise chaque tâche pour permettre un maximum d'amélioration et une facilité dans la maintenance.



Ca risque pas d'alourdir le programme ?
17 juillet 2008 à 23:04:22

C'est vrai qe ça améliore le côté pratique...donc bon :D
18 juillet 2008 à 9:18:35

@Hunlyxxod: Pour les test en effet. Mais bon ce n'est pas un mode de remplissage de tableau très efficace et surtout difficilement utilisable dans un algorithme. Je vois plutôt cela comme un moyen d'initialiser le tableau pour un humain.
Pour le static, en effet ca gêne pour les threads, mais dans ce cas, on peut l'enlever et ajouter un paramètre aux fonctions. Je voulais mettre une fois un static, histoire d'avoir un cas où c'est utilisable.
Pour les copies, je sais pas où tu en vois. A part des copies d'int.

@lmghs: Oui, mais ça ne correspond pas à l'énoncé. Le but était de remplir un tableau déjà existant et pas d'ajouter des cases à la fin.
De plus pour correspondre au "niveau 2", il fallait qu'une expression comme :
tab = 1,2;

sur un tableu de 10 cases lève une exception (ou signale une erreur). Il fallait donc un moyen de compter le nombre d'appel à l'opérateur virgule.

Au sujet des classes: C'est bien de séparer les concepts, mais je suis quand même d'avis que pour ces exercices plus de 1 ou 2 classes est souvent exagéré. Il n'y a pas beaucoup de concepts vraiment séparés à représenter. Avoir plusieurs fonctions est par contre tout à fait normal.

@nono212: Ca n'alourdit rien au moment de la compilation (pour autant que le code soit correct) et donc au niveau du programme final.
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
18 juillet 2008 à 13:04:39

Citation : Chlab_lak

Citation : Spaz

ca gêne si on fait des classes ?



Moi j'ai 5 classes différentes (4 si on compte qu'une devrait être imbriquée).
Je divise chaque tâche pour permettre un maximum d'amélioration et une facilité dans la maintenance.



Perso pour BF j'ai 110 lignes (commentées et présentées correctement), alors séparer 110 lignes en plusieurs classes je suis pas sur que ce soit plus lisible, surtout si tu met une classe par fichier !
FaQ : Fr | En 1 2 | C++11 | Template || Blog : Deloget | C++|Boost--Dev | C++Next | GotW || Installer Boost
18 juillet 2008 à 14:33:18

Citation : Nanoc

@lmghs: Oui, mais ça ne correspond pas à l'énoncé. Le but était de remplir un tableau déjà existant et pas d'ajouter des cases à la fin.
De plus pour correspondre au "niveau 2", il fallait qu'une expression comme :

tab = 1,2;


sur un tableau de 10 cases lève une exception (ou signale une erreur). Il fallait donc un moyen de compter le nombre d'appel à l'opérateur virgule.


OK. J'avais zappé ce détail dans les énoncés.
C'est quand même contre nature. Une affectation devrait ignorer l'ancien état pour en définir un nouveau. (sauf contraintes particulières comme un membre référence ou constant qui feront refuser l'affectation). Surtout avec un vecteur qui est redimensionnable.
C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
19 juillet 2008 à 23:20:32

Et hop ! Interpréteur Brainfuck terminé.

Par contre le code inconnu est tout simplement énorme !
Et pourtant si petit (172 caractères en enlevant les espaces qui servent à rien).
20 juillet 2008 à 17:38:28

Juste une question: le code inconnu s'arrête-t-il une fois ? Parce que là j'attents depuis je sais pas combien de temps.
20 juillet 2008 à 18:02:23

Je pense pas qu'il s'arrête. J'ai pas étudié le code mais je l'ai fait tourner jusqu'à des nombres de 450 chiffres !!!
20 juillet 2008 à 18:23:20

Il part en boucle infinie. C'est correct. (En tout cas sur cet aspect)
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
20 juillet 2008 à 20:15:46

Citation : Nanoc

Il part en boucle infinie. C'est correct. (En tout cas sur cet aspect)


Pourquoi, on est sensé trouver une erreur ?
Pour moi il fonctionne très bien.