Question

Je me demandais s'il existe des solutions connues pour l'algorithme de création d'un calendrier scolaire. En gros, il est sur l'optimisation « heure de dispersion » (à la fois des enseignants et des classes cas) pour les associations données de classe sous-enseignants. Nous pouvons supposer que nous avons des ensembles de classes, sujets de cours et les enseignants associés entre eux à l'entrée et que le calendrier doit tenir 8 heures-16 heures.

Je suppose qu'il n'y a probablement pas d'algorithme précis pour cela, mais peut-être quelqu'un connaît une bonne approximation ou des conseils pour le développer.

Était-ce utile?

La solution

Ce problème est NP-complet !
En un mot il faut explorer toutes les combinaisons possibles pour trouver la liste des solutions acceptables. En raison des variations dans les circonstances dans lesquelles le problème apparaît dans diverses écoles (par exemple: Y at-il des contraintes en ce qui concerne les salles de classe ?, quelques-unes des cours divisés en sous-groupes de temps en temps ?, Est-ce un horaire hebdomadaire? etc.) il n'y a pas une classe de problème bien connu qui correspond à tous les problèmes de planification. Peut-être, problème havresac a de nombreux éléments de similitude avec ces problèmes à grand.

Une confirmation que c'est à la fois un problème difficile et pour lequel les gens cherchent perpétuellement une solution, est de vérifier ce (long) liste d'outils logiciels de planification (la plupart du temps commercial)

En raison du grand nombre de variables impliquées, la plus grande source qui sont, en général, les désirs de membres du corps professoral; -) ..., il est généralement impossible de considérer énumérer toutes les combinaisons possibles . Au lieu de cela, nous devons choisir une approche qui rend visite à un sous-ensemble des espaces problème / solution.
- Algorithmes génétiques , cité dans une autre réponse est (ou, à mon humble avis, semble ) bien équipé pour effectuer ce type de recherche semi-guidée (le problème de trouver une bonne évaluation fonction pour les candidats à conserver pour la prochaine génération)
- Graphique Réécriture approches sont également utiles avec ce type d'optimisation combinatoire problèmes.

