"""Optimisation du trajet boulot <> dodo Les but de ce _programme_ est de déterminer la combinaison de transports optimale sur un mois donné pour minimiser le coût récurrent de trajet vers et depuis le travail. Les 3 modes de transport sont : - Voiture individuelle - Transports en communs (sans abonnement) - Transports en communs (avec abonnement) On pourra envisager d'ajouter le covoiturage quand l'offre pour ce mode sera plus développée / fiable dans le coin. """ from itertools import product from datetime import datetime, date, timedelta import pandas as pd def easter_dates(thisdate): """This function returns a 3-tuple containing, for the year of thisdate, Easter day, Ascension and Pentecost as datetime. It is computed with the Butcher algorithm, from Wikipedia. """ y = thisdate.year _, n = divmod(y, 19) # cycle de Méton c, u = divmod(y, 100) # centaine et range de l'année s, t = divmod(c, 4) # siècle bissextile p, _ = divmod((c+8), 25) # cycle de proemptose q, _ = divmod((c-p+1), 3) # proemptose _, e = divmod((19*n+c-s-q+15), 30) # épacte b, d = divmod(u, 4) # année bissextile _, l = divmod((2*t+2*b-e-d+32), 7) # lettre dominicale h, _ = divmod((n+11*e+22*l), 451) # correction m, j = divmod((e+l-7*h+114), 31) # mois et quantième du Samedi saint easter = date(y, m, j+2) # Fêté le lendemain, lundi ascension = easter + timedelta(days=38) # 40 jrs après Pâques, jeudi pentecost = easter + timedelta(days=49) # Fêté le lendemain, lundi return easter, ascension, pentecost def is_business_day(thisdate, bridge=True): """This function returns True if the given date is not a public holiday nor a weekend. If bridge is set to True, returns True if the date is not between two days off (weekend / public holiday). """ # Public holidays easter, ascension, pentecost = easter_dates(thisdate) public_holidays = { date(thisdate.year, 1, 1), # 1er janvier jour de l'an date(thisdate.year, 5, 1), # 1er mai fête du travail date(thisdate.year, 5, 8), # 8 mai fête de la Victoire date(thisdate.year, 7, 14), # 14 juillet fête nationale date(thisdate.year, 8, 15), # 15 août Assomption date(thisdate.year, 11, 1), # 1er novembre Toussaint date(thisdate.year, 11, 11), # 11 novembre armistice date(thisdate.year, 12, 25), # 25 décembre Noël easter, # lundi de Pâques ascension, # jeudi de l'Ascension pentecost, # lundi de Pentecôte } if bridge: td = timedelta(days=1) hb = not is_business_day(thisdate-td, bridge=False) # Holiday Before hn = not is_business_day(thisdate+td, bridge=False) # Holiday Next bridged = hb and hn else: bridged = False holiday = thisdate in public_holidays business = thisdate.weekday() < 5 and not (holiday or bridged) return business # On pourrait en faire un générateur... def combinations(nb_items, total, start_at=0): """This function returns a colleciton of nb_items-tuples so that their sum is always equal to total. """ r = product(range(start_at, total+1), repeat=nb_items) r = list(filter(lambda c: sum(c) == total, r)) return r def count_business_days(year, month, holidays={}): """This function returns the number of working daysin the given month.""" bd_count = 0 for i in range(1,32): try: thisdate = date(year, month, i) except(ValueError): # no more days in month break if is_business_day(thisdate) and not thisdate in holidays: bd_count += 1 return bd_count if __name__ == "__main__": """Les données Pas de coût initial pour le T+ ou la Voiture (_on ne prend pas en compte l'entretien/l'assurance, au moins dans un premier temps_). Le Navigo est payé en début de mois, à moitié prix car remboursé par l'employeur. Le coût récurrent du Navigo doit prendre en compte le prix de l'essence pour la mobylette, puis on a un prix unitaire pour les T+ et une consom- -mation rapportée au prix du diesel pour la voiture. * Voiture : 5,6 l/100 à 1,44 €/l et pour 2x38 km * 6,13 €/A-R * Mobylette : 3,7 l/100 à 1,56 €/l et pour 2x10 km * 1,15 €/A-R """ modes = ('Ticket T+', 'Navigo', 'Voiture') cout_initial = (0.0, 35.0, 0.0) cout_recurent = (5.96, 1.15, 6.13) annee, mois = 2018, 6 jrs_ouvres = count_business_days(annee, mois) nb_modes = len(modes) cout_mix = list() comb_mix = combinations(nb_modes, jrs_ouvres) for mix in comb_mix: cout = 0 for mode in range(nb_modes): # TODO: utiliser une expression ternaire if mix[mode]: cout += cout_initial[mode] cout += mix[mode]*cout_recurent[mode] cout_mix.append(cout) bilan = [[m for m in modes] + ['Cout']] #bilan.append([]) for i, mix in enumerate(comb_mix): bilan.append([*mix,cout_mix[i]]) bilan_df = pd.DataFrame(bilan) print(bilan_df)