Initial commit.
This commit is contained in:
parent
2eca8499dc
commit
58245c1345
17
Pipfile
Normal file
17
Pipfile
Normal file
@ -0,0 +1,17 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
colorclass = "==2.2.0"
|
||||
numpy = "==1.14.3"
|
||||
pandas = "==0.22.0"
|
||||
six = "==1.11.0"
|
||||
terminaltables = "==3.1.0"
|
||||
request = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
138
Pipfile.lock
generated
Normal file
138
Pipfile.lock
generated
Normal file
@ -0,0 +1,138 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "4ab96f17ddd44492eb549ca013af03d1ea82744d4cbfb5dda01a1a47b2a7c3ed"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"colorclass": {
|
||||
"hashes": [
|
||||
"sha256:b05c2a348dfc1aff2d502527d78a5b7b7e2f85da94a96c5081210d8e9ee8e18b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"get": {
|
||||
"hashes": [
|
||||
"sha256:9177b24df2b5be1ba86c6d6c2afbee8a77b716ab1b5bb11fcbdf65c3f1decf1a"
|
||||
],
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"numpy": {
|
||||
"hashes": [
|
||||
"sha256:0074d42e2cc333800bd09996223d40ec52e3b1ec0a5cab05dacc09b662c4c1ae",
|
||||
"sha256:034717bfef517858abc79324820a702dc6cd063effb9baab86533e8a78670689",
|
||||
"sha256:0db6301324d0568089663ef2701ad90ebac0e975742c97460e89366692bd0563",
|
||||
"sha256:1864d005b2eb7598063e35c320787d87730d864f40d6410f768fe4ea20672016",
|
||||
"sha256:46ce8323ca9384814c7645298b8b627b7d04ce97d6948ef02da357b2389d6972",
|
||||
"sha256:510863d606c932b41d2209e4de6157ab3fdf52001d3e4ad351103176d33c4b8b",
|
||||
"sha256:560e23a12e7599be8e8b67621396c5bc687fd54b48b890adbc71bc5a67333f86",
|
||||
"sha256:57dc6c22d59054542600fce6fae2d1189b9c50bafc1aab32e55f7efcc84a6c46",
|
||||
"sha256:760550fdf9d8ec7da9c4402a4afe6e25c0f184ae132011676298a6b636660b45",
|
||||
"sha256:8670067685051b49d1f2f66e396488064299fefca199c7c80b6ba0c639fedc98",
|
||||
"sha256:9016692c7d390f9d378fc88b7a799dc9caa7eb938163dda5276d3f3d6f75debf",
|
||||
"sha256:98ff275f1b5907490d26b30b6ff111ecf2de0254f0ab08833d8fe61aa2068a00",
|
||||
"sha256:9ccf4d5c9139b1e985db915039baa0610a7e4a45090580065f8d8cb801b7422f",
|
||||
"sha256:a8dbab311d4259de5eeaa5b4e83f5f8545e4808f9144e84c0f424a6ee55a7b98",
|
||||
"sha256:aaef1bea636b6e552bbc5dae0ada87d4f6046359daaa97a05a013b0169620f27",
|
||||
"sha256:b8987e30d9a0eb6635df9705a75cf8c4a2835590244baecf210163343bc65176",
|
||||
"sha256:c3fe23df6fe0898e788581753da453f877350058c5982e85a8972feeecb15309",
|
||||
"sha256:c5eb7254cfc4bd7a4330ad7e1f65b98343836865338c57b0e25c661e41d5cfd9",
|
||||
"sha256:c80fcf9b38c7f4df666150069b04abbd2fe42ae640703a6e1f128cda83b552b7",
|
||||
"sha256:e33baf50f2f6b7153ddb973601a11df852697fba4c08b34a5e0f39f66f8120e1",
|
||||
"sha256:e8578a62a8eaf552b95d62f630bb5dd071243ba1302bbff3e55ac48588508736",
|
||||
"sha256:f22b3206f1c561dd9110b93d144c6aaa4a9a354e3b07ad36030df3ea92c5bb5b",
|
||||
"sha256:f39afab5769b3aaa786634b94b4a23ef3c150bdda044e8a32a3fc16ddafe803b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.14.3"
|
||||
},
|
||||
"pandas": {
|
||||
"hashes": [
|
||||
"sha256:02541a4fdd31315f213a5c8e18708abad719ee03eda05f603c4fe973e9b9d770",
|
||||
"sha256:052a66f58783a59ea38fdfee25de083b107baa81fdbe38fabd169d0f9efce2bf",
|
||||
"sha256:06efae5c00b9f4c6e6d3fe1eb52e590ff0ea8e5cb58032c724e04d31c540de53",
|
||||
"sha256:12f2a19d0b0adf31170d98d0e8bcbc59add0965a9b0c65d39e0665400491c0c5",
|
||||
"sha256:244ae0b9e998cfa88452a49b20e29bf582cc7c0e69093876d505aec4f8e1c7fe",
|
||||
"sha256:2907f3fe91ca2119ac3c38de6891bbbc83333bfe0d98309768fee28de563ee7a",
|
||||
"sha256:44a94091dd71f05922eec661638ec1a35f26d573c119aa2fad964f10a2880e6c",
|
||||
"sha256:587a9816cc663c958fcff7907c553b73fe196604f990bc98e1b71ebf07e45b44",
|
||||
"sha256:66403162c8b45325a995493bdd78ad4d8be085e527d721dbfa773d56fbba9c88",
|
||||
"sha256:68ac484e857dcbbd07ea7c6f516cc67f7f143f5313d9bc661470e7f473528882",
|
||||
"sha256:68b121d13177f5128a4c118bb4f73ba40df28292c038389961aa55ea5a996427",
|
||||
"sha256:97c8223d42d43d86ca359a57b4702ca0529c6553e83d736e93a5699951f0f8db",
|
||||
"sha256:af0dbac881f6f87acd325415adea0ce8cccf28f5d4ad7a54b6a1e176e2f7bf70",
|
||||
"sha256:c2cd884794924687edbaad40d18ac984054d247bb877890932c4d41e3c3aba31",
|
||||
"sha256:c372db80a5bcb143c9cb254d50f902772c3b093a4f965275197ec2d2184b1e61"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.22.0"
|
||||
},
|
||||
"post": {
|
||||
"hashes": [
|
||||
"sha256:13db715defcc1ab9e987c3a397cfb4e4eaf205023830876fb0979f905f56fd5f"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"public": {
|
||||
"hashes": [
|
||||
"sha256:b923b41a57ef886f4d0aee932aac848506680f3d746326149079f73a6403485f"
|
||||
],
|
||||
"version": "==1.0.3"
|
||||
},
|
||||
"python-dateutil": {
|
||||
"hashes": [
|
||||
"sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0",
|
||||
"sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8"
|
||||
],
|
||||
"version": "==2.7.3"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
|
||||
"sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
|
||||
],
|
||||
"version": "==2018.4"
|
||||
},
|
||||
"query-string": {
|
||||
"hashes": [
|
||||
"sha256:9d06d013034e9c855e26fac2e67d154d0b556ce0353c2eba7330f2ca79fa1a95"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"request": {
|
||||
"hashes": [
|
||||
"sha256:009397eba1bd73ddc8fdc0745f15ecacdf38d0be2186ef431bc8357a60f0900b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.11.0"
|
||||
},
|
||||
"terminaltables": {
|
||||
"hashes": [
|
||||
"sha256:f3eb0eb92e3833972ac36796293ca0906e998dc3be91fbe1f8615b331b853b81"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.1.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
}
|
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = sage
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
6
docs/_static/figure1
vendored
Normal file
6
docs/_static/figure1
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
+-------------+-----------+-----------+-----------+-----------+-----------+
|
||||
| 0 | 1 | 2 | 3 | 4 | 5 |
|
||||
+-------------+-----------+-----------+-----------+-----------+-----------+
|
||||
| Muet ou très peu bavard | | | Très bavard |
|
||||
+-------------+-----------+-----------+-----------+-----------+-----------+
|
BIN
docs/_static/figure1.png
vendored
Normal file
BIN
docs/_static/figure1.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
3
docs/_static/figure2
vendored
Normal file
3
docs/_static/figure2
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
+----+-----+---------------------+
|
||||
| ID | NOM | NIVEAU DE BAVARDAGE |
|
||||
+----+-----+---------------------+
|
BIN
docs/_static/figure2.png
vendored
Normal file
BIN
docs/_static/figure2.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
9
docs/api.rst
Normal file
9
docs/api.rst
Normal file
@ -0,0 +1,9 @@
|
||||
***
|
||||
API
|
||||
***
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
api/*
|
8
docs/api/engine.rst
Normal file
8
docs/api/engine.rst
Normal file
@ -0,0 +1,8 @@
|
||||
.. _engine:
|
||||
|
||||
========
|
||||
Engine
|
||||
========
|
||||
|
||||
.. autoclass:: sage.Engine
|
||||
:members:
|
8
docs/api/interface.rst
Normal file
8
docs/api/interface.rst
Normal file
@ -0,0 +1,8 @@
|
||||
.. _interface:
|
||||
|
||||
=========
|
||||
Interface
|
||||
=========
|
||||
|
||||
.. automodule:: sage.interface
|
||||
:members:
|
8
docs/api/seatingplan.rst
Normal file
8
docs/api/seatingplan.rst
Normal file
@ -0,0 +1,8 @@
|
||||
.. _seatingplan:
|
||||
|
||||
===========
|
||||
SeatingPlan
|
||||
===========
|
||||
|
||||
.. autoclass:: sage.SeatingPlan
|
||||
:members:
|
8
docs/api/student.rst
Normal file
8
docs/api/student.rst
Normal file
@ -0,0 +1,8 @@
|
||||
.. _student:
|
||||
|
||||
========
|
||||
Student
|
||||
========
|
||||
|
||||
.. autoclass:: sage.Student
|
||||
:members:
|
194
docs/conf.py
Normal file
194
docs/conf.py
Normal file
@ -0,0 +1,194 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file does only contain a selection of the most common options. For a
|
||||
# full list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
import os
|
||||
import sys
|
||||
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
# sys.path.insert(0, os.path.abspath('../spoticla/'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'SAGE (Seating plAn GEnerator)'
|
||||
copyright = '2018, Jeff LANCE'
|
||||
author = 'Jeff LANCE'
|
||||
|
||||
# The short X.Y version
|
||||
version = ''
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.5'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinxcontrib.napoleon',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.imgmath',
|
||||
'sphinx.ext.ifconfig',
|
||||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = 'fr'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path .
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
# html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# The default sidebars (for documents that don't match any pattern) are
|
||||
# defined by theme itself. Builtin themes are using these templates by
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ---------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'spoticladoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'spoticla.tex', 'spoticla Documentation',
|
||||
'Jeff LANCE', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'spoticla', 'spoticla Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output ----------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'spoticla', 'spoticla Documentation',
|
||||
author, 'spoticla', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Epub output -------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = project
|
||||
epub_author = author
|
||||
epub_publisher = author
|
||||
epub_copyright = copyright
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
#
|
||||
# epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
#
|
||||
# epub_uid = ''
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
epub_exclude_files = ['search.html']
|
||||
|
||||
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
||||
# -- Options for todo extension ----------------------------------------------
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
3
docs/getting-started.rst
Normal file
3
docs/getting-started.rst
Normal file
@ -0,0 +1,3 @@
|
||||
**************
|
||||
Pour commencer
|
||||
**************
|
11
docs/index.rst
Normal file
11
docs/index.rst
Normal file
@ -0,0 +1,11 @@
|
||||
#####################
|
||||
SAGE's documentation!
|
||||
#####################
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
intro
|
||||
getting-started
|
||||
api
|
||||
references
|
483
docs/intro.rst
Normal file
483
docs/intro.rst
Normal file
@ -0,0 +1,483 @@
|
||||
########
|
||||
À propos
|
||||
########
|
||||
|
||||
|
||||
|
||||
*************
|
||||
Problématique
|
||||
*************
|
||||
|
||||
*Contexte: Élèves d'une classe, places assises dans une salle*
|
||||
|
||||
*Contraintes:
|
||||
Le niveau de bavardage d'un élève, les liens sociaux entre élèves*
|
||||
|
||||
Comme tout enseignant j'ai été et je suis encore confronté au problème de la
|
||||
gestion de classe et plus précisément aux interactions efficaces ou inefficaces
|
||||
des élèves au sein d'une classe.
|
||||
|
||||
En effet, les associations d´élèves bavards entre eux peuvent mettre à mal une séance.
|
||||
Dissocier une binôme productif peut l'être également.
|
||||
|
||||
Pour "régler" cela, la démarche consiste à créer un plan de classe afin de diriger
|
||||
ou limiter les liens que peuvent avoir les élèves entre eux durant une séance.
|
||||
Ainsi, j'ai tenté plusieurs fois de casser les bavardages entre plusieurs
|
||||
camarades en les disposant dans la classe à divers endroits et ce dans le
|
||||
but d'arriver à un équilibre entre bonne dynamique du groupe classe du point
|
||||
de vue de l'ambiance et du travail et la limitation des bavardages, de l'inattention.
|
||||
|
||||
Voulant également passer la certification ISN, il me fallait trouver un projet
|
||||
qui m'intéressait et qui semblait pertinent pour le dossier à présenter.
|
||||
|
||||
|
||||
|
||||
*****************
|
||||
Solution proposée
|
||||
*****************
|
||||
|
||||
*La solution proposée doit:
|
||||
- utiliser Python
|
||||
- être simple d'utilisation (IHM, import de la liste des élèves,...)
|
||||
- proposer un placement automatique des élèves en fonction de certaines contraintes
|
||||
- permettre d'éditer cette solution de positionnement ou d'en créer une
|
||||
- permettre de modifier les contraintes*
|
||||
|
||||
|
||||
|
||||
**********
|
||||
Conception
|
||||
**********
|
||||
|
||||
|
||||
1ère phase de recherche
|
||||
=======================
|
||||
|
||||
J'ai tout d'abord commencé par faire des recherches sur cette thématique.
|
||||
Au hasard de mes recherches, je suis tombé sur une application web, `xperprof`_,
|
||||
permettant de génerer un plan de classe.
|
||||
Cette application propose une fonction de positionnement aléatoire des élèves et
|
||||
notamment la capacité de ne pas soumettre certaines tables à l'algorithme et ainsi
|
||||
créer des zones vide autour de certains élèves.
|
||||
Une première idée d'algorithme m'est venue : le positionnement aléatoire.
|
||||
|
||||
J'ai toutefois continué mes recherches et ai vu une analogie de mon projet avec
|
||||
un jeu d'échec.
|
||||
Un article du blog de `Jean-François GAZET`_ au sujet la création d'un jeu d'échec m'a apporter quelques idées
|
||||
pour mon développement. Il m'a aider à définir quelques éléments de l'implémentation, à savoir :
|
||||
|
||||
- l'IHM: mode console ou graphique ?
|
||||
- la façon de représenter un élève, un plan de classe
|
||||
- la notion de "critères" que j'ai requalifié de "contraintes" pour mon projet
|
||||
- la stratégie algorithmique pour proposer une solution
|
||||
|
||||
Restait un élément à préciser : le format de données utilisé pour contenir la liste des élèves.
|
||||
Mais ce dernier point n'était qu'une formalité.
|
||||
|
||||
|
||||
IHM
|
||||
===
|
||||
|
||||
J'ai tout de suite souhaité que mon application fonctionne en mode console pour deux raisons :
|
||||
|
||||
- la première :
|
||||
Pour moi une application doit pouvoir fonctionner en mode console et pouvoir proposer
|
||||
une interface graphique au besoin.
|
||||
J'espérais ainsi passer la majeure partie du temps pour développer l'API et passer le reste sur
|
||||
l'Interface-Homme-Machine.
|
||||
- la seconde (qui est une conséquence de la première) :
|
||||
Compte-tenu du délai qui m'était imposé je ne voulais pas passer trop de temps dans l'apprentissage
|
||||
d'une librairie graphique.
|
||||
|
||||
|
||||
Format de la liste des élèves
|
||||
=============================
|
||||
|
||||
Afin de charger directement dans l'application une liste des élèves à manipuler,
|
||||
il est nécessaire de choisir un format de fichier.
|
||||
|
||||
Le format **.csv** est celui retenu, de part sa simplicité à le générer.
|
||||
Le séparateur de champ sera le point-virgule.
|
||||
|
||||
L'en-tête du ficher élèves est la suivante :
|
||||
|
||||
.. figure:: _static/figure2.png
|
||||
:align: center
|
||||
:scale: 60 %
|
||||
|
||||
|
||||
Classes ``Student`` et ``SeatingPlan``
|
||||
======================================
|
||||
|
||||
Dans le contexte de la classe, un **élève** est un individu portant certains attributs, comme
|
||||
un nom, un prénom, une liste d'amis dans cette même classe ou à l'extérieur,...
|
||||
Un **plan de classe** également peut porter certaines propriétés, comme ses dimensions,
|
||||
la liste des sièges disponibles, leurs occupants,...
|
||||
|
||||
Ces deux éléments peuvent de surcroît interagir.
|
||||
|
||||
Il est alors apparu évident de considérer, d'un point de vue informatique, ces notions
|
||||
comme des structures de données à part entière.
|
||||
Ainsi leur définition, leur création, leur manipulation s'en trouveraient facilitées.
|
||||
|
||||
Dès lors, le choix du paradigme de programmation orientée objet a été décidé.
|
||||
|
||||
Deux classes, ont donc été définies pour cela :
|
||||
|
||||
- la classe ``Student``, pour définir les élèves
|
||||
- la classe ``SeatingPlan``, pour définir le plan de classe
|
||||
|
||||
Student
|
||||
-------
|
||||
|
||||
Un élève est défini par :
|
||||
|
||||
- son nom
|
||||
- un niveau de bavardage
|
||||
- une liste d'amis
|
||||
|
||||
::
|
||||
|
||||
def __init__(self, name, chat_lvl=0, friends=[]):
|
||||
""" Instancie un objet plan de classe.
|
||||
|
||||
:param name:
|
||||
Nom de l'élève.
|
||||
:type name: str
|
||||
:param chat_lvl:
|
||||
Coefficient ou niveau de bavardage.
|
||||
:type chat_lvl: int
|
||||
:param friends:
|
||||
Liste d'amis.
|
||||
:type friends: list
|
||||
"""
|
||||
self.name = name
|
||||
self.chat_lvl = chat_lvl
|
||||
self.friends = friends
|
||||
|
||||
SeatingPlan
|
||||
-----------
|
||||
|
||||
Un plan de classe est défini par :
|
||||
|
||||
- un nombre de rangées
|
||||
- un nombre de colonnes
|
||||
- une "cartographie" de la classe
|
||||
|
||||
À l'origine, la cartographie de mon plan de classe prenait la forme d'une liste
|
||||
imbriquée, comme dans `jepychess`_.
|
||||
Cette représentation a évolué pour une structure plus souple et surtout en adéquation
|
||||
avec celle utilisée dans l'algorithme de calcul.
|
||||
|
||||
::
|
||||
|
||||
def __init__(self, row=5, col=8):
|
||||
""" Instancie un objet plan de classe.
|
||||
|
||||
:param row:
|
||||
Nombre de rangées dans la classe.
|
||||
:type row: int
|
||||
:param col:
|
||||
Nombre de colonnes dans la classe.
|
||||
:type col: int
|
||||
:param mapping:
|
||||
Représentation du plan de la classe.
|
||||
:type mapping: dict
|
||||
"""
|
||||
self.row = row
|
||||
self.col = col
|
||||
self.mapping = {(i, j): None for i in range(row)
|
||||
for j in range(col)}
|
||||
|
||||
|
||||
Les contraintes
|
||||
===============
|
||||
|
||||
Les contraintes sont les conditions suivant lesquelles on accepte que deux
|
||||
élèves soient voisins dans le plan de classe.
|
||||
|
||||
Pour commencer, celles-ci seront "naïves". C'est-à-dire qu'elles seront basées
|
||||
sur une valeur entière représentant le niveau de bavardage de l'élève.
|
||||
Puis, elles sreont redéfinies pour prendre appui sur les relations sociales
|
||||
entre les élèves (qui est ami avec ami).
|
||||
|
||||
Ainsi, deux élèves pourront être voisins si la différence entre leur niveaux
|
||||
respectifs de bavardage est d'au moins une valeur fixée.
|
||||
De la même manière, deux élèves pourront être voisins, si leur lien social
|
||||
est minimal.
|
||||
|
||||
Les niveaux ou coefficients de bavardage seront définis comme suit :
|
||||
|
||||
.. figure:: _static/figure1.png
|
||||
:align: center
|
||||
:scale: 60 %
|
||||
|
||||
|
||||
2nde phase de recherches : l'algorithme ?
|
||||
=========================================
|
||||
|
||||
Balbutiements
|
||||
-------------
|
||||
|
||||
Initialement, en me basant sur l'exemple de `xperprof`_, j'ai réalisé un placement
|
||||
aléatoire sous contrainte, des élèves dans le plan de classe.
|
||||
Le résultat fût concluant mais chaque lancement de cet algorithme produisait une seule proposition
|
||||
qui pouvait ou non, être complète en raison de contraintes parfois trop fortes.
|
||||
|
||||
La première difficulté est ainsi apparue: satisfaire des contraintes tout en proposant des solutions possibles.
|
||||
|
||||
L'algorithme **alpha-beta** cité dans le blog de `Jean-François GAZET`_ m'a dirigé vers les méthode de résolution
|
||||
de jeu d'échec, de dames ou plus généralement de problèmes NP-complet.
|
||||
Mais également sur le fait de considérer les différentes étapes de recherche de complétion du plan de classe comme
|
||||
un arbre de recherche.
|
||||
|
||||
Mes diverses lectures ainsi que des recherches sur l'Internet m'ont amené au problème des N-Reines.
|
||||
Diverses méthodes de programmation permettent de résoudre ce problème : comme la programmation par contraintes.
|
||||
|
||||
Résoudre un problème de satisfaction de contraintes... ?
|
||||
--------------------------------------------------------
|
||||
|
||||
J'ai vu la résolution de ma problématique comme un problème de satisfaction de contraintes et me suis mis en
|
||||
quête de réponses concernant la programmation par contraintes.
|
||||
|
||||
Plusieurs possibilités
|
||||
|
||||
Je me suis alors tourné vers un algorithme avec retour sur trace.
|
||||
|
||||
L'algorithme au final
|
||||
---------------------
|
||||
|
||||
Validateur de contraintes
|
||||
"""""""""""""""""""""""""
|
||||
|
||||
::
|
||||
|
||||
def respect_constraints(self, seat, student, seatingplan, solution):
|
||||
"""Indique si une place associée à une élève satisfait aux
|
||||
contraintes, à savoir si :
|
||||
|
||||
- l'élève n'a pas déjà été positionné
|
||||
- la place n'est pas déjà occupée
|
||||
- la place est libre, que son voisinage est composé de bons voisins
|
||||
en termes de bavardages.
|
||||
|
||||
:param seat:
|
||||
Place que l'on veut associer à un élève.
|
||||
:type seat: tuple
|
||||
:param student:
|
||||
Élève que l'on veut associer à une place.
|
||||
:type student: Student
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
:param solution:
|
||||
Associations 'place - élève' valides.
|
||||
:type solution: dict
|
||||
|
||||
:rtype: bool
|
||||
|
||||
.. todo::
|
||||
- Modifier le traitement des contraintes de façon à tolérer
|
||||
un certains nombre de voisins ne satisfaisants pas aux
|
||||
contraintes.
|
||||
- Ajouter les liens sociaux comme contraintes supplémentaires.
|
||||
"""
|
||||
# L'association en cours est supposée valide
|
||||
result = True
|
||||
# Si l'élève en cours a déjà été traité, l'association est invalide
|
||||
if student in solution.values():
|
||||
result = False
|
||||
# return False
|
||||
# Si la place en cours a déjà été traitée, l'association est invalide
|
||||
elif seat in solution.keys():
|
||||
result = False
|
||||
# return False
|
||||
else:
|
||||
# Si non, veŕifions le voisinage de la place
|
||||
# Récupérons le voisinage de la place en cours
|
||||
neighbourhood = self.get_neighbourhood(seatingplan,
|
||||
seat,
|
||||
self.NEIGHBOURHOOD_RADIUS)
|
||||
|
||||
for neighbour_seat in neighbourhood:
|
||||
# On ne teste les contraintes que sur les sièges déjà occupés
|
||||
# donc sur ceux faisant déjà partie des solutions
|
||||
if neighbour_seat in list(solution.keys()):
|
||||
# On récupère l'élève du siège voisin
|
||||
neighbour = solution[neighbour_seat]
|
||||
|
||||
# Lequel de l'élève en cours ou du voisin
|
||||
# est le plus bavard ?
|
||||
max_chat_lvl = max(student.chat_lvl,
|
||||
neighbour.chat_lvl)
|
||||
|
||||
# Les contraintes portent sur la différence de
|
||||
# coefficient de bavardage entre deux voisins:
|
||||
if max_chat_lvl == 5:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_5)
|
||||
|
||||
elif max_chat_lvl == 4:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_4)
|
||||
|
||||
elif max_chat_lvl == 3:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_3)
|
||||
|
||||
elif max_chat_lvl == 2:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_2)
|
||||
|
||||
elif max_chat_lvl == 1:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_1)
|
||||
|
||||
else:
|
||||
result = result and True
|
||||
# Le siège voisin n'est pas occupé, il est donc valide
|
||||
result = result and True
|
||||
# retournons la validité de l'association
|
||||
return result
|
||||
|
||||
Le solveur
|
||||
""""""""""
|
||||
|
||||
::
|
||||
|
||||
def solve(self, seatingplan, students_list):
|
||||
"""Cherche pour une liste de places et une liste d'élèves, un ensemble
|
||||
de dispositions dans un plan de classe respectueuses de certaines
|
||||
contraintes décrites dans la fonction 'respect_constraints' ci-dessus.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
:param students_list:
|
||||
Liste d'élèves à positionner dans le plan de classe.
|
||||
:type students_list: list
|
||||
:param solution:
|
||||
Solution de placement proposée.
|
||||
:type solutions: dict
|
||||
|
||||
:return:
|
||||
Disposition des élèves dans le plan de classe.
|
||||
:rtype: generator
|
||||
"""
|
||||
# Liste des places
|
||||
seats_list = list(seatingplan.mapping.keys())
|
||||
|
||||
# Quantités de places et d'élèves à traiter
|
||||
number_of_seats = len(seats_list)
|
||||
number_of_students = len(students_list)
|
||||
|
||||
# On stocke nos associations 'place - élève' dans un dictionnaire
|
||||
solution = {}
|
||||
|
||||
# Indexes de départ dans nos listes de places et d'élèves
|
||||
idx_seat = 0
|
||||
idx_student = 0
|
||||
|
||||
# Retour sur trace (backtracking)
|
||||
backtrack = False
|
||||
# Fin du parcours
|
||||
end = False
|
||||
|
||||
# On commence notre recherche
|
||||
while not end:
|
||||
# On ne revient pas encore en arrière
|
||||
while not backtrack:
|
||||
# Place à traiter
|
||||
current_seat = seats_list[idx_seat]
|
||||
# Élève à traiter
|
||||
current_student = students_list[idx_student]
|
||||
|
||||
# L'association 'place - élève' est-elle satisfaisante ?
|
||||
if self.respect_constraints(current_seat,
|
||||
current_student,
|
||||
seatingplan,
|
||||
solution):
|
||||
# Oui, alors associons l'élève à cette place
|
||||
solution[current_seat] = current_student
|
||||
|
||||
# Est-ce qu'on a traité tous les élèves dans
|
||||
# notre parcourt ?
|
||||
if (idx_student == number_of_students-1):
|
||||
# Oui, alors renvoyons notre solution
|
||||
yield {p: s for p, s in solution.items()}
|
||||
del solution[current_seat]
|
||||
|
||||
# A-t'on traité toutes les places ?
|
||||
if (idx_seat != number_of_seats-1):
|
||||
# Non, on passe à la suivante
|
||||
idx_seat = idx_seat + 1
|
||||
else:
|
||||
# Oui, on revient en arrière
|
||||
backtrack = True
|
||||
else:
|
||||
# Non, on passe au suivant en recommençant à la
|
||||
# première place
|
||||
idx_seat = 0
|
||||
idx_student = idx_student + 1
|
||||
|
||||
# Les contraintes n'ont pas été satisfaites,
|
||||
# il nous reste des places à traiter
|
||||
elif (idx_seat != number_of_seats-1):
|
||||
# On passe à la suivante
|
||||
idx_seat = idx_seat + 1
|
||||
|
||||
else:
|
||||
# Si non, on revient en arrière
|
||||
backtrack = True
|
||||
|
||||
# Sommes-nous remonté jusqu'au premier élève ?
|
||||
end = (idx_student == 0)
|
||||
|
||||
# On revient sur nos pas...
|
||||
while (backtrack and not end):
|
||||
# Prenons l'élève précédent
|
||||
idx_student = idx_student - 1
|
||||
current_student = students_list[idx_student]
|
||||
# On récupère (et on enlève des solutions) la place qui lui
|
||||
# est associée
|
||||
current_seat = list(solution.keys())[list(solution.values())
|
||||
.index(current_student)]
|
||||
solution.pop(current_seat)
|
||||
idx_seat = seats_list.index(current_seat)
|
||||
|
||||
# Il nous reste des places
|
||||
if (idx_seat != number_of_seats-1):
|
||||
# Prenons la place suivante et stoppons notre retour
|
||||
# en arrière
|
||||
idx_seat = idx_seat + 1
|
||||
backtrack = False
|
||||
# Ou nous sommes revenu au premier élève, on stop là
|
||||
elif idx_student == 0:
|
||||
end = True
|
||||
|
||||
|
||||
|
||||
********************************
|
||||
Bilan et améliorations possibles
|
||||
********************************
|
||||
|
||||
L'algorithme fonctionne bien dès lors que l'échantillon d'élèves n'est pas trop grand.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.. _xperprof: https://www.xperprof.fr/generateur_plan_de_classe
|
||||
.. _Jean-François GAZET: https://fr.jeffprod.com/blog/2014/comment-programmer-un-jeu-dechecs.html
|
||||
.. _jepychess: `Jean-François GAZET`_
|
9
docs/references.rst
Normal file
9
docs/references.rst
Normal file
@ -0,0 +1,9 @@
|
||||
**********
|
||||
Références
|
||||
**********
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
references/bibliography
|
17
docs/references/bibliography.rst
Normal file
17
docs/references/bibliography.rst
Normal file
@ -0,0 +1,17 @@
|
||||
*************
|
||||
Bibliographie
|
||||
*************
|
||||
|
||||
.. rubric::
|
||||
|
||||
.. [CAC+14] A. Cohen, J-P. Archambault, C. Cimelli, "Informatique et sciences du numérique - Edition spéciale Python !",
|
||||
Eyrolles, 2014
|
||||
|
||||
.. [GRU17] J. Grus, "Data Science par la pratique : Fondamentaux avec Python",
|
||||
Eyrolles, 2017
|
||||
|
||||
.. [SWI12] G. Swinnen, "Apprendre à programmer avec Python 3",
|
||||
Eyrolles, 2012
|
||||
|
||||
.. [LeG14] V. Le Goff, "Apprenez à programmer en Python", 2e Édition,
|
||||
OpenClassrooms - ex-Site du Zéro, 2014
|
6
docs/references/webography.rst
Normal file
6
docs/references/webography.rst
Normal file
@ -0,0 +1,6 @@
|
||||
***********
|
||||
Webographie
|
||||
***********
|
||||
|
||||
.. _xperprof: https://www.xperprof.fr/generateur_plan_de_classe
|
||||
.. _Jean-François GAZET: https://fr.jeffprod.com/blog/2014/comment-programmer-un-jeu-dechecs.html
|
314
main.py
Executable file
314
main.py
Executable file
@ -0,0 +1,314 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Name: main.py
|
||||
Author: Jeff LANCE <jeff.lance@mala.fr>
|
||||
Date: 29/03/2018
|
||||
|
||||
Générateur de plan de classe.
|
||||
|
||||
Cette application nécessite une liste d'élèves donnée sous forme d'un ficher
|
||||
CSV d'en-tête "ID";"NAME";"CHAT LEVEL", avec:
|
||||
|
||||
ID: Numéro d'identification arbitraire de l'élève.
|
||||
NAME: Nom de l'élève.
|
||||
CHAT LEVEL: Coefficient de bavardage de l'élève.
|
||||
"""
|
||||
|
||||
|
||||
# Imports de modules
|
||||
import random
|
||||
from itertools import islice
|
||||
from sage import SeatingPlan, Engine, interface
|
||||
|
||||
|
||||
# Instanciation des objets
|
||||
seatingplan = SeatingPlan()
|
||||
engine = Engine()
|
||||
|
||||
|
||||
# Fonctions du menu
|
||||
def mainmenu_option1():
|
||||
""" Option 1 du menu principal.
|
||||
|
||||
Redimensionne le plan de classe selon le nombre de rangées et de colonnes
|
||||
demandés à l'utilisateur.
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
# on demande les nouvelles dimensions du plan de classe
|
||||
row, col = interface.console_ask_seatingplan_size()
|
||||
|
||||
if row is not None and col is not None:
|
||||
# on redimensionne le plan de classe
|
||||
seatingplan.resize(row, col)
|
||||
|
||||
|
||||
def mainmenu_option2():
|
||||
"""Option 2 du menu principal.
|
||||
|
||||
Demande à l'utilisateur le fichier contenant la liste des élèves à
|
||||
intégrer dans le plan de classe.
|
||||
|
||||
:return:
|
||||
Rien ou la liste des élèves.
|
||||
:rtype: list
|
||||
"""
|
||||
filepath = interface.dialog_ask_file()
|
||||
# L'utilisateur a-t'il annulé sa recherche de fichier ?
|
||||
if filepath:
|
||||
# Non, on renvoie le fichier ouvert
|
||||
dataframe = interface.open_file(filepath)
|
||||
students_list = interface.load_users(dataframe, seatingplan)
|
||||
return students_list
|
||||
else:
|
||||
# Oui
|
||||
print('Opération annulée')
|
||||
|
||||
|
||||
def mainmenu_option3(students_list):
|
||||
"""Option 3 du menu principal.
|
||||
|
||||
Affiche la liste des élèves dans la console, si celle-ci n'est pas vide.
|
||||
|
||||
:param students_list:
|
||||
Liste des élèves.
|
||||
:type students_list: list
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
if students_list:
|
||||
# on affiche la liste des élèves
|
||||
interface.console_display_students(students_list)
|
||||
else:
|
||||
print("Votre liste d'élèves est vide")
|
||||
|
||||
|
||||
def mainmenu_option4(students_list):
|
||||
"""Option 4 du menu principal.
|
||||
|
||||
Demande à l'utilisateur l'identifiant d'un élève ainsi que les coordonnées
|
||||
d'une place dans le plan de classe.
|
||||
Positionne cet élève à la place indiquée.
|
||||
|
||||
:param students_list:
|
||||
Liste des élèves.
|
||||
:type students_list: list
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
# Si la liste contient des élèves
|
||||
if students_list:
|
||||
# On demande l'ID de l'élève
|
||||
student_id = interface.console_ask_student_id()
|
||||
# Si on a pu récupérer une valeur
|
||||
if student_id is not None:
|
||||
# On récupère l'élève associé à l'ID
|
||||
student = students_list[student_id]
|
||||
# On demande le siège où l'asseoir
|
||||
student_seat = interface.console_ask_student_seat()
|
||||
# Si on a pu récupérer une valeur
|
||||
if seatingplan.is_a_seat(student_seat):
|
||||
# On positionne l'élève
|
||||
seatingplan.place_student(student, student_seat)
|
||||
else:
|
||||
print("Votre liste d'élèves est vide")
|
||||
|
||||
|
||||
def mainmenu_option5(students_list):
|
||||
"""Option 5 du menu principal.
|
||||
|
||||
Demande à l'utilisateur l'identifiant d'un élève.
|
||||
Retire cet élève du plan de classe.
|
||||
|
||||
:param students_list:
|
||||
Liste des élèves.
|
||||
:type students_list: list
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
# Si la liste contient des élèves
|
||||
if students_list:
|
||||
# on demande l'ID de l'élève
|
||||
student_id = interface.console_ask_student_id()
|
||||
# Si on a pu récupérer une valeur
|
||||
if student_id is not None:
|
||||
# On récupère l'élève associé à l'ID
|
||||
student = students_list[student_id]
|
||||
# Et on le retire de sa place
|
||||
seatingplan.remove_student(student)
|
||||
else:
|
||||
print("Votre liste d'élèves est vide")
|
||||
|
||||
|
||||
def mainmenu_option6(students_list):
|
||||
"""Option 6 du menu principal.
|
||||
|
||||
Demande à l'utilisateur les identifiants de deux élèves.
|
||||
Échange les places de ces élèves.
|
||||
|
||||
:param students_list:
|
||||
Liste des élèves.
|
||||
:type students_list: list
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
# On demande l'ID du 1er élève
|
||||
print('1er élève')
|
||||
id1 = interface.console_ask_student_id()
|
||||
# Si on a pu récupérer une valeur
|
||||
if id1 is not None:
|
||||
# On demande l'ID du 2nd élève
|
||||
print('2ème élève')
|
||||
id2 = interface.console_ask_student_id()
|
||||
# Si on a pu récupérer une valeur
|
||||
if id2 is not None:
|
||||
# On échange les deux élèves de place
|
||||
seatingplan.swap_students(students_list[id1], students_list[id2])
|
||||
|
||||
|
||||
def mainmenu_option7(students_list):
|
||||
"""Option 7 du menu principal.
|
||||
|
||||
Démarre une recherche des agencements possibles des élèves dans le plan
|
||||
de classe.
|
||||
|
||||
:param students_list:
|
||||
Liste des élèves.
|
||||
:type students_list: list
|
||||
|
||||
:return:
|
||||
Plan de classe possible.
|
||||
:rtype: iterator
|
||||
"""
|
||||
number_of_proposals = input('Nombre maximal de propositions à faire : ')
|
||||
number_of_proposals = int(number_of_proposals)
|
||||
print('Presser Ctrl-C pour interrompre...')
|
||||
try:
|
||||
return iter(list(islice(engine.solve(seatingplan, students_list),
|
||||
number_of_proposals)))
|
||||
# return iter(list(engine.solve(seatingplan, students_list)))
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
else:
|
||||
print('Appuyer sur "n" pour afficher la disposition suivante...')
|
||||
|
||||
|
||||
def mainmenu_option8():
|
||||
"""Option 8 du menu principal.
|
||||
|
||||
Efface le contenu du plan de classe courant.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
print('Effacement du plan de classe...')
|
||||
engine.flush_seatingplan(seatingplan)
|
||||
|
||||
|
||||
def mainmenu_option_next(solution):
|
||||
"""Option n du menu principal.
|
||||
|
||||
Affiche l'agencement de plan de classe possible suivant.
|
||||
|
||||
:param solution:
|
||||
Agencement de plan de classe.
|
||||
:type solution: dict
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
if solution:
|
||||
try:
|
||||
engine.flush_seatingplan(seatingplan)
|
||||
engine.write_solution_to_seatingplan(next(solution), seatingplan)
|
||||
except StopIteration:
|
||||
print("Il n'y a plus de propositions de placement")
|
||||
else:
|
||||
print("Vous devez le calcul de solutions d'abord")
|
||||
|
||||
|
||||
def settingsmenu():
|
||||
""" Menu paramètres.
|
||||
|
||||
Affiche le menu paramètres dans la console.
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
while True:
|
||||
interface.console_display_settingsmenu(engine)
|
||||
command = input('>>> ')
|
||||
|
||||
if (command == 'b'):
|
||||
return None
|
||||
elif not command:
|
||||
print('Choisissez une option')
|
||||
else:
|
||||
interface.console_set_engine_levels(engine, command)
|
||||
|
||||
|
||||
def mainmenu():
|
||||
""" Menu principal.
|
||||
|
||||
Affiche le menu principal dans la console.
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
while True:
|
||||
# affichage de l'interface et du prompt
|
||||
interface.console_display(seatingplan)
|
||||
command = input('>>> ')
|
||||
|
||||
if (command == '1'):
|
||||
mainmenu_option1()
|
||||
|
||||
elif (command == '2'):
|
||||
students_list = mainmenu_option2()
|
||||
|
||||
elif (command == '3'):
|
||||
mainmenu_option3(students_list)
|
||||
|
||||
elif (command == '4'):
|
||||
mainmenu_option4(students_list)
|
||||
|
||||
elif (command == '5'):
|
||||
mainmenu_option5(students_list)
|
||||
|
||||
elif (command == '6'):
|
||||
mainmenu_option6(students_list)
|
||||
|
||||
elif (command == '7'):
|
||||
proposals = mainmenu_option7(students_list)
|
||||
|
||||
elif (command == '8'):
|
||||
mainmenu_option8()
|
||||
|
||||
elif (command == 'n'):
|
||||
mainmenu_option_next(proposals)
|
||||
|
||||
elif (command == 'DEBUG'):
|
||||
def f(s):
|
||||
if s is not None:
|
||||
return s.name
|
||||
return s
|
||||
|
||||
# DEBUG: print solutions
|
||||
for s in engine.solve(seatingplan, students_list):
|
||||
print({p: f(s) for p, s in s.items()})
|
||||
# DEBUG
|
||||
|
||||
elif (command == 's'):
|
||||
settingsmenu()
|
||||
|
||||
elif (command == 'q'):
|
||||
exit(0)
|
||||
|
||||
|
||||
mainmenu()
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
colorclass==2.2.0
|
||||
numpy==1.14.3
|
||||
pandas==0.22.0
|
||||
six==1.11.0
|
||||
terminaltables==3.1.0
|
9
sage/__init__.py
Normal file
9
sage/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""
|
||||
Name: __init__.py
|
||||
Author: Jeff LANCE <jeff.lance@mala.fr>
|
||||
Date: 29/03/2018
|
||||
"""
|
||||
|
||||
from .student import Student
|
||||
from .seatingplan import SeatingPlan
|
||||
from .engine import Engine
|
383
sage/engine.py
Normal file
383
sage/engine.py
Normal file
@ -0,0 +1,383 @@
|
||||
"""
|
||||
Name: engine.py
|
||||
Author: Jeff LANCE <jeff.lance@mala.fr>
|
||||
Date: 29/03/2018
|
||||
|
||||
Définition d'une classe Engine.
|
||||
|
||||
Contient les éléments intervenant dans les calculs de recherche de solutions.
|
||||
"""
|
||||
|
||||
|
||||
class Engine:
|
||||
"""Le moteur de calculs qui travaille sur un plan de classe."""
|
||||
|
||||
def __init__(self):
|
||||
"""Instancie un objet Engine.
|
||||
|
||||
:ivar NEIGHBORHOOD_RADIUS:
|
||||
Rayon du voisinage.
|
||||
:vartype NEIGHBORHOOD_RADIUS: int
|
||||
:ivar DELTA_FOR_MAX_CHAT_LVL_1:
|
||||
Différence minimale entre le coefficient de bavardage d'un élève et
|
||||
ses voisins lorsque leur coefficient maximal vaut 1.
|
||||
:vartype DELTA_FOR_MAX_CHAT_LVL_1: int
|
||||
:ivar DELTA_FOR_MAX_CHAT_LVL_2:
|
||||
Différence minimale entre le coefficient de bavardage d'un élève et
|
||||
ses voisins lorsque leur coefficient maximal vaut 2.
|
||||
:vartype DELTA_FOR_MAX_CHAT_LVL_32 int
|
||||
:ivar DELTA_FOR_MAX_CHAT_LVL_3:
|
||||
Différence minimale entre le coefficient de bavardage d'un élève et
|
||||
ses voisins lorsque leur coefficient maximal vaut 3.
|
||||
:vartype DELTA_FOR_MAX_CHAT_LVL_3: int
|
||||
:ivar DELTA_FOR_MAX_CHAT_LVL_4:
|
||||
Différence minimale entre le coefficient de bavardage d'un élève et
|
||||
ses voisins lorsque leur coefficient maximal vaut 4.
|
||||
:vartype DELTA_FOR_MAX_CHAT_LVL_4: int
|
||||
:ivar DELTA_FOR_MAX_CHAT_LVL_5:
|
||||
Différence minimale entre le coefficient de bavardage d'un élève et
|
||||
ses voisins lorsque leur coefficient maximal vaut 5.
|
||||
:vartype DELTA_FOR_MAX_CHAT_LVL_5: int
|
||||
"""
|
||||
self.NEIGHBOURHOOD_RADIUS = 1
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_1 = 0
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_2 = 1
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_3 = 1
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_4 = 2
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_5 = 2
|
||||
|
||||
def get_neighbourhood(self, seatingplan, seat, radius):
|
||||
"""Dans un plan de classe, retourne le voisinage dans un rayon donné
|
||||
d'une place.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
:param seat:
|
||||
Coordonnées de la place dans le plan de classe.
|
||||
:type seat: tuple
|
||||
:param radius:
|
||||
Rayon du voisinage.
|
||||
:type radius: int
|
||||
|
||||
:return:
|
||||
Liste des places voisines.
|
||||
:rtype: list
|
||||
"""
|
||||
# La liste des voisins
|
||||
neighbourhood = []
|
||||
# 'position' est un tuple de coordonnées (i, j) où:
|
||||
# - i compris entre 0 et seatingplan.row-1
|
||||
# - j compris entre 0 et seatingplan.col-1
|
||||
#
|
||||
# Les voisins sont les élèves aux places de coordonnées situées
|
||||
# dans le carré passant par les points suivants :
|
||||
# (i, j-radius), (i, j+radius), (i-radius, j), (i+radius, j),
|
||||
# (i-radius, j-radius), (i-radius, j+radius),
|
||||
# (i+radius, j-radius) et (i+radius, j+radius)
|
||||
# On analyse ce voisinage et on n'ajoute un voisin que s'il y en a un
|
||||
for i in range(-radius, radius+1, 1):
|
||||
for j in range(-radius, radius+1, 1):
|
||||
# Traitons la place voisine
|
||||
neighbour_seat = (seat[0]+i, seat[1]+j)
|
||||
# print("Place: ", neighbour_seat) # DEBUG #
|
||||
# Cette place est-elle ?
|
||||
# - différente de la place autour de laquelle on regarde
|
||||
# - bien comprise dans le plan de classe (coordonnées de
|
||||
# dépassant pas celles du plan de classe)
|
||||
if (neighbour_seat != seat and
|
||||
0 <= neighbour_seat[0] <= seatingplan.row-1 and
|
||||
0 <= neighbour_seat[1] <= seatingplan.col-1):
|
||||
neighbourhood.append(neighbour_seat)
|
||||
# On retourne notre liste de places voisines
|
||||
return neighbourhood
|
||||
|
||||
def are_safe_neighbours(self, student, neighbour, delta_chat_lvl):
|
||||
"""Indique si deux élèves constituent de bons voisins.
|
||||
C'est-à-dire si la différence de leur coefficient de bavardage
|
||||
est d'une certaine valeur.
|
||||
|
||||
:param student:
|
||||
Élève.
|
||||
:type student: Student
|
||||
:param neighbour:
|
||||
Élève.
|
||||
:type neighbour: Student
|
||||
:param delta_chat_lvl:
|
||||
Différence entre les coefficients de bavardage.
|
||||
:type delta_chat_lvl: int
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
return bool(abs(student.chat_lvl - neighbour.chat_lvl)
|
||||
>= delta_chat_lvl)
|
||||
|
||||
def respect_constraints(self, seat, student, seatingplan, solution):
|
||||
"""Indique si une place associée à une élève satisfait aux
|
||||
contraintes, à savoir si :
|
||||
|
||||
- l'élève n'a pas déjà été positionné
|
||||
- la place n'est pas déjà occupée
|
||||
- la place est libre, que son voisinage est composé de bons voisins
|
||||
en termes de bavardages.
|
||||
|
||||
:param seat:
|
||||
Place que l'on veut associer à un élève.
|
||||
:type seat: tuple
|
||||
:param student:
|
||||
Élève que l'on veut associer à une place.
|
||||
:type student: Student
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
:param solution:
|
||||
Associations 'place - élève' valides.
|
||||
:type solution: dict
|
||||
|
||||
:rtype: bool
|
||||
|
||||
.. todo::
|
||||
- Modifier le traitement des contraintes de façon à tolérer
|
||||
un certains nombre de voisins ne satisfaisants pas aux
|
||||
contraintes.
|
||||
- Ajouter les liens sociaux comme contraintes supplémentaires.
|
||||
"""
|
||||
# L'association en cours est supposée valide
|
||||
result = True
|
||||
# Si l'élève en cours a déjà été traité, l'association est invalide
|
||||
if student in solution.values():
|
||||
result = False
|
||||
# return False
|
||||
# Si la place en cours a déjà été traitée, l'association est invalide
|
||||
elif seat in solution.keys():
|
||||
result = False
|
||||
# return False
|
||||
else:
|
||||
# Si non, veŕifions le voisinage de la place
|
||||
# Récupérons le voisinage de la place en cours
|
||||
neighbourhood = self.get_neighbourhood(seatingplan,
|
||||
seat,
|
||||
self.NEIGHBOURHOOD_RADIUS)
|
||||
|
||||
for neighbour_seat in neighbourhood:
|
||||
# On ne teste les contraintes que sur les sièges déjà occupés
|
||||
# donc sur ceux faisant déjà partie des solutions
|
||||
if neighbour_seat in list(solution.keys()):
|
||||
# On récupère l'élève du siège voisin
|
||||
neighbour = solution[neighbour_seat]
|
||||
|
||||
# Lequel de l'élève en cours ou du voisin
|
||||
# est le plus bavard ?
|
||||
max_chat_lvl = max(student.chat_lvl,
|
||||
neighbour.chat_lvl)
|
||||
|
||||
# Les contraintes portent sur la différence de
|
||||
# coefficient de bavardage entre deux voisins:
|
||||
if max_chat_lvl == 5:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_5)
|
||||
|
||||
elif max_chat_lvl == 4:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_4)
|
||||
|
||||
elif max_chat_lvl == 3:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_3)
|
||||
|
||||
elif max_chat_lvl == 2:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_2)
|
||||
|
||||
elif max_chat_lvl == 1:
|
||||
result = result and self.are_safe_neighbours(
|
||||
student,
|
||||
neighbour,
|
||||
self.DELTA_FOR_MAX_CHAT_LVL_1)
|
||||
|
||||
else:
|
||||
result = result and True
|
||||
# Le siège voisin n'est pas occupé, il est donc valide
|
||||
result = result and True
|
||||
# retournons la validité de l'association
|
||||
return result
|
||||
|
||||
def solve(self, seatingplan, students_list):
|
||||
"""Cherche pour une liste de places et une liste d'élèves, un ensemble
|
||||
de dispositions dans un plan de classe respectueuses de certaines
|
||||
contraintes décrites dans la fonction 'respect_constraints' ci-dessus.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
:param students_list:
|
||||
Liste d'élèves à positionner dans le plan de classe.
|
||||
:type students_list: list
|
||||
:param solution:
|
||||
Solution de placement proposée.
|
||||
:type solutions: dict
|
||||
|
||||
:return:
|
||||
Disposition des élèves dans le plan de classe.
|
||||
:rtype: generator
|
||||
"""
|
||||
# Liste des places
|
||||
seats_list = list(seatingplan.mapping.keys())
|
||||
|
||||
# Quantités de places et d'élèves à traiter
|
||||
number_of_seats = len(seats_list)
|
||||
number_of_students = len(students_list)
|
||||
|
||||
# On stocke nos associations 'place - élève' dans un dictionnaire
|
||||
solution = {}
|
||||
|
||||
# Indexes de départ dans nos listes de places et d'élèves
|
||||
idx_seat = 0
|
||||
idx_student = 0
|
||||
|
||||
# Retour sur trace (backtracking)
|
||||
backtrack = False
|
||||
# Fin du parcours
|
||||
end = False
|
||||
|
||||
# On commence notre recherche
|
||||
while not end:
|
||||
# On ne revient pas encore en arrière
|
||||
while not backtrack:
|
||||
# Place à traiter
|
||||
current_seat = seats_list[idx_seat]
|
||||
# Élève à traiter
|
||||
current_student = students_list[idx_student]
|
||||
|
||||
# L'association 'place - élève' est-elle satisfaisante ?
|
||||
if self.respect_constraints(current_seat,
|
||||
current_student,
|
||||
seatingplan,
|
||||
solution):
|
||||
# Oui, alors associons l'élève à cette place
|
||||
solution[current_seat] = current_student
|
||||
|
||||
# Est-ce qu'on a traité tous les élèves dans
|
||||
# notre parcourt ?
|
||||
if (idx_student == number_of_students-1):
|
||||
# Oui, alors renvoyons notre solution
|
||||
yield {p: s for p, s in solution.items()}
|
||||
del solution[current_seat]
|
||||
|
||||
# A-t'on traité toutes les places ?
|
||||
if (idx_seat != number_of_seats-1):
|
||||
# Non, on passe à la suivante
|
||||
idx_seat = idx_seat + 1
|
||||
else:
|
||||
# Oui, on revient en arrière
|
||||
backtrack = True
|
||||
else:
|
||||
# Non, on passe au suivant en recommençant à la
|
||||
# première place
|
||||
idx_seat = 0
|
||||
idx_student = idx_student + 1
|
||||
|
||||
# Les contraintes n'ont pas été satisfaites,
|
||||
# il nous reste des places à traiter
|
||||
elif (idx_seat != number_of_seats-1):
|
||||
# On passe à la suivante
|
||||
idx_seat = idx_seat + 1
|
||||
|
||||
else:
|
||||
# Si non, on revient en arrière
|
||||
backtrack = True
|
||||
|
||||
# Sommes-nous remonté jusqu'au premier élève ?
|
||||
end = (idx_student == 0)
|
||||
|
||||
# On revient sur nos pas...
|
||||
while (backtrack and not end):
|
||||
# Prenons l'élève précédent
|
||||
idx_student = idx_student - 1
|
||||
current_student = students_list[idx_student]
|
||||
# On récupère (et on enlève des solutions) la place qui lui
|
||||
# est associée
|
||||
current_seat = list(solution.keys())[list(solution.values())
|
||||
.index(current_student)]
|
||||
solution.pop(current_seat)
|
||||
idx_seat = seats_list.index(current_seat)
|
||||
|
||||
# Il nous reste des places
|
||||
if (idx_seat != number_of_seats-1):
|
||||
# Prenons la place suivante et stoppons notre retour
|
||||
# en arrière
|
||||
idx_seat = idx_seat + 1
|
||||
backtrack = False
|
||||
# Ou nous sommes revenu au premier élève, on stop là
|
||||
elif idx_student == 0:
|
||||
end = True
|
||||
|
||||
def write_solution_to_seatingplan(self, solution, seatingplan):
|
||||
"""Place les élèves de la solution dans un plan de classe.
|
||||
|
||||
:param solution:
|
||||
Solution de positionnement des élèves dans le plan de classe.
|
||||
:type solution: dict
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
|
||||
:return: None
|
||||
"""
|
||||
for place, student in solution.items():
|
||||
if student is not None:
|
||||
seatingplan.place_student(student, place)
|
||||
else:
|
||||
seatingplan.mapping[place] = None
|
||||
|
||||
def flush_seatingplan(self, seatingplan):
|
||||
"""Vide le plan de classe. Supprime tous les élèves placés.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
|
||||
:return: None
|
||||
"""
|
||||
for seat in list(seatingplan.mapping.keys()):
|
||||
student = seatingplan.get_student(seat)
|
||||
if student is not None:
|
||||
seatingplan.remove_student(student)
|
||||
|
||||
def get_corners(self, seatingplan):
|
||||
""" Renvoie, pour un plan de classe, la liste des places aux coins
|
||||
ou extrémités.
|
||||
C'est-à-dire une place située en : (0,0), (0,m-1),
|
||||
(n-1,0) ou (n-1,m-1) pour un plan de classe de dimensions n x m.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
|
||||
:return:
|
||||
Liste des places aux coins/extrémités.
|
||||
:rtype: list
|
||||
"""
|
||||
# Dimensions du plan de classe
|
||||
n = seatingplan.raw
|
||||
m = seatingplan.col
|
||||
|
||||
# Le plan de classe est un tableau de dimensions n x m :
|
||||
# - si n>1 et m>1, il possède 4 coins
|
||||
# - si n=1 et m>1, il en possède 2
|
||||
# - si n>1 et m=1, il en possède 2
|
||||
# - si n=1 et m=1, il en possède 1
|
||||
#
|
||||
# On utilise donc un set qui va permettre d'éliminer les doublons de
|
||||
# coordonnées
|
||||
# Ce set est ensuite "traduit" en liste
|
||||
corners = list({(0, 0), (n-1, 0), (0, m-1), (n-1, m-1)})
|
||||
return corners
|
489
sage/interface.py
Normal file
489
sage/interface.py
Normal file
@ -0,0 +1,489 @@
|
||||
"""
|
||||
Name: interface.py
|
||||
Author: Jeff LANCE <jeff.lance@mala.fr>
|
||||
Date: 29/03/2018
|
||||
|
||||
Module d'interface de l'application.
|
||||
|
||||
Contient les éléments permettant de communiquer avec l'utilisateur.
|
||||
"""
|
||||
|
||||
|
||||
# Import de modules
|
||||
import os
|
||||
from tkinter import filedialog
|
||||
from tkinter import *
|
||||
|
||||
import pandas as pd
|
||||
from pandas import errors
|
||||
|
||||
from terminaltables import AsciiTable
|
||||
from colorclass import Color
|
||||
|
||||
from .student import Student
|
||||
|
||||
|
||||
#########################
|
||||
# Interface application #
|
||||
#########################
|
||||
def console_display(seatingplan):
|
||||
"""Affiche le menu et le plan de classe dans la console.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe à afficher.
|
||||
:type seatingplan: SeatingPlan
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
console_display_mainmenu(seatingplan)
|
||||
console_display_seatingplan(seatingplan)
|
||||
|
||||
|
||||
#########
|
||||
# Menus #
|
||||
#########
|
||||
def console_display_mainmenu(seatingplan):
|
||||
"""Affiche le menu principal de l'application dans la console.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe à afficher.
|
||||
:type seatingplan: SeatingPlan
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
|
||||
input('\nAppuyer sur une touche pour continuer...')
|
||||
# On efface l'écran (cls: win, clear: unix)
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
|
||||
print('')
|
||||
print('Bienvenue !')
|
||||
print('')
|
||||
print("=> Entrez le numéro de l'action à effectuer :")
|
||||
print('- 1. pour définir les dimensions du plan de classe: {}x{} '
|
||||
'actuellement'.format(seatingplan.row, seatingplan.col))
|
||||
print("- 2. pour ouvrir et charger une liste d'élèves depuis "
|
||||
'un fichier .csv')
|
||||
print('- 3. pour afficher la liste des élèves chargés précédemment')
|
||||
print("- 4. pour placer un élève à une place précise:\n"
|
||||
' • la place en bas à gauche a pour coordonnées (1,1)\n'
|
||||
' • la place en haut à droite a pour coordonnées ({},{})\n'
|
||||
' (nécessite son ID)'
|
||||
.format(seatingplan.row, seatingplan.col))
|
||||
print("- 5. pour enlever un élève du plan de classe (nécessite son ID)")
|
||||
print('- 6. pour intervertir les places de deux élèves (nécessite les ID)')
|
||||
print('- 7. pour obtenir une liste de plans de classe possibles')
|
||||
print('- 8. pour vider le plan de classe')
|
||||
print('')
|
||||
print("- s. pour paramétrer l'application")
|
||||
print('- q. pour quitter')
|
||||
print('')
|
||||
|
||||
|
||||
def console_display_settingsmenu(engine):
|
||||
"""Affiche le menu de paramétrage du de l'application dans la console.
|
||||
|
||||
:param engine:
|
||||
Moteur de calcul.
|
||||
:type engine: Engine
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
|
||||
input('\nAppuyer sur une touche pour continuer...')
|
||||
# On efface l'écran (cls: win, clear: unix)
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
|
||||
print('')
|
||||
print('Paramètres actuels')
|
||||
print('')
|
||||
print('Delta pour coefficient de bavardage\n'
|
||||
'Max 1 = {} | Max 2 = {} | Max 3 = {} | Max 4 = {} | Max 5 = {}'
|
||||
.format(engine.DELTA_FOR_MAX_CHAT_LVL_1,
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_2,
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_3,
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_4,
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_5))
|
||||
print('')
|
||||
print('- Entrez le numéro du niveau à ajuster\n')
|
||||
print('- b. pour revenir au menu principal')
|
||||
print('')
|
||||
|
||||
|
||||
##################
|
||||
# Plan de classe #
|
||||
##################
|
||||
def console_display_seatingplan(seatingplan):
|
||||
"""Affiche le plan de classe dans la console.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe à afficher.
|
||||
:type seatingplan: SeatingPlan
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
# On transforme le plan de classe en table
|
||||
rendered_seatingplan = _seatingplan_to_table(seatingplan)
|
||||
|
||||
# Quelques ajustements visuels...
|
||||
for i in range(seatingplan.col):
|
||||
rendered_seatingplan.justify_columns[i] = 'center'
|
||||
rendered_seatingplan.padding_left = 2
|
||||
rendered_seatingplan.padding_right = 2
|
||||
rendered_seatingplan.inner_heading_row_border = False
|
||||
rendered_seatingplan.inner_row_border = True
|
||||
# On affiche notre jolie tableau
|
||||
print(rendered_seatingplan.table, '\n')
|
||||
|
||||
|
||||
def _seatingplan_to_table(seatingplan):
|
||||
"""Embellit le plan de classe pour affichage.
|
||||
|
||||
:param seatingplan:
|
||||
Plan de classe à afficher.
|
||||
:type seatingplan: SeatingPlan
|
||||
|
||||
:return:
|
||||
Plan de classe sous forme de tableau.
|
||||
:rtype: AsciiTable
|
||||
"""
|
||||
# On crée un tableau vide de dimensions égales à celle du plan de classe
|
||||
seatingplan_as_table = [[None for j in range(seatingplan.col)]
|
||||
for i in range(seatingplan.row)]
|
||||
|
||||
# Parcourons le plan de classe
|
||||
for place in seatingplan.mapping:
|
||||
# On récupère les coordonnées de la place en cours...
|
||||
x, y = place[0], place[1]
|
||||
# ...on prend son contenu que l'on formate (coloration,...)
|
||||
content = _console_render_student(seatingplan.get_student(place))
|
||||
|
||||
# Si la place est occupée
|
||||
if not seatingplan.is_empty_seat(place):
|
||||
# On rajoute des informations au contenu (id, chat level)
|
||||
addon = 'I: {} - L: {}' \
|
||||
.format(seatingplan.get_student(place).id,
|
||||
seatingplan.get_student(place).chat_lvl)
|
||||
content = content + '\n' + addon
|
||||
|
||||
# On place le contenu dans notre table
|
||||
seatingplan_as_table[seatingplan.row-1-x][y] = content
|
||||
|
||||
# On transforme et on renvoit notre table en tant qu'objet formaté
|
||||
# à l'aide du modul terminaltables
|
||||
return AsciiTable(seatingplan_as_table)
|
||||
|
||||
|
||||
###################
|
||||
# Options du menu #
|
||||
###################
|
||||
#
|
||||
# Option 1
|
||||
#
|
||||
def console_ask_seatingplan_size():
|
||||
"""Demande à l'utilisateur de définir la taille du plan de classe.
|
||||
|
||||
:return:
|
||||
Dimensions du plan de classe.
|
||||
:rtype: tuple or None
|
||||
:raises: ValueError
|
||||
"""
|
||||
# On demande le nombre de rangées
|
||||
# row va contenir une chaîne de caractère
|
||||
row = input('\nVeuillez entrer le nombre de rangées: ')
|
||||
# La valeur entrée est-elle valide (un entier > 0) ?
|
||||
try:
|
||||
# row est convertit en entier, sinon on intercepte une exception
|
||||
row = int(row)
|
||||
# Si pas d'exception...
|
||||
# si row est négative ou nulle, on lève une exception
|
||||
if row <= 0:
|
||||
raise ValueError('Le numéro de rangées doit être strictement \
|
||||
supérieur à zéro')
|
||||
|
||||
# Autrement tout va bien, on passe aunombre de colonnes
|
||||
col = input('\nVeuillez entrer le nombre de colonnes: ')
|
||||
# La valeur entrée est-elle valide (un entier > 0) ?
|
||||
try:
|
||||
# col est convertit en entier, sinon on intercepte une exception
|
||||
col = int(col)
|
||||
# Si pas d'exception...
|
||||
# si col est négative ou nulle, on lève une exception
|
||||
if col <= 0:
|
||||
raise ValueError('Le numéro de colonnes doit être strictement \
|
||||
supérieur à zéro')
|
||||
# Interception de l'exception pour col
|
||||
except ValueError:
|
||||
print('La valeur de colonne est invalide')
|
||||
col = None
|
||||
|
||||
# Interception de l'exception pour row
|
||||
except ValueError:
|
||||
print('La valeur de rangée est invalide')
|
||||
row, col = None, None
|
||||
|
||||
return row, col
|
||||
|
||||
|
||||
#
|
||||
# Option 2
|
||||
#
|
||||
def dialog_ask_file():
|
||||
"""Affiche une boite de dialogue permettant de sélectionner
|
||||
un fichier csv contenant la liste des utilisateurs à affecter
|
||||
dans le plan de classe.
|
||||
|
||||
:return:
|
||||
Chemin absolu du fichier sélectionné.
|
||||
:rtype: str
|
||||
"""
|
||||
root = Tk()
|
||||
# On ne veut pas voir la fenêtre Tkinter
|
||||
root.withdraw()
|
||||
# Affiche une boite de dialogue demandant de sélectionner un fichier
|
||||
# Renvoie le chemin du path qui est stocké dans 'filename'
|
||||
filepath = filedialog.askopenfilename(filetypes=[("CSV", "*.csv")])
|
||||
return filepath
|
||||
|
||||
|
||||
def open_file(filename):
|
||||
""" Ouvre un fichier de données csv.
|
||||
|
||||
:param filename:
|
||||
Chemin du fichier à ouvrir.
|
||||
:type filename: str
|
||||
|
||||
:return:
|
||||
DataFrame pandas
|
||||
:rtype: dict
|
||||
:raises: pd.errors.EmptyDataError
|
||||
"""
|
||||
# On gère l'erreur liée au fait que le fichier soit vide
|
||||
try:
|
||||
dataframe = pd.read_csv(filename, sep=';',
|
||||
header=0, index_col=None).to_dict('records')
|
||||
print('Ouverture du fichier:', filename)
|
||||
return dataframe
|
||||
except pd.errors.EmptyDataError:
|
||||
print('Fichier vide')
|
||||
|
||||
|
||||
def load_users(dataframe, seatingplan):
|
||||
"""Instancie des élèves à partir d'un dataframe.
|
||||
|
||||
Les objets 'student' sont instanciés à partir du dictionnaire
|
||||
'dataframe', contenant :
|
||||
- un id(entifiant) pour l'élève
|
||||
- nom de l'élève
|
||||
- niveau de bavardage de l'élève
|
||||
|
||||
:param dataframe:
|
||||
"Liste" d'élèves.
|
||||
:type dataframe: dict
|
||||
:param seatingplan:
|
||||
Plan de classe.
|
||||
:type seatingplan: SeatingPlan
|
||||
|
||||
:return:
|
||||
Liste d'élèves instanciés.
|
||||
:rtype: list
|
||||
"""
|
||||
students_list = []
|
||||
for i, user in enumerate(dataframe):
|
||||
students_list.append(Student(user['NAME']))
|
||||
students_list[i].id = user['ID']
|
||||
students_list[i].chat_lvl = user['CHAT LEVEL']
|
||||
return students_list
|
||||
|
||||
|
||||
#
|
||||
# Option 3
|
||||
#
|
||||
def console_display_students(students_list):
|
||||
"""Affiche la liste des élèves dans la console.
|
||||
|
||||
:param students_list:
|
||||
Liste d'élèves.
|
||||
:type students_list: list
|
||||
|
||||
:return: None
|
||||
"""
|
||||
students_table = [['ID', 'NAME', 'CHAT LEVEL']]
|
||||
for student in students_list:
|
||||
students_table.append([student.id,
|
||||
_console_render_student(student),
|
||||
student.chat_lvl])
|
||||
rendered_students_table = AsciiTable(students_table)
|
||||
print(rendered_students_table.table)
|
||||
|
||||
|
||||
def _console_render_student(student):
|
||||
"""Formate le nom de l'élève 'student' pour l'affichage en mode
|
||||
console du plan de classe.
|
||||
|
||||
:param student:
|
||||
Élève.
|
||||
:type student: Student
|
||||
|
||||
:return:
|
||||
Nom de l'élève en couleur.
|
||||
:rtype: str
|
||||
"""
|
||||
name = '.'
|
||||
# Est-on bien en présence d'un élève (objet Student) et
|
||||
# pas d'une place vide (None) ?
|
||||
if isinstance(student, Student):
|
||||
# En fonction du coefficient de bavardage de l'élève, on colorie son
|
||||
# nom
|
||||
if 0 <= student.chat_lvl <= 1:
|
||||
name = Color('{green}' + student.name + '{/green}')
|
||||
elif 2 <= student.chat_lvl <= 3:
|
||||
name = Color('{yellow}' + student.name + '{/yellow}')
|
||||
elif 4 <= student.chat_lvl <= 5:
|
||||
name = Color('{red}' + student.name + '{/red}')
|
||||
#
|
||||
# On remplace l'espcace entre le nom et le prénom par un saut de ligne
|
||||
# "incompatible" avec la coloration: provoque une coloration de ligne
|
||||
# name = student.name.replace(' ', '\n', 1)
|
||||
#
|
||||
return name
|
||||
|
||||
|
||||
#
|
||||
# Option 4 et 5
|
||||
#
|
||||
def console_ask_student_id():
|
||||
"""Demande à l'utilisateur l'identifiant d'un élève.
|
||||
|
||||
:return:
|
||||
Identifiant d'un élève.
|
||||
:rtype: int
|
||||
:raises: ValueError
|
||||
"""
|
||||
id = input("Veuillez entrer l'ID de l'élève: ")
|
||||
# La valeur entrée est-elle un entier > 0 ?
|
||||
try:
|
||||
# On s'assure que la valeur entrée est entière en la convertissant
|
||||
# sinon, on intercepte une exception
|
||||
id = int(id)
|
||||
# Si la valeur est entière mais négative stricte
|
||||
if id < 0:
|
||||
# On lève une exception
|
||||
raise ValueError("La valeur d'ID doit être supérieure ou égale "
|
||||
"à zéro")
|
||||
except ValueError:
|
||||
print('La valeur saisie est invalide')
|
||||
else:
|
||||
return id
|
||||
|
||||
|
||||
#
|
||||
# Option 4
|
||||
#
|
||||
def console_ask_student_seat():
|
||||
"""Demande à l'utilisateur les coordonnées d'une place
|
||||
dans le plan de classe.
|
||||
|
||||
:return:
|
||||
Coordonnées d'une place.
|
||||
:rtype: tuple
|
||||
:raises: ValueError
|
||||
"""
|
||||
student_row = input('\nVeuillez entrer le numéro de rangée où vous'
|
||||
" souhaitez placer l'élève: ")
|
||||
# La valeur entrée est-elle un entier > 0 ?
|
||||
try:
|
||||
# On s'assure que la valeur entrée est entière en la convertissant
|
||||
# sinon, on intercepte une exception
|
||||
student_row = int(student_row)
|
||||
# Si la valeur est entière mais négative
|
||||
if student_row <= 0:
|
||||
# On lève une exception
|
||||
raise ValueError('Le numéro de rangée doit être strictement '
|
||||
'positif')
|
||||
|
||||
# Tout va bien, on passe au numéro de colonne
|
||||
student_col = input('\nVeuillez entrer le numéro de colonne où vous'
|
||||
" souhaitez placer l'élève: ")
|
||||
# La valeur entrée est-elle un entier > 0 ?
|
||||
try:
|
||||
# On s'assure que la valeur entrée est entière en la convertissant
|
||||
# sinon, on intercepte une exception
|
||||
student_col = int(student_col)
|
||||
# Si la valeur est entière mais négative
|
||||
if student_col <= 0:
|
||||
# On lève une exception
|
||||
raise ValueError('Le numéro de colonne doit être strictement '
|
||||
'positif')
|
||||
except ValueError:
|
||||
print('La valeur saisie pour le numéro de colonne est invalide')
|
||||
student_col = None
|
||||
|
||||
except ValueError:
|
||||
print('La valeur saisie pour le numéro de rangée est invalide')
|
||||
student_row, student_col = None, None
|
||||
|
||||
# On renvoit les valeurs entrées mais ajustées à l'indexation
|
||||
# du plan de classe
|
||||
return student_row, student_col
|
||||
|
||||
|
||||
#
|
||||
# Option s
|
||||
#
|
||||
def console_set_engine_levels(engine, level):
|
||||
"""Option Level du menu paramètres.
|
||||
|
||||
Propose à l'utilisateur de modifier les contraintes sur les niveaux de
|
||||
bavardage.
|
||||
|
||||
:param engine:
|
||||
Moteur de calculs.
|
||||
:type engine: Engine
|
||||
:param level:
|
||||
Niveau à modifier.
|
||||
:type level: int
|
||||
|
||||
:return:
|
||||
Rien.
|
||||
"""
|
||||
# La valeur passée en paramètres est-elle un entier compris entre 1 et 5 ?
|
||||
try:
|
||||
# On s'assure que la valeur entrée est bien entière
|
||||
level = int(level)
|
||||
# Si elle n'est pas comprise entre 1 et 5
|
||||
if (level < 1 or level > 5):
|
||||
# On lève une exception
|
||||
raise ValueError('La valeur doit être comprise entre 1 et 5')
|
||||
|
||||
# Autrement, on continue en demandant la valeur du coefficient
|
||||
coeff = input('Ajuster coefficient maximal {}: '.format(level))
|
||||
# Cette valeur est-elle un entier positif ou nul ?
|
||||
try:
|
||||
# On s'assure que la valeur entrée est bien entière
|
||||
coeff = int(coeff)
|
||||
# Si elle est strictement négative
|
||||
if coeff < 0:
|
||||
# On lève une exception
|
||||
raise ValueError('La valeur doit être positive ou nulle')
|
||||
except ValueError:
|
||||
print('La valeur saisie est invalide')
|
||||
else:
|
||||
print(coeff)
|
||||
if level == 1:
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_1 = coeff
|
||||
elif level == 2:
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_2 = coeff
|
||||
elif level == 3:
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_3 = coeff
|
||||
elif level == 4:
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_4 = coeff
|
||||
elif level == 5:
|
||||
engine.DELTA_FOR_MAX_CHAT_LVL_5 = coeff
|
||||
|
||||
except ValueError:
|
||||
print('Saisie est invalide')
|
205
sage/seatingplan.py
Normal file
205
sage/seatingplan.py
Normal file
@ -0,0 +1,205 @@
|
||||
"""
|
||||
Name: seatingplan.py
|
||||
Author: Jeff LANCE <jeff.lance@mala.fr>
|
||||
Date: 29/03/2018
|
||||
|
||||
Définition d'une classe 'Plan de classe'.
|
||||
"""
|
||||
|
||||
|
||||
class SeatingPlan:
|
||||
"""Plan de classe.
|
||||
|
||||
Est un ensemble de places disposées suivant un nombre donné de rangées et
|
||||
de colonnes.
|
||||
"""
|
||||
|
||||
def __init__(self, row=5, col=8):
|
||||
"""Instancie un objet plan de classe.
|
||||
|
||||
:param row:
|
||||
Nombre de rangées dans la classe.
|
||||
:type row: int
|
||||
:param col:
|
||||
Nombre de colonnes dans la classe.
|
||||
:type col: int
|
||||
:param mapping:
|
||||
Représentation du plan de la classe sous la forme
|
||||
{(place): (student)}
|
||||
:type mapping: dict
|
||||
"""
|
||||
self.row = row
|
||||
self.col = col
|
||||
self.mapping = {(i, j): None for i in range(row)
|
||||
for j in range(col)}
|
||||
|
||||
def is_full(self):
|
||||
"""Renvoie si le plan de classe est plein ou non.
|
||||
|
||||
:return:
|
||||
True s'il ne reste aucune place vide. False sinon.
|
||||
:rtype: bool
|
||||
"""
|
||||
return None in self.mapping.values()
|
||||
|
||||
def resize(self, row, col):
|
||||
"""Redimensionne le plan de classe.
|
||||
|
||||
:param row:
|
||||
Nombre de rangées.
|
||||
:type row: int
|
||||
:param col:
|
||||
Nombre de colonnes.
|
||||
:type row: int
|
||||
"""
|
||||
self.row = row
|
||||
self.col = col
|
||||
self.mapping = {(i, j): None for i in range(row)
|
||||
for j in range(col)}
|
||||
|
||||
def is_a_seat(self, seat):
|
||||
"""Renvoie si une place est valide ou non.
|
||||
|
||||
Vérifie si les coordonnées de la place sont valides. C'est-à-dire
|
||||
si chacune des coordonnées n'est pas vide et si la place fait bien
|
||||
partie du plan de classe.
|
||||
|
||||
:param seat:
|
||||
Place.
|
||||
:type seat: tuple
|
||||
|
||||
:return:
|
||||
True, si la place est valide et appartient au plan de classe.
|
||||
False, si non.
|
||||
:rtype: bool
|
||||
"""
|
||||
# On suppose par défaut que la place est valide
|
||||
valid = True
|
||||
# Si les coordonnées sont valides
|
||||
if (seat[0] is not None and seat[1] is not None):
|
||||
# Si le siège n'est pas dans le plan de classe
|
||||
if seat not in self.mapping:
|
||||
# La place n'est pas valide
|
||||
valid = False
|
||||
# Si les coordonnées ne le sont pas, la place non plus
|
||||
else:
|
||||
valid = False
|
||||
|
||||
# On retourne la validité
|
||||
return valid
|
||||
|
||||
def get_student(self, seat):
|
||||
"""Renvoie l'élève assis à une place.
|
||||
|
||||
:param seat:
|
||||
Coordonnées de la place dans le plan de classe.
|
||||
:type seat: tuple
|
||||
|
||||
:return: None si personne à cette place.
|
||||
Élève si la place est occupée.
|
||||
:rtype: Student ou None
|
||||
"""
|
||||
if self.is_a_seat(seat):
|
||||
return self.mapping[seat]
|
||||
|
||||
def get_seat(self, student):
|
||||
"""Renvoie la place d'un élève.
|
||||
|
||||
:param student:
|
||||
Élève.
|
||||
:type student: Student
|
||||
|
||||
:return:
|
||||
La place de l'élève dans le plan de classe.
|
||||
False si l'élèves n'est pas dans le plan de classe.
|
||||
:rtype: tuple or False
|
||||
"""
|
||||
try:
|
||||
return list(self.mapping.keys())[list(self.mapping.values())
|
||||
.index(student)]
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def is_empty_seat(self, seat):
|
||||
"""Indique si une place est libre.
|
||||
|
||||
:param seat:
|
||||
Coordonnées de la place dans le plan de classe.
|
||||
:type seat: tuple
|
||||
|
||||
:return:
|
||||
True si la place est vide, False sinon.
|
||||
:rtype: bool
|
||||
"""
|
||||
if self.is_a_seat(seat):
|
||||
return self.get_student(seat) is None
|
||||
return False
|
||||
|
||||
def place_student(self, student, seat):
|
||||
"""Positionne un élève dans le plan de classe.
|
||||
|
||||
:param student:
|
||||
Élève à placer.
|
||||
:type student: Student
|
||||
:param seat:
|
||||
Place à laquelle asseoir l'élève.
|
||||
:type seat: tuple
|
||||
|
||||
:return:
|
||||
True, si la place est libre et que le positionnement s'est
|
||||
bien déroulé.
|
||||
False, si la place est occupée.
|
||||
:rtype: bool
|
||||
"""
|
||||
# On peut placer l'élève a une place si celle-ci est vide
|
||||
if self.is_empty_seat(seat):
|
||||
# On place l'élève
|
||||
self.mapping[seat] = student
|
||||
return True
|
||||
return False
|
||||
|
||||
def remove_student(self, student):
|
||||
"""Retire un élève de sa place.
|
||||
|
||||
:param student:
|
||||
Élève à retirer.
|
||||
:type student: Student
|
||||
|
||||
:return:
|
||||
True si la suppression s'est bien déroulée, False sinon.
|
||||
:rtype: bool
|
||||
"""
|
||||
# On rećupère la place de l'élève
|
||||
seat = self.get_seat(student)
|
||||
if seat:
|
||||
# Le siège dans le plan de classe est libéré
|
||||
self.mapping[seat] = None
|
||||
return True
|
||||
return False
|
||||
|
||||
def swap_students(self, student_one, student_two):
|
||||
"""Fait s'échanger de place deux élèves.
|
||||
|
||||
:param student_one:
|
||||
Premier élève.
|
||||
:type student_one: Student
|
||||
:param student_two:
|
||||
Deuxième élève.
|
||||
:type student_one: Student
|
||||
|
||||
:return:
|
||||
Si l'opération s'est bien déroulée.
|
||||
:rtype: bool
|
||||
"""
|
||||
# On récupère leur place
|
||||
seat_one = self.get_seat(student_one)
|
||||
seat_two = self.get_seat(student_two)
|
||||
|
||||
# On les retire
|
||||
if (self.remove_student(student_one)
|
||||
and self.remove_student(student_two)):
|
||||
# On attribue à chacun sa nouvelle place
|
||||
if (self.place_student(student_one, seat_two)
|
||||
and self.place_student(student_two, seat_one)):
|
||||
return True
|
||||
return False
|
34
sage/student.py
Normal file
34
sage/student.py
Normal file
@ -0,0 +1,34 @@
|
||||
"""
|
||||
Name: student.py
|
||||
Author: Jeff LANCE <jeff.lance@mala.fr>
|
||||
Date: 29/03/2018
|
||||
|
||||
Définiton d'une classe 'Student'.
|
||||
|
||||
Contient les éléments néćessaire à la création, manipulation d'élèves.
|
||||
"""
|
||||
|
||||
|
||||
class Student:
|
||||
"""Élève.
|
||||
|
||||
Un élève d'une classe a pour attributs: un nom, un niveau de bavardage,
|
||||
une liste d'amis.
|
||||
"""
|
||||
|
||||
def __init__(self, name, chat_lvl=0, friends=[]):
|
||||
"""Instancie un objet plan de classe.
|
||||
|
||||
:param name:
|
||||
Nom de l'élève.
|
||||
:type name: str
|
||||
:param chat_lvl:
|
||||
Coefficient ou niveau de bavardage.
|
||||
:type chat_lvl: int
|
||||
:param friends:
|
||||
Liste d'amis.
|
||||
:type friends: list
|
||||
"""
|
||||
self.name = name
|
||||
self.chat_lvl = chat_lvl
|
||||
self.friends = friends
|
1
tests/test_empty.csv
Normal file
1
tests/test_empty.csv
Normal file
@ -0,0 +1 @@
|
||||
"Id";"Name";"Chat level"
|
|
0
tests/test_fullempty.csv
Normal file
0
tests/test_fullempty.csv
Normal file
|
2
tests/test_malformed_1.csv
Normal file
2
tests/test_malformed_1.csv
Normal file
@ -0,0 +1,2 @@
|
||||
"Id";"Name"
|
||||
0;"Jean AIMAR";4
|
Can't render this file because it has a wrong number of fields in line 2.
|
8
tests/user_relationships.csv
Normal file
8
tests/user_relationships.csv
Normal file
@ -0,0 +1,8 @@
|
||||
;0;1;2;3;4;5;6
|
||||
0;0;0;1;1;1;1;0
|
||||
1;0;0;0;0;1;0;1
|
||||
2;1;0;0;0;1;1;0
|
||||
3;1;0;0;0;1;1;1
|
||||
4;1;1;1;1;0;1;0
|
||||
5;1;0;1;1;1;0;0
|
||||
6;0;1;0;1;0;0;0
|
|
13
tests/user_table_12.csv
Normal file
13
tests/user_table_12.csv
Normal file
@ -0,0 +1,13 @@
|
||||
"ID";"NAME";"CHAT LEVEL"
|
||||
0;"Jean NEYMAR";1
|
||||
1;"Sam DÉPASSE";0
|
||||
2;"Elsa DORSA";2
|
||||
3;"Sophie STICKÉ";3
|
||||
4;"Terry GOLO";4
|
||||
5;"Marie HONETTE";1
|
||||
6;"Jérémy TOUTENPLACE";3
|
||||
7;"Yvon PARTIR";5
|
||||
8;"Anne HANNA";2
|
||||
9;"Théo KAY";1
|
||||
10;"Marin DODOUCE";4
|
||||
11;"Pierre KIROULE";5
|
|
16
tests/user_table_15.csv
Normal file
16
tests/user_table_15.csv
Normal file
@ -0,0 +1,16 @@
|
||||
"ID";"NAME";"CHAT LEVEL"
|
||||
0;"Jean NEYMAR";1
|
||||
1;"Sam DÉPASSE";0
|
||||
2;"Elsa DORSA";2
|
||||
3;"Sophie STICKÉ";3
|
||||
4;"Terry GOLO";4
|
||||
5;"Marie HONETTE";1
|
||||
6;"Jérémy TOUTENPLACE";3
|
||||
7;"Yvon PARTIR";5
|
||||
8;"Anne HANNA";2
|
||||
9;"Théo KAY";1
|
||||
10;"Marin DODOUCE";4
|
||||
11;"Pierre KIROULE";5
|
||||
12;"Agathe ZEUBLOUZE";1
|
||||
13;"Ahmed AYPAN";3
|
||||
14;"Élie KOPTER";0
|
|
17
tests/user_table_16.csv
Normal file
17
tests/user_table_16.csv
Normal file
@ -0,0 +1,17 @@
|
||||
"ID";"NAME";"CHAT LEVEL"
|
||||
0;"Jean NEYMAR";1
|
||||
1;"Sam DÉPASSE";0
|
||||
2;"Elsa DORSA";2
|
||||
3;"Sophie STICKÉ";3
|
||||
4;"Terry GOLO";4
|
||||
5;"Marie HONETTE";1
|
||||
6;"Jérémy TOUTENPLACE";3
|
||||
7;"Yvon PARTIR";5
|
||||
8;"Anne HANNA";2
|
||||
9;"Théo KAY";1
|
||||
10;"Marin DODOUCE";4
|
||||
11;"Pierre KIROULE";5
|
||||
12;"Agathe ZEUBLOUZE";1
|
||||
13;"Ahmed AYPAN";3
|
||||
14;"Élie KOPTER";0
|
||||
15;"Nordine ATEUR";3
|
|
37
tests/user_table_36.csv
Normal file
37
tests/user_table_36.csv
Normal file
@ -0,0 +1,37 @@
|
||||
"ID";"NAME";"CHAT LEVEL"
|
||||
0;"Jean NEYMAR";1
|
||||
1;"Sam DÉPASSE";0
|
||||
2;"Elsa DORSA";2
|
||||
3;"Sophie STICKÉ";3
|
||||
4;"Terry GOLO";4
|
||||
5;"Marie HONETTE";1
|
||||
6;"Jérémy TOUTENPLACE";3
|
||||
7;"Yvon PARTIR";5
|
||||
8;"Anne HANNA";2
|
||||
9;"Théo KAY";1
|
||||
10;"Marin DODOUCE";4
|
||||
11;"Pierre KIROULE";5
|
||||
12;"Agathe ZEUBLOUZE";1
|
||||
13;"Ahmed AYPAN";3
|
||||
14;"Élie KOPTER";0
|
||||
15;"Nordine ATEUR";3
|
||||
16;"Youri LIGOTMI";5
|
||||
17;"Robin DIDON";5
|
||||
18;"Kader HUSSELLE";2
|
||||
19;"Paul AUCHON";3
|
||||
20;"Ali ABALDAKIN";4
|
||||
21;"Annie CROCHE";1
|
||||
22;"Fabien GAFATOY";3
|
||||
23;"Lydie AUDUVILAGE";5
|
||||
24;"Maud TETTE";1
|
||||
25;"Medhi KAMAN";2
|
||||
26;"Sarah VIGOTTE";4
|
||||
27;"Walid TONTIKAY";5
|
||||
28;"Yann NIVERSERT";1
|
||||
29;"Youssouf DANLBALON";3
|
||||
30;"Aude JAVEL";0
|
||||
31;"Emma KARENA";0
|
||||
32;"Gilbert MUDA";1
|
||||
33;"Thibault LONIAISE";5
|
||||
34;"Ève ANOUI";5
|
||||
35;"Mélanie SANDANLGARAJE";5
|
|
37
tests/user_table_36_soft.csv
Normal file
37
tests/user_table_36_soft.csv
Normal file
@ -0,0 +1,37 @@
|
||||
"ID";"NAME";"CHAT LEVEL"
|
||||
0;"Jean NEYMAR";1
|
||||
1;"Sam DÉPASSE";0
|
||||
2;"Elsa DORSA";1
|
||||
3;"Sophie STICKÉ";1
|
||||
4;"Terry GOLO";0
|
||||
5;"Marie HONETTE";1
|
||||
6;"Jérémy TOUTENPLACE";1
|
||||
7;"Yvon PARTIR";1
|
||||
8;"Anne HANNA";1
|
||||
9;"Théo KAY";1
|
||||
10;"Marin DODOUCE";0
|
||||
11;"Pierre KIROULE";0
|
||||
12;"Agathe ZEUBLOUZE";0
|
||||
13;"Ahmed AYPAN";1
|
||||
14;"Élie KOPTER";0
|
||||
15;"Nordine ATEUR";1
|
||||
16;"Youri LIGOTMI";0
|
||||
17;"Robin DIDON";0
|
||||
18;"Kader HUSSELLE";0
|
||||
19;"Paul AUCHON";0
|
||||
20;"Ali ABALDAKIN";0
|
||||
21;"Annie CROCHE";1
|
||||
22;"Fabien GAFATOY";1
|
||||
23;"Lydie AUDUVILAGE";1
|
||||
24;"Maud TETTE";1
|
||||
25;"Medhi KAMAN";1
|
||||
26;"Sarah VIGOTTE";1
|
||||
27;"Walid TONTIKAY";3
|
||||
28;"Yann NIVERSERT";1
|
||||
29;"Youssouf DANLBALON";3
|
||||
30;"Aude JAVEL";3
|
||||
31;"Emma KARENA";3
|
||||
32;"Gilbert MUDA";4
|
||||
33;"Thibault LONIAISE";4
|
||||
34;"Ève ANOUI";5
|
||||
35;"Mélanie SANDANLGARAJE";5
|
|
6
tests/user_table_5.csv
Normal file
6
tests/user_table_5.csv
Normal file
@ -0,0 +1,6 @@
|
||||
"ID";"NAME";"CHAT LEVEL"
|
||||
0;"Jean NEYMAR";1
|
||||
1;"Sam DÉPASSE";0
|
||||
2;"Elsa DORSA";2
|
||||
3;"Sophie STICKÉ";3
|
||||
4;"Terry GOLO";4
|
|
10
tests/user_table_9.csv
Normal file
10
tests/user_table_9.csv
Normal file
@ -0,0 +1,10 @@
|
||||
"ID";"NAME";"CHAT LEVEL"
|
||||
0;"Jean NEYMAR";1
|
||||
1;"Sam DÉPASSE";0
|
||||
2;"Elsa DORSA";2
|
||||
3;"Sophie STICKÉ";3
|
||||
4;"Terry GOLO";4
|
||||
5;"Marie HONETTE";1
|
||||
6;"Jérémy TOUTENPLACE";3
|
||||
7;"Yvon PARTIR";5
|
||||
8;"Anne HANNA";2
|
|
Loading…
Reference in New Issue
Block a user