Au lieu de se concentrer sur les implémentations spécifiques d'un programme générateur de calendrier automatique, je voudrais suggérer quelques stratégies qui peuvent être appliquées, au niveau de la définition du problème .
La raison générale est que dans la plupart des problèmes d'ordonnancement du monde réel, des compromis seront nécessaires, pas toutes les contraintes, exprimées et implicites: seront pleinement satisfaits. Par conséquent, nous nous aidons par:

  • Définir et classer toutes les contraintes connues
  • Réduire l'espace de problème, en actionnant manuellement, fournissant un ensemble de supplémentaires contraintes.
    Cela peut sembler contre-intuitif, mais par exemple en fournissant une première, le calendrier partiellement rempli (disons environ 30% des tranches de temps), d'une manière qui satisfait pleinement toutes les contraintes, et en tenant compte de ce calendrier partiel immuable, nous réduisons considérablement le temps / espace nécessaire pour produire des solutions candidats.
    une autre façon des contraintes supplémentaires de l'aide est par exemple « artificiellement "l'ajout d'une contrainte qui empêchent d'enseigner certains sujets sur certains jours de la semaine (si c'est un horaire hebdomadaire ...); ce type de résultats de contraintes pour réduire les espaces problème / solution, sans, en général, à l'exclusion d'un grand nombre de bons candidats.
  • Veiller à ce que certaines des contraintes du problème peut être rapidement calculé. Ceci est souvent associé au choix du modèle de données utilisé pour représenter le problème; l'idée est d'être en mesure d'opter pour-rapidement (ou taillez-out) certaines des options.
  • Redéfinir le problème et de permettre certaines des contraintes à être rompu, quelques fois, (généralement vers les nœuds d'extrémité du graphe). L'idée ici est soit supprimer certains de contraintes pour le remplissage dans les derniers créneaux horaires dans le calendrier, ou d'avoir le programme générateur de calendrier d'arrêt automatique timide de compléter le programme complet, au lieu de nous fournir une liste d'une douzaine de candidats plausibles. Un être humain est souvent dans une meilleure position pour compléter le puzzle, comme il est indiqué, brisant peut-être quelques-unes des contraintes, en utilisant dansformation qui ne sont pas généralement partagée avec la logique automatisée (par exemple: « Pas de mathématiques dans l'après-midi » règle peut être brisée à l'occasion de la classe « mathématiques avancées et la physique », ou « Il est préférable de briser l'une des exigences de M. Jones que l'un des Mme Smith ... ;-))

Dans relectures cette réponse, je me rends compte qu'il est assez timide de fournir une réponse définitive, mais pas moins plein de suggestions pratiques. J'espère que cette aide, avec ce qui est, après tout, un « problème difficile ».

Autres conseils

Il est un gâchis. un désordre royal. Pour ajouter aux réponses, déjà très complet, je veux souligner mon expérience familiale. Ma mère était un enseignant et utilisé pour être impliqué dans le processus.

Transforme que d'avoir un ordinateur pour le faire est non seulement difficile à coder par-soi, il est également difficile, car il y a des conditions qui sont difficiles à spécifier à un programme informatique précuite. Exemples:

  • un enseignant enseigne à la fois à votre école et à un autre institut. De toute évidence, s'il finit la leçon là à 10h30, il ne peut pas commencer dans vos locaux à 10h30, parce qu'il a besoin de temps pour la navette entre les instituts.
  • deux enseignants sont mariés. En général, il est considéré comme une bonne pratique de ne pas avoir deux enseignants mariés sur la même classe. Ces deux enseignants doivent donc avoir deux classes différentes
  • deux enseignants sont mariés, et leur enfant fréquente la même école. Encore une fois, il faut éviter que les deux enseignants à enseigner dans la classe spécifique où leur enfant est.
  • l'école dispose d'installations séparées, comme un jour la classe est dans un institut, et un autre jour la classe est dans un autre.
  • l'école a partagé les laboratoires, mais ces laboratoires ne sont disponibles que sur certains jours de la semaine (pour des raisons de sécurité, par exemple, où du personnel supplémentaire est nécessaire).
  • certains enseignants ont des préférences pour la journée libre: certains préfèrent le lundi, certains le vendredi, certains mercredi. Certains préfèrent venir tôt le matin, certains préfèrent venir plus tard.
  • vous ne devriez pas avoir des situations où vous avez une leçon de dire, l'histoire à la première heure, puis trois heures de mathématiques, puis une autre heure de l'histoire. Il n'a pas de sens pour les élèves, ni pour l'enseignant.
  • vous devez répartir les arguments uniformément. Il n'a pas de sens d'avoir les premiers jours de la semaine que les mathématiques, puis le reste de la semaine que la littérature.
  • vous devez donner quelques professeurs deux heures consécutives pour faire des tests d'évaluation.

Comme vous pouvez le voir, le problème n'est pas NP-complet, il est NP-fou.

Alors qu'est-ce qu'ils font est qu'ils ont une grande table avec de petites incrustations en plastique, et ils se déplacent EISN autour jusqu'à l'obtention d'un résultat satisfaisant. Ils commencent jamais à partir de zéro. Ils commencent normalement à partir du calendrier de l'année précédente et faire des ajustements

Le Concours International Timetabling 2007 avait une leçon piste de planification et d'ordonnancement piste d'examen. De nombreux chercheurs ont participé à cette compétition. Beaucoup de heuristiques et métaheuristiques ont été jugés, mais à la fin métaheuristiques de recherche locale (tels que la recherche Tabou et Recuit Simulé) clairement battu les autres algorithmes (comme les algorithmes génétiques).

Jetez un oeil à 2 frameworks open source utilisés par quelques-uns des finalistes:

L'une de mes missions à mi-terme était une génération de table scolaire algorithme génétique.

Tableau entier est une « organisme ». Il y a eu quelques changements et mises en garde à l'approche des algorithmes génétiques générique:

  • Les règles ont été faites pour « tables illégales »: deux classes dans la même salle de classe, un enseignant à enseigner deux groupes en même temps, etc. Ces mutations ont été jugées létales immédiatement et un nouveau « organisme » a été germé en place de immédiatement le « décédé ». Le premier fut produit par une série d'essais au hasard pour obtenir un juridique (si insensée). mutation létale n'a pas été prise en compte pour le nombre de mutations dans l'itération.

  • mutations "échange" des mutations étaient beaucoup plus fréquents que "Modifier". Les changements étaient seulement entre parties du gène qui avait un sens -. Remplaçant pas un enseignant avec une salle de classe

  • Les petits bonus ont été attribués pour empaqueter certaines 2 heures ensemble, pour assigner une même classe générique dans l'ordre pour le même groupe, pour garder les heures de travail des enseignants et de la classe charge continue. bonus modérés ont été assignés pour donner des classes correctes pour sujet donné, en gardant des heures de cours dans les obligations (matin ou après-midi), et autres. bonus Big étaient pour attribuer bon nombre de sujet donné, la charge de travail donnée pour un enseignant, etc.

  • Les enseignants peuvent créer leurs horaires de la charge de travail de « vouloir travailler alors », « bien travailler alors », «n'aime pas travailler alors », « ne peut pas travailler alors », avec des poids appropriés assignés. Toute 24h étaient heures de travail légal, sauf la nuit était très indésirable.

  • La fonction de poids ... oh ouais. La fonction de poids est énorme, produit monstrueux (comme dans la multiplication) des poids attribués aux caractéristiques sélectionnées et les propriétés. Il était extrêmement raide, une propriété facilement en mesure de le changer par un ordre de grandeur ou vers le bas - et il y avait des centaines ou des milliers de biens dans un organisme. Cela a donné lieu à un nombre absolument énorme que les poids, et en conséquence directe, besoin d'utiliser pour effectuer les calculs une bibliothèque bignum (PMG). l'ensemble initial a commencé une petite testcase de quelque 10 groupes, 10 enseignants et 10 salles de classe, avec la note de 10 ^ -200something et a terminé avec 10 ^ + 300something. Il était totalement inefficace quand il était plus plat. En outre, les valeurs ont augmenté beaucoup plus loin avec les plus grandes « écoles ».

  • Le temps de calcul sage, il y avait peu de différence entre une petite population (100) sur une longue période et une grande population (10k +) sur moins générations. Le calcul sur la même période produit de la même qualité.

  • Le calcul (sur certains CPU 1GHz) prendrait une 1h pour stabiliser près de 10 ^ + 300, générant des horaires qui avaient l'air assez agréable, pour lesdits 10x10x10 cas de test.

  • Le problème est facilement paralellizable en fournissant des installations de réseau qui échange les meilleurs spécimens entre des ordinateurs exécutant le calcul.

Le programme résultant n'a jamais vu la lumière du jour en dehors de moi d'obtenir une bonne note pour le semestre. Il a montré une certaine promesse mais je jamais eu assez de motivation pour ajouter une interface graphique et le rendre utilisable au grand public.

Ce problème est plus difficile qu'il n'y paraît.

Comme d'autres l'ont mentionné, cela est un problème NP-complet, mais Analysons ce que cela signifie.

En gros, cela signifie que vous devez regarder toutes les combinaisons possibles.

Mais « regard sur » ne vous dit pas beaucoup ce que vous devez faire.

Génération toutes les combinaisons possibles est facile. Il peut produire une énorme quantité de données, mais vous ne devriez pas avoir beaucoup du mal à comprendre les concepts de cette partie du problème.

Le deuxième problème est celui de juger si une combinaison possible donnée est bonne, mauvaise ou meilleure que la précédente solution « bon ».

Pour cela, vous avez besoin plus que « est-il une solution possible ».

Par exemple, est le même enseignant travaillant 5 jours par semaine pendant des semaines X droites? Même si cela est une solution de travail, il pourrait ne pas être une meilleure solution que d'alterner entre deux personnes de sorte que chaque enseignant fait une semaine chacune. Oh, tu ne pensais pas à ce sujet? Rappelez-vous, ce sont les gens que vous avez affaire, pas seulement un problème d'allocation des ressources.

Même si un enseignant pourrait travailler à temps plein pendant 16 semaines d'affilée, qui pourrait être une solution sous-optimale par rapport à une solution où vous essayez d'alterner entre les enseignants, et ce genre d'équilibre est très difficile de construire dans le logiciel.

Pour résumer, la production d'une bonne solution à ce problème sera vaut beaucoup, beaucoup à beaucoup de gens. Par conséquent, il est pas un problème facile à décomposer et résoudre. Préparez-vous à jalonner des objectifs qui ne sont pas à 100% et les qualifiant de « assez bon ».

Mise à jour: des commentaires ... devrait avoir heuristiques trop

Je vais avec Prolog ... puis utilisez Ruby ou Perl ou quelque chose pour le nettoyage de votre solution dans une forme plus jolie.

teaches(Jill,math).
teaches(Joe,history).

involves(MA101,math).
involves(SS104,history).

myHeuristic(D,A,B) :- [test_case]->D='<';D='>'.
createSchedule :- findall(Class,involves(Class,Subject),Classes),
                  predsort(myHeuristic,Classes,ClassesNew),
                  createSchedule(ClassesNew,[]).
createSchedule(Classes,Scheduled) :- [the actual recursive algorithm].

Je suis (encore) dans le processus de faire quelque chose de similaire à ce problème, mais en utilisant le même chemin que je viens de mentionner. Prolog (comme un langage fonctionnel) fait vraiment résoudre les problèmes NP-dur plus facile.

Les algorithmes génétiques sont souvent utilisés pour cette programmation.

cet exemple (Faire classe en utilisant l'algorithme génétique annexe) qui correspond votre assez bien besoin.

Voici quelques liens que j'ai trouvé:

calendrier scolaire - dresse la liste des problèmes impliqués

un algorithme génétique hybride pour l'école Timetabling

Programmation Utilitaires et outils

Mon algorithme emplois du temps, mis en œuvre dans FET (Free Software Timetabling, http://lalescu.ro/liviu/fet / , une application réussie):

L'algorithme est heuristique. Je l'ai appelé "swapping récursive".

Entrée:. Un ensemble d'activités A_1 ... A_n et les contraintes

Sortie: un ensemble de fois TA_1 ... TA_n (. Le créneau horaire de chaque activité Les chambres sont exclues ici, par souci de simplicité). L'algorithme doit mettre chaque activité à un intervalle de temps, en respectant les contraintes. Chaque TA_i est compris entre 0 (T_1) et max_time_slots-1 (T_M).

Contraintes:

C1) de base: une liste de paires d'activités qui ne peuvent pas être simultanées (par exemple, A_1 et A_2, parce qu'ils ont le même enseignant ou les mêmes étudiants);

C2) Beaucoup d'autres contraintes (exclues ici, par souci de simplicité).

L'algorithme emplois du temps (que j'ai appelé "swapping récursive"):

  1. activités de tri, le plus difficile en premier. Pas étape critique, mais accélère l'algorithme peut-être 10 fois ou plus.
  2. Essayez de placer chaque activité (A_i) dans un intervalle de temps permis, en suivant l'ordre ci-dessus, une à la fois. Rechercher un emplacement disponible (T_j) pour A_i, dans lequel peut être placé cette activité en respectant les contraintes. Si plusieurs emplacements sont disponibles, choisissez un hasard. Si aucune est disponible, ne swapping récursive:

    . Pour chaque tranche de temps T_j, pensez à ce qui se passe si vous mettez A_i en T_j. Il y aura une liste d'autres activités qui ne sont pas d'accord avec ce mouvement (par exemple, l'activité A_K est sur le même emplacement T_j et a le même enseignant ou même étudiants A_i). Gardez une liste d'activités contradictoires pour chaque tranche de temps T_j.

    b . Choisissez une fente (T_j) avec le plus petit nombre d'activités contradictoires. Dites la liste des activités dans ce créneau contient 3 activités:. A_p, A_q, A_r

    c . Placez A_i à T_j et faire A_p, A_q, A_r non alloué.

    d . essayez récursive de placer A_p, A_q, A_r (si le niveau de récursivité est pas trop grand, disons 14, et si le nombre total d'appels récursifs comptés depuis l'étape 2) sur A_i a commencé est pas trop grand, disons 2 * n), comme dans l'étape 2).

    e . Si A_r, A_p, A_q a placé avec succès, le retour avec succès, sinon essayez d'autres créneaux horaires (passez à l'étape 2 b) et choisir la meilleure tranche horaire).

    f . Si tous (ou un certain nombre de raisonnable) ont essayé créneaux horaires sans succès, le retour sans succès.

    g . Si nous sommes au niveau 0, et nous pas réussi à placer A_i, placez eu comme dans les étapes 2 b) et 2 c), mais sans récursivité. Nous avons maintenant 3 - 1 = 2 plus d'activités à placer. Passez à l'étape 2) (certaines méthodes pour éviter le vélo sont utilisés ici).

