diff --git a/.DS_Store b/.DS_Store index 977dfb2ca3e10ade31ef08d0bf14d75b78cbd7ac..28a601ed998575079069aed0e8da4e8a9e53a217 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Tp09/ap_decorators.py b/Tp09/ap_decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..5f4adc9129d3d7ffef9d7dc68b59e4d6643d419d --- /dev/null +++ b/Tp09/ap_decorators.py @@ -0,0 +1,135 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +:module: ap_decorators +:author: FIL - Faculté des Sciences et Technologies - Univ. Lille <http://portail.fil.univ-lille1.fr>_ +:date: 2018, september + +""" + +from functools import wraps + + +def trace(fct): + ''' + Decorator for tracing every call to fct. + Recursive calls are indented. + + :Example: + + >>> @trace + ... def fact(n): + ... if n == 0: + ... return 1 + ... else: + ... return n * fact(n - 1) + + >>> fact(5) + -> fact((5,), {}) + ... -> fact((4,), {}) + ...... -> fact((3,), {}) + ......... -> fact((2,), {}) + ............ -> fact((1,), {}) + ............... -> fact((0,), {}) + ............... <- 1 + ............ <- 1 + ......... <- 2 + ...... <- 6 + ... <- 24 + <- 120 + 120 + ''' + @wraps(fct) + def wrapper(*args, **kwargs): + dots = '...' * wrapper.__depth + print('{:s} -> {:s}{:s}'.format(dots, wrapper.__name__, repr((args, kwargs)))) + wrapper.__depth += 1 + y = fct(*args, **kwargs) + wrapper.__depth -= 1 + print('{:s} <- {:s}'.format(dots, repr(y))) + return y + wrapper.__depth = 0 + return wrapper + +def count(fct): + ''' + decorator for counting calls to function fct + + :Example: + + >>> @count + ... def fact(n): + ... if n == 0: + ... return 1 + ... else: + ... return n * fact(n - 1) + + >>> fact.counter + 0 + >>> fact(5) + 120 + >>> fact.counter + 6 + ''' + @wraps(fct) + def wrapper(*args, **kwargs): + y = fct(*args, **kwargs) + wrapper.counter += 1 + return y + wrapper.counter = 0 + return wrapper + + +def memoize(fct): + ''' + decorator for memoizing computed values of function fct + + :Example: + + >>> @count + ... @memoize + ... def fact(n): + ... if n == 0: + ... return 1 + ... else: + ... return n * fact(n - 1) + + >>> fact.counter + 0 + >>> fact(5) + 120 + >>> fact.counter + 6 + >>> fact.counter = 0 + >>> fact(5) + 120 + >>> fact.counter + 1 + ''' + cache = dict() + @wraps(fct) + def wrapper(*args, **kwargs): + key = repr((args, kwargs)) + if key in cache: + return cache[key] + else: + y = fct(*args, **kwargs) + cache[key] = y + return y + return wrapper + + + +if __name__ == '__main__': + import doctest + doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS, verbose=False) + + + + + + + + + diff --git a/Tp09/apl1test.py b/Tp09/apl1test.py new file mode 100644 index 0000000000000000000000000000000000000000..8533ccaca5d99e7cfb83d6d86aa9334bb6a73a40 --- /dev/null +++ b/Tp09/apl1test.py @@ -0,0 +1,89 @@ +import thonnycontrib +from thonnycontrib.backend.evaluator import Evaluator +import thonnycontrib.backend.l1test_backend +from thonny.plugins.cpython_backend.cp_back import MainCPythonBackend +import thonnycontrib.backend.doctest_parser +from thonnycontrib.backend.doctest_parser import ExampleWithExpected, ExampleWithoutExpected +import thonnycontrib.backend.ast_parser +from thonnycontrib.backend.ast_parser import L1DocTest +import thonnycontrib.backend.verdicts +from thonnycontrib.backend.verdicts.ExceptionVerdict import ExceptionVerdict + +import inspect +import tempfile +import os +import sys + +class MockBackend(MainCPythonBackend): + """ + Fake backend. + """ + def __init__(self): + ... + + def send_message(self, msg) -> None: + ... + +# register backend +thonnycontrib.backend.l1test_backend.BACKEND = MockBackend() + +def l1test_to_org(filename: str, source: str=""): + """ + Return an org abstract of the tests presents in `filename` file. + """ + abstract = {'total': 0, + 'success': 0, + 'failures': 0, + 'errors': 0, + 'empty': 0} + + if source == "": + with open(filename, 'rt') as fin: + source = fin.read() + evaluator = Evaluator(filename=filename, + source=source) + tests = evaluator.evaluate() + n = len(tests) + abstract['total'] = n + res = "" + for test in tests: + examples = test.get_examples() + res_examples = "" + nb_test, nb_test_ok = 0, 0 + empty = True + for example in examples: + verdict = test.get_verdict_from_example(example) + if isinstance(example, ExampleWithExpected): + nb_test += 1 + if verdict.isSuccess(): + nb_test_ok += 1 + abstract['success'] += 1 + else: + abstract['failures'] += 1 + empty = False + if isinstance(verdict, ExceptionVerdict): + abstract['errors'] += 1 + empty = False + res_examples += f"** {verdict}\n\n" + if not verdict.isSuccess(): + res_examples += f" {verdict.get_details()}\n\n" + if not empty: + res += f"* {test.get_name()} ~ {nb_test_ok}/{nb_test} réussis\n\n" + else: + abstract['empty'] += 1 + res += f"* {test.get_name()}\n\n Aucun test trouvé !\n\n" + res += res_examples + res = f"Tests exécutés : {abstract['total']}\nSuccès: {abstract['success']}, \ +Echecs: {abstract['failures']}, Erreurs: {abstract['errors']}, \ +Vide: {abstract['empty']}\n\n" + res + return res + + +def testmod(modulename: str): + """ + mimic the doctest.testmod function + for `modulename` module + """ + print(l1test_to_org(modulename)) + + diff --git a/Tp09/aplst.py b/Tp09/aplst.py new file mode 100755 index 0000000000000000000000000000000000000000..3fcca937a0a806af3ea8c780d9dcb84ce5017e1d --- /dev/null +++ b/Tp09/aplst.py @@ -0,0 +1,141 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +:mod:`list` module + +:author: FIL - Faculté des Sciences et Technologies - + Univ. Lille <http://portail.fil.univ-lille1.fr>_ + +:date: 2022, march +:last revision: 2024, march + +Provides + +- a class :class:`ApLst` for non mutable lists +- an exception :class:`ApLstError` + + +ApLsts are either empty, either essentially objects with two composants : + +#. a *head* composant which represent the head value of the list, +#. and a *tail* composant which is represent the tail of the list + +""" + + +class ApLstError(Exception): + """ + Exception used by methods + + * ``__init__`` + * ``head`` + * ``tail`` + + of class :class:`ApLst`. + """ + def __init__(self, msg): + self.message = msg + + +class ApLst(): + """ + $$$ list = ApLst() + $$$ list.is_empty() + True + $$e list.head() + ApLstError + $$$ list2 = ApLst(1, list) + $$$ list2.is_empty() + False + $$$ list2.head() + 1 + $$$ list2.tail().is_empty() + True + $$$ l = ApLst(2, list2) + $$$ repr(l) + 'ApLst(2, ApLst(1, ApLst()))' + $$$ str(l) + '[2, 1]' + $$$ repr(list2) + 'ApLst(1, ApLst())' + """ + + def __init__(self, *args): + """ + build a new empty list if args is empty, + or a list whose head is first element of args, + and tail list is second element of args. + + précondition: len(args) in {0, 2} + and if len(args) == 2, args[1] must be a ApLst + + raise: `ApLstError` if précondition is not satisfied + """ + if len(args) == 0: + self.content = () + elif len(args) == 2: + if isinstance(args[1], ApLst): + self.content = (args[0], args[1]) + else: + raise ApLstError('bad type for second argument') + else: + raise ApLstError('bad number of arguments') + + def is_empty(self) -> bool: + """ + return + - True if self is empty + - False otherwise + précondition: none + """ + return len(self.content) == 0 + + def head(self): + """ + return: head element of self + raise: `ApLstError` if self is empty + """ + if self.is_empty(): + raise ApLstError('head: empty list') + else: + return self.content[0] + + def tail(self) -> "ApLst": + """ + return: tail list of self + raise: `ApLstError` if self is empty + """ + if self.is_empty(): + raise ApLstError('head: empty list') + else: + return self.content[1] + + def __str__(self) -> str: + """ + return: a string representation of list self + précondition: none + """ + def str_content(self, item_number=0): + if self.is_empty(): + return '' + elif item_number == 50: + return ', ...' + else: + comma = '' if item_number == 0 else ', ' + return (comma + str(self.head()) + + str_content(self.tail(), item_number + 1)) + + return f'[{str_content(self)}]' + + def __repr__(self) -> str: + if self.is_empty(): + content = "" + else: + content = f"{repr(self.head())}, {repr(self.tail())}" + return f"ApLst({content})" + + +if (__name__ == '__main__'): + import apl1test + apl1test.testmod("aplst.py") diff --git a/Tp09/merge_sort.py b/Tp09/merge_sort.py new file mode 100755 index 0000000000000000000000000000000000000000..211389d2ed13b0eea43838460eb347975b103d3f --- /dev/null +++ b/Tp09/merge_sort.py @@ -0,0 +1,183 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +:mod:`recursive sorts` +:author: `FIL - FST - Univ. Lille.fr <http://portail.fil.univ-lille1.fr>`_ +:date: 2016, september. Last revised: 2018, september + +Some recursive sorting algorithms: + +- quicksort +- mergesort + +""" +from typing import Callable, TypeVar +from aplst import ApLst + + +T = TypeVar('T') + + +def compare(a: T, b: T) -> int: + """ + return: + - -1 if a < b + - 1 if a > b + - 0 if a = b + precondition: a and b must be comparable with < + exemples: + + $$$ compare(0, 1) + -1 + $$$ compare('a', 'a') + 0 + $$$ compare((2, 1), (1, 2)) + 1 + """ + if a < b: + return -1 + elif a > b: + return 1 + else: + return 0 + + +def length(li: ApLst) -> int: + """ + return the length of li. + + precondition: none + + examples: + + $$$ length(ApLst()) + 0 + $$$ length(ApLst(3, ApLst(1, ApLst(4, ApLst())))) + 3 + """ + if not li.is_empty(): + res = 1 + lenght(li.tail()) + else: + res = 0 + return res + + +def native_to_list(li: list[T]) -> ApLst: + """ + return a recursive list containing the same element of li. + + precondition: none + + examples: + + $$$ native_to_list([]).is_empty() + True + $$$ rec_lst = native_to_list([3, 1, 4, 1, 5]) + $$$ length(rec_lst) + 5 + $$$ rec_lst.head() + 3 + $$$ l = rec_lst.tail() + $$$ l.head() + 1 + $$$ l = l.tail() + $$$ l.head() + 4 + """ + + + +def list_to_native(li: ApLst) -> list[T]: + """ + return a native python list containing the same element of li. + + precondition: none + + examples: + + $$$ list_to_native(ApLst()) + [] + $$$ list_to_native(ApLst(3, ApLst(1, ApLst(4, ApLst(1, ApLst(5, ApLst())))))) + [3, 1, 4, 1, 5] + """ + ... + + +def is_sorted(l: ApLst, comp: Callable[[T, T], int]=compare) -> bool: + """ + return True if list l is sorted by ascending order + and False otherwise. + + precondition: elements of l must be comparable + exemples: + + $$$ is_sorted(native_to_list([1, 2, 3, 4])) + True + $$$ is_sorted(native_to_list([1, 2, 4, 3])) + False + """ + ... + + +def split(l: ApLst) -> tuple[ApLst, ApLst]: + """ + return a couple (l1,l2) of lists of equal length + + exemples: + + $$$ l = [3, 1, 4, 1, 5, 9, 2] + $$$ l1, l2 = split(native_to_list(l)) + $$$ abs(length(l1) - length(l2)) <= 1 + True + $$$ l3 = list_to_native(l1) + list_to_native(l2) + $$$ len(l3) == len(l) + True + $$$ all(k in l for k in l3) + True + """ + ... + + +def merge(l1: ApLst, l2: ApLst, + comp: Callable[[T, T], int]=compare) -> ApLst: + """ + return a list containing all elements de l1 and l2. + If l1 and l2 are sorted, so is the returned list. + + precondition: elements of l1 and l2 are comparable + exemples: + + $$$ list_to_native(merge(native_to_list([1, 3, 4, 9]), native_to_list([1, 2, 5]))) + [1, 1, 2, 3, 4, 5, 9] + """ + ... + + +def mergesort(l: ApLst, comp: Callable[[T, T], int]=compare) -> ApLst: + """ + return a new list containing elements of l sorted by ascending order. + + precondition: elements of l are comparable + exemples: + + $$$ list_to_native(mergesort(native_to_list([3, 1, 4, 1, 5, 9, 2]))) + [1, 1, 2, 3, 4, 5, 9] + $$$ import random + $$$ n = random.randrange(20) + $$$ l = native_to_list([random.randrange(20) for k in range(n)]) + $$$ l1 = mergesort(l) + $$$ length(l1) == length(l) + True + $$$ is_sorted(l1) + True + """ + ... + + +if (__name__ == '__main__'): + import apl1test + apl1test.testmod("merge_sort.py") + + + diff --git a/Tp09/merge_sort_analysis.py b/Tp09/merge_sort_analysis.py new file mode 100644 index 0000000000000000000000000000000000000000..e93962c1f2b985724e9f71d40666aa9364afa043 --- /dev/null +++ b/Tp09/merge_sort_analysis.py @@ -0,0 +1,92 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +:mod:`mergesort_analysis` module +:author: FIL - FST - Univ. Lille <http://portail.fil.univ-lille.fr>_ +:date: 2024, April. + +Experimental analysis of mergesort algorithm + +""" +from aplst import ApLst +from merge_sort import compare, mergesort, native_to_list +from ap_decorators import count + + +import random +import matplotlib.pyplot as plt +from math import log + +def random_list(size: int) -> ApLst: + ''' + :param size: (int) + :return: (list) list of size size containing all natural numbers from 0 to n-1 in random order + :CU: n >= 0 + ''' + l = list(range(size)) + random.shuffle(l) + return native_to_list(l) + + +def myplot(listX: list[float], listY: list[float], + title: str='', xlabel: str ='', ylabel: str=''): + ''' + plot the data in listX and listY + ''' + plt.plot(listX, listY) + plt.title(title) + plt.xlabel(xlabel) + plt.ylabel(ylabel) + plt.show() + + +def build_comp_number_list(sort, max_size: int, sample_size: int) -> list[float]: + ''' + return: list of average number of comparisons for sorting lists of size up to max_size + ''' + comp = count(compare) + nb_comps = [] + for size in range(1, max_size): + comp.counter = 0 + for _ in range(sample_size): + sort(random_list(size), comp=comp) + nb_comps.append(comp.counter / sample_size) + return nb_comps + + +def usage(): + print('Usage: {:s} <max size> <sample size>'.format(sys.argv[0]), + file=sys.stderr) + print('\t<max size> = max size of lists to sort', file=sys.stderr) + print('\t<sample size> = size of samples.', file=sys.stderr) + exit(1) + +if __name__ == '__main__': + import sys + + if len(sys.argv) != 3: + print('Bad number of arguments!', file=sys.stderr) + usage() + + try: + MAX_SIZE = int(sys.argv[1]) + except ValueError: + print('Max size must be an integer!', file=sys.stderr) + usage() + + SIZES = list(range(1, MAX_SIZE)) + + try: + SAMPLE_SIZE = int(sys.argv[2]) + except ValueError: + print('Sample size must be an integer!', file=sys.stderr) + usage() + + # all is OK! + NB_COMPS = build_comp_number_list(mergesort, MAX_SIZE, SAMPLE_SIZE) + myplot(SIZES, NB_COMPS, + title=f'Nbre de comparaisons du tri fusion (mesuré sur échantillon de taille {SAMPLE_SIZE})', + xlabel='taille des listes', + ylabel='nbre de comparaisons') +