J'ai conçu des algorithmes commerciaux pour les emplois du temps de classe et de l'examen emplois du temps. Pour la première je programmation entier; pour la seconde une heuristique basée sur la maximisation d'une fonction objective en choisissant des swaps de sous, très similaire au processus manuel original qui avait été évolué. Ils principales choses à obtenir de telles solutions acceptées sont la capacité de représenter toutes les contraintes du monde réel; et pour l'homme timetablers de ne pas être en mesure de voir les moyens d'améliorer la solution. En fin de la partie algorithmiques était assez simple et facile à mettre en œuvre par rapport à la préparation des bases de données, l'interface utilisateur, la capacité de faire rapport sur les statistiques telles que l'utilisation des salles, formation des utilisateurs et ainsi de suite.

Cet article décrit assez bien le problème du calendrier scolaire et leur approche de l'algorithme: « Le développement de SYLLABUS-interactif, Constraint-Based Scheduler pour les écoles et les collèges. " [PDF]

L'auteur me informe le logiciel SYLLABUS est encore utilisé / développé ici: http: //www.scientia .com / uk /

Je travaille sur un moteur de planification largement utilisé qui fait exactement cela. Oui, il est NP-complet; les meilleures approches cherchent à se rapprocher d'une solution optimale. Et, bien sûr, il y a beaucoup de façons différentes de dire que l'on est la « meilleure » solution - est-il plus important que vos professeurs sont satisfaits de leur emploi du temps, ou que les étudiants entrent dans toutes leurs classes, par exemple

La question absolue la plus importante que vous devez résoudre tôt est ce qui fait un moyen de planification de ce système est meilleur qu'un autre ? Autrement dit, si j'ai un emploi du temps avec Mme Jones à l'enseignement des mathématiques 8 et M. Smith l'enseignement des mathématiques à 9, est-ce mieux ou pire que l'un avec les deux l'enseignement des mathématiques à 10? Est-il meilleur ou pire que celui avec Mme Jones enseignement à 8 et M. Jones enseignement à 2? Pourquoi?

Le principal conseil que je donnerais ici est de diviser le problème autant que possible - peut-être cours par cours, peut-être enseignant par l'enseignant, salle peut-être par chambre - et travailler sur la résolution du problème sous-première. Là, vous devriez finir avec plusieurs solutions à choisir, et la nécessité de choisir un comme optimal le plus probable. Ensuite, le travail sur faire les « précédents » sous-problèmes prennent en compte les besoins des sous-problèmes plus tard dans la notation de leurs solutions potentielles. Ensuite, peut-être travailler sur la façon de vous sortir des situations dans-la-coin peint (en supposant que vous ne pouvez pas anticiper ces situations dans les sous-problèmes antérieurs) quand vous arrivez à un état « pas de solutions valables ».

Une carte d'optimisation de recherche locale est souvent utilisé pour « polir » la réponse finale pour de meilleurs résultats.

Notez que généralement nous avons affaire à des systèmes à ressources très limitées dans la planification scolaire. Les écoles ne passent pas par l'année avec beaucoup de chambres ou d'enseignants vides assis dans le salon 75% de la journée. Les approches qui fonctionnent le mieux dans des environnements de solutions riches ne sont pas nécessairement applicables dans la planification scolaire.

En général, la programmation par contraintes est une bonne approche pour ce type de problème de planification. Une recherche sur « la programmation par contraintes » et l'ordonnancement ou « ordonnancement à base de contraintes » aussi bien dans un débordement de pile et sur Google va générer quelques bonnes références. Il est impossible - il est juste un peu difficile de penser à l'utilisation de méthodes d'optimisation traditionnelles comme l'optimisation linéaire ou entier. Une sortie serait - ce qu'un programme existe qui satisfait toutes les exigences? Cela, en soi, est évidemment utile.

Bonne chance!

Vous pouvez takle avec des algorithmes génétiques, oui. Mais vous ne devriez pas :). Il peut être trop lent et le réglage paramètre peut être trop chronophage etc.

Il y a d'autres approches réussies. Tous les projets mis en œuvre en open source:

  • approche à base de contraintes
    • Mis en œuvre en Unitime (pas vraiment pour les écoles)
    • Vous pouvez aussi aller plus loin et utiliser la programmation entière. Fait avec succès à Udine Université et également à l'Université de Bayreuth (I a été impliqué là-bas) à l'aide du logiciel commercial (ILOG CPLEX)
    • approche basée sur la règle avec heuristisc - Voir planificateur Drools
  • Différents heuristiques - et mon

Voir ici pour une la liste des logiciels emplois du temps

Je pense que vous devez utiliser un algorithme génétique parce que:

  • Il est le mieux adapté pour les cas de problèmes importants.
  • Il donne la complexité du temps réduit sur le prix de la réponse erronée (Pas le meilleur final)
  • Vous pouvez spécifier des contraintes et préférences facilement en ajustant les punitions de conditionnement physique pour les non rencontrés.
  • Vous pouvez spécifier délai pour l'exécution du programme.
  • La qualité de la solution dépend de combien de temps vous avez l'intention de passer la résolution du programme ..

    algorithmes génétiques Définition

    algorithmes génétiques Tutorial

    projet de planification de classe avec GA

Jetez aussi un coup d'oeil à: une question similaire et un autre

Je ne sais pas un sera d'accord avec ce code, mais j'ai développé ce code avec l'aide de mon propre algorithme et travaille pour moi dans ruby.Hope il les aidera à qui recherchent ce dans le code suivant le periodflag, dayflag subjectflag et la teacherflag sont la table de hachage avec l'ID correspondant et la valeur du drapeau qui est booléen. Toute question me contacter ....... (-_-)

periodflag.each do | 2 m end, v2 |

            if(TimetableDefinition.find(k2).period.to_i != 0)
                subjectflag.each do |k3,v3|
                    if (v3 == 0)
                        if(getflag_period(periodflag,k2))
                            @teachers=EmployeesSubject.where(subject_name: @subjects.find(k3).name, division_id: division.id).pluck(:employee_id)
                            @teacherlists=Employee.find(@teachers)
                            teacherflag=Hash[teacher_flag(@teacherlists,teacherflag,flag).to_a.shuffle] 
                            teacherflag.each do |k4,v4|
                                if(v4 == 0)
                                    if(getflag_subject(subjectflag,k3))
                                        subjectperiod=TimetableAssign.where("timetable_definition_id = ? AND subject_id = ?",k2,k3)
                                        if subjectperiod.blank?
                                            issubjectpresent=TimetableAssign.where("section_id = ? AND subject_id = ?",section.id,k3)
                                            if issubjectpresent.blank?
                                                isteacherpresent=TimetableAssign.where("section_id = ? AND employee_id = ?",section.id,k4)
                                                if isteacherpresent.blank?
                                                    @finaltt=TimetableAssign.new
                                                    @finaltt.timetable_struct_id=@timetable_struct.id
                                                    @finaltt.employee_id=k4
                                                    @finaltt.section_id=section.id
                                                    @finaltt.standard_id=standard.id
                                                    @finaltt.division_id=division.id
                                                    @finaltt.subject_id=k3
                                                    @finaltt.timetable_definition_id=k2
                                                    @finaltt.timetable_day_id=k1
                                                    set_school_id(@finaltt,current_user)
                                                    if(@finaltt.save)

                                                        setflag_sub(subjectflag,k3,1)
                                                        setflag_period(periodflag,k2,1)
                                                        setflag_teacher(teacherflag,k4,1)
                                                    end
                                                end
                                            else
                                                @subjectdetail=TimetableAssign.find_by_section_id_and_subject_id(@section.id,k3)
                                                @finaltt=TimetableAssign.new
                                                @finaltt.timetable_struct_id=@subjectdetail.timetable_struct_id
                                                @finaltt.employee_id=@subjectdetail.employee_id
                                                @finaltt.section_id=section.id
                                                @finaltt.standard_id=standard.id
                                                @finaltt.division_id=division.id
                                                @finaltt.subject_id=@subjectdetail.subject_id
                                                @finaltt.timetable_definition_id=k2
                                                @finaltt.timetable_day_id=k1
                                                set_school_id(@finaltt,current_user)
                                                if(@finaltt.save)

                                                    setflag_sub(subjectflag,k3,1)
                                                    setflag_period(periodflag,k2,1)
                                                    setflag_teacher(teacherflag,k4,1)
                                                end
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top