Méthode pythonique pour vérifier si une liste est triée ou non


145

Existe-t-il un moyen pythonique de vérifier si une liste est déjà triée ASCouDESC

listtimestamps = [1, 2, 3, 5, 6, 7]

quelque chose comme isttimestamps.isSorted()ça revient Trueou False.

Je souhaite saisir une liste d'horodatages pour certains messages et vérifier si les transactions sont apparues dans le bon ordre.

Réponses:


212

En fait, nous ne donnons pas la réponse qu'anijhaw recherche. Voici la seule doublure:

all(l[i] <= l[i+1] for i in xrange(len(l)-1))

Pour Python 3:

all(l[i] <= l[i+1] for i in range(len(l)-1))

2
c'est zonte. Vous voudrez peut-être l'envelopper dans une fonction afin que vous puissiez passer une keyfonction à utiliser. key=lambda x, y: x < yfait un bon défaut.
aaronasterling

3
Un combo de quelques solutions:def isSorted(x, key = lambda x: x): return all([key(x[i]) <= key(x[i + 1]) for i in xrange(len(x) - 1)])
eacousineau

2
@aaronasterling: operator.ledevrait être plus rapide que le lambda
Marian

Cela ne fonctionne pas pour moi (python --version = 2.6.4) l = [1, 2, 3, 4, 1, 6, 7, 8, 7] all(l[i] <= l[i+1] for i in xrange(len(l)-1)) imprimer comme résultat:True
prodev_paris

1
On dirait que Python 3.x n'en a xrangeplus, il suffit d'utiliser range. Je reçois NameError: name 'xrange' is not definedquand j'exécute ce code. Je l'ai changé pour simplement utiliser rangeau lieu de xrangeet cela fonctionne très bien. Voir: stackoverflow.com/questions/15014310/…
Cale Sweeney

78

Je voudrais juste utiliser

if sorted(lst) == lst:
    # code here

sauf s'il s'agit d'une très grande liste, auquel cas vous souhaiterez peut-être créer une fonction personnalisée.

si vous voulez simplement le trier s'il n'est pas trié, oubliez le chèque et triez-le.

lst.sort()

et n'y pensez pas trop.

si vous voulez une fonction personnalisée, vous pouvez faire quelque chose comme

def is_sorted(lst, key=lambda x: x):
    for i, el in enumerate(lst[1:]):
        if key(el) < key(lst[i]): # i is the index of the previous element
            return False
    return True

Ce sera O (n) si la liste est déjà triée (et O (n) dans une forboucle!) Donc, à moins que vous ne vous attendiez à ce qu'elle ne soit pas triée (et assez aléatoire) la plupart du temps, je le ferais, encore une fois, triez simplement la liste.


10
Si c'est ce que vous allez faire, vous pourriez aussi bien dire: lst.sort () sans le contrôle conditionnel ;-)
SapphireSun

5
thats nlogn il existe un moyen nettement plus rapide dans O (n) en utilisant une simple boucle for.
anijhaw

1
@SapphireSun. C'est ce que j'ai dit;)
aaronasterling

@anijhaw, voyez la mise à jour que j'ai faite pendant que vous laissiez le commentaire. la vérification est O (n) et le tri est O (nlgn). est-il préférable d'engager un coût O (n) pour simplement faire demi-tour et ajouter O (nlgn) ou simplement prendre le coût du tri d'une liste triée qui est (je crois) O (n) pour le tri temporel.
aaronasterling

@ Aaron: Vérifiez la modification de la question d'origine,
anijhaw

44

Cette forme d'itérateur est 10 à 15% plus rapide que l'utilisation de l'indexation d'entiers:

# python2 only
if str is bytes:
    from itertools import izip as zip

def is_sorted(l):
    return all(a <= b for a, b in zip(l, l[1:]))

Je ne vois pas de différence significative sur ma machine gist.github.com/735259 La variante # 7 modifiée de la réponse de @Nathan Farrington est 2x plus rapide stackoverflow.com/questions/3755136
...

Cela ne fonctionnera que pour les conteneurs «indexables» comme une liste, auquel cas deux nouvelles listes sont créées avec le découpage. Pour les itérateurs généraux, je préfère la solution d'Alexandre .
Bas Swinckels

1
Réponse élégante, vous pouvez utiliser izipet à islicepartir d'itertools pour la rendre plus rapide.
Elmex80s

@jfs: la "variante # 7 de Nathan Farrington" est fausse. il ne fait tout simplement pas ce qu'il est censé faire et c'est pourquoi il est plus rapide. voir mon commentaire là-bas.
olivecoder

1
Vous pouvez simplifier votre solution en zip (l, l [1:]), car zip s'arrête lorsque l'argument le plus court est épuisé
Gelineau

20

Une belle façon d'implémenter cela est d'utiliser la imapfonction de itertools:

from itertools import imap, tee
import operator

def is_sorted(iterable, compare=operator.le):
  a, b = tee(iterable)
  next(b, None)
  return all(imap(compare, a, b))

Cette implémentation est rapide et fonctionne sur tous les itérables.


4
Nice, mais buggy! Essayez is_sorted(iter([1,2,3,2,5,8]))ou un générateur équivalent. Vous devez utiliser un itérateur indépendant pour tail, essayez itertools.tee.
Kos

Rappelez-vous que iter(x) is xpour les itérateurs
Kos

1
Ah, c'est une mauvaise surprise! Je l'ai réparé maintenant. Merci!
Alexandre Vassalotti

3
Notez que dans Python 3 itertools.imapa été renommé en [__builtins__.]map.
Nick T

10

J'ai dirigé un benchmark etsorted(lst, reverse=True) == lstall(l[i] >= l[i+1] for i in xrange(len(l)-1)) j'ai été le plus rapide pour les longues listes et le plus rapide pour les listes courtes . Ces tests ont été exécutés sur un MacBook Pro 2010 13 "(Core2 Duo 2,66 GHz, 4 Go de RAM DDR3 à 1067 MHz, Mac OS X 10.6.5).

METTRE À JOUR: J'ai révisé le script afin que vous puissiez l'exécuter directement sur votre propre système. La version précédente avait des bugs. En outre, j'ai ajouté des entrées triées et non triées.

  • Idéal pour les listes triées courtes: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Idéal pour les longues listes triées: sorted(l, reverse=True) == l
  • Idéal pour les listes courtes non triées: all(l[i] >= l[i+1] for i in xrange(len(l)-1))
  • Idéal pour les longues listes non triées: all(l[i] >= l[i+1] for i in xrange(len(l)-1))

Donc, dans la plupart des cas, il y a un gagnant clair.

MISE À JOUR: les réponses d'aaronsterling (# 6 et # 7) sont en fait les plus rapides dans tous les cas. Le n ° 7 est le plus rapide car il n'a pas de couche d'indirection pour rechercher la clé.

#!/usr/bin/env python

import itertools
import time

def benchmark(f, *args):
    t1 = time.time()
    for i in xrange(1000000):
        f(*args)
    t2 = time.time()
    return t2-t1

L1 = range(4, 0, -1)
L2 = range(100, 0, -1)
L3 = range(0, 4)
L4 = range(0, 100)

# 1.
def isNonIncreasing(l, key=lambda x,y: x >= y): 
    return all(key(l[i],l[i+1]) for i in xrange(len(l)-1))
print benchmark(isNonIncreasing, L1) # 2.47253704071
print benchmark(isNonIncreasing, L2) # 34.5398209095
print benchmark(isNonIncreasing, L3) # 2.1916718483
print benchmark(isNonIncreasing, L4) # 2.19576501846

# 2.
def isNonIncreasing(l):
    return all(l[i] >= l[i+1] for i in xrange(len(l)-1))
print benchmark(isNonIncreasing, L1) # 1.86919999123
print benchmark(isNonIncreasing, L2) # 21.8603689671
print benchmark(isNonIncreasing, L3) # 1.95684289932
print benchmark(isNonIncreasing, L4) # 1.95272517204

# 3.
def isNonIncreasing(l, key=lambda x,y: x >= y): 
    return all(key(a,b) for (a,b) in itertools.izip(l[:-1],l[1:]))
print benchmark(isNonIncreasing, L1) # 2.65468883514
print benchmark(isNonIncreasing, L2) # 29.7504849434
print benchmark(isNonIncreasing, L3) # 2.78062295914
print benchmark(isNonIncreasing, L4) # 3.73436689377

# 4.
def isNonIncreasing(l):
    return all(a >= b for (a,b) in itertools.izip(l[:-1],l[1:]))
print benchmark(isNonIncreasing, L1) # 2.06947803497
print benchmark(isNonIncreasing, L2) # 15.6351969242
print benchmark(isNonIncreasing, L3) # 2.45671010017
print benchmark(isNonIncreasing, L4) # 3.48461818695

# 5.
def isNonIncreasing(l):
    return sorted(l, reverse=True) == l
print benchmark(isNonIncreasing, L1) # 2.01579380035
print benchmark(isNonIncreasing, L2) # 5.44593787193
print benchmark(isNonIncreasing, L3) # 2.01813793182
print benchmark(isNonIncreasing, L4) # 4.97615599632

# 6.
def isNonIncreasing(l, key=lambda x, y: x >= y): 
    for i, el in enumerate(l[1:]):
        if key(el, l[i-1]):
            return False
    return True
print benchmark(isNonIncreasing, L1) # 1.06842684746
print benchmark(isNonIncreasing, L2) # 1.67291283607
print benchmark(isNonIncreasing, L3) # 1.39491200447
print benchmark(isNonIncreasing, L4) # 1.80557894707

# 7.
def isNonIncreasing(l):
    for i, el in enumerate(l[1:]):
        if el >= l[i-1]:
            return False
    return True
print benchmark(isNonIncreasing, L1) # 0.883186101913
print benchmark(isNonIncreasing, L2) # 1.42852401733
print benchmark(isNonIncreasing, L3) # 1.09229516983
print benchmark(isNonIncreasing, L4) # 1.59502696991

1
Votre point de repère teste le pire des cas pour les formes d'expression du générateur et le meilleur des cas pour ma solution. Vous voudrez peut-être également effectuer un test sur une liste non triée. Ensuite, vous verrez que, sauf si vous vous attendez à ce que la liste soit triée la plupart du temps, l'expression du générateur est meilleure.
aaronasterling

@aaronsterling, j'ai mis à jour le script pour avoir à la fois des entrées triées et non triées.
Nathan Farrington

Toutes les fonctions avec enumeratesont incorrectes. enumerate(l[1:])devrait être remplacé parenumerate(l[1:], 1)
jfs le

1
au lieu de remplacer enumerate(l[1:])par, enumerate(l[1:], 1)vous pouvez remplacer l[i-1]par l[i].
jfs

Si vous ajoutez une entrée aléatoire, par exemple, L5=range(100); random.shuffle(L5)alors # 5 est relativement lent. Dans ce cas, le # 7 modifié est globalement plus rapide codepad.org/xmWPxHQY
jfs

9

Je ferais ceci (voler de nombreuses réponses ici [Aaron Sterling, Wai Yip Tung, en quelque sorte de Paul McGuire] et surtout Armin Ronacher ):

from itertools import tee, izip

def pairwise(iterable):
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

def is_sorted(iterable, key=lambda a, b: a <= b):
    return all(key(a, b) for a, b in pairwise(iterable))

Une bonne chose: vous n'avez pas à réaliser le deuxième itérable pour la série (contrairement à une tranche de liste).


2
nom trompeur key. keydoit être utilisé pour transformer des éléments en valeurs comparables.
InQβ

4

J'utilise ce one-liner basé sur numpy.diff ():

def issorted(x):
    """Check if x is sorted"""
    return (numpy.diff(x) >= 0).all() # is diff between all consecutive entries >= 0?

Je ne l'ai pas vraiment chronométré par rapport à une autre méthode, mais je suppose que c'est plus rapide que n'importe quelle méthode Python pure, en particulier pour les grands n, car la boucle dans numpy.diff (probablement) s'exécute directement en C (n-1 soustractions suivies de n -1 comparaisons).

Cependant, vous devez faire attention si x est un entier non signé, ce qui peut entraîner un dépassement d'entier silencieux dans numpy.diff (), entraînant un faux positif. Voici une version modifiée:

def issorted(x):
    """Check if x is sorted"""
    try:
        if x.dtype.kind == 'u':
            # x is unsigned int array, risk of int underflow in np.diff
            x = numpy.int64(x)
    except AttributeError:
        pass # no dtype, not an array
    return (numpy.diff(x) >= 0).all()

4

Ceci est similaire à la réponse du haut, mais je l'aime mieux car cela évite l'indexation explicite. En supposant que votre liste porte le nom lst, vous pouvez générer des
(item, next_item)tuples à partir de votre liste avec zip:

all(x <= y for x,y in zip(lst, lst[1:]))

En Python 3, ziprenvoie déjà un générateur, en Python 2 vous pouvez utiliseritertools.izip pour une meilleure efficacité de la mémoire.

Petite démo:

>>> lst = [1, 2, 3, 4]
>>> zip(lst, lst[1:])
[(1, 2), (2, 3), (3, 4)]
>>> all(x <= y for x,y in zip(lst, lst[1:]))
True
>>> 
>>> lst = [1, 2, 3, 2]
>>> zip(lst, lst[1:])
[(1, 2), (2, 3), (3, 2)]
>>> all(x <= y for x,y in zip(lst, lst[1:]))
False

Le dernier échoue lorsque le tuple (3, 2) est évalué.

Bonus: vérification des générateurs finis (!) Non indexables:

>>> def gen1():
...     yield 1
...     yield 2
...     yield 3
...     yield 4
...     
>>> def gen2():
...     yield 1
...     yield 2
...     yield 4
...     yield 3
... 
>>> g1_1 = gen1()
>>> g1_2 = gen1()
>>> next(g1_2)
1
>>> all(x <= y for x,y in zip(g1_1, g1_2))
True
>>>
>>> g2_1 = gen2()
>>> g2_2 = gen2()
>>> next(g2_2)
1
>>> all(x <= y for x,y in zip(g2_1, g2_2))
False

Assurez-vous de l'utiliser itertools.izipici si vous utilisez Python 2, sinon vous iriez à l'encontre de l'objectif de ne pas avoir à créer de listes à partir des générateurs.


2
Vous pouvez même utiliser islicepour optimiser le tranchage. Également dans le module itertools. all(x <= y for x, y in izip(lst, islice(lst, 1))).
Elmex80s

3

SapphireSun a tout à fait raison. Vous pouvez simplement utiliser lst.sort(). L'implémentation de tri de Python (TimSort) vérifie si la liste est déjà triée. Si c'est le cas, sort () se terminera en temps linéaire. Cela ressemble à une façon pythonique de s'assurer qu'une liste est triée;)


20
Seulement en temps linéaire si la liste est, en fait, triée. Sinon, il n'y a pas de court-circuit pour sauter la tâche de tri proprement dite, ce pourrait donc être une énorme pénalité à payer si la liste est longue.
PaulMcG

C'est une excellente réponse si votre tâche est "assurez-vous que la liste est triée et mourez sinon". Ce qui est assez courant en tant que vérification de l'intégrité des données qui devraient être triées pour une autre raison. Alors seul le cas d'erreur est lent.
Ed Avis

3

Bien que je ne pense pas qu'il y ait une garantie pour que la fonction sortedintégrée appelle sa fonction cmp aveci+1, i , il semble le faire pour CPython.

Vous pouvez donc faire quelque chose comme:

def my_cmp(x, y):
   cmpval = cmp(x, y)
   if cmpval < 0:
      raise ValueError
   return cmpval

def is_sorted(lst):
   try:
      sorted(lst, cmp=my_cmp)
      return True
   except ValueError:
      return False

print is_sorted([1,2,3,5,6,7])
print is_sorted([1,2,5,3,6,7])

Ou de cette façon (sans instructions if -> EAFP a mal tourné? ;-)):

def my_cmp(x, y):
   assert(x >= y)
   return -1

def is_sorted(lst):
   try:
      sorted(lst, cmp=my_cmp)
      return True
   except AssertionError:
      return False

3

Pas très pythonique du tout, mais nous avons besoin d'au moins une reduce()réponse, non?

def is_sorted(iterable):
    prev_or_inf = lambda prev, i: i if prev <= i else float('inf')
    return reduce(prev_or_inf, iterable, float('-inf')) < float('inf')

La variable d'accumulateur stocke simplement cette dernière valeur vérifiée, et si une valeur est inférieure à la valeur précédente, l'accumulateur est mis à l'infini (et sera donc toujours l'infini à la fin, car la `` valeur précédente '' sera toujours plus grande que l'actuel).


2

Comme indiqué par @aaronsterling, la solution suivante est la plus courte et semble la plus rapide lorsque le tableau est trié et pas trop petit: def is_sorted (lst): return (sorted (lst) == lst)

Si la plupart du temps le tableau n'est pas trié, il serait souhaitable d'utiliser une solution qui n'analyse pas tout le tableau et renvoie False dès qu'un préfixe non trié est découvert. Voici la solution la plus rapide que j'ai pu trouver, elle n'est pas particulièrement élégante:

def is_sorted(lst):
    it = iter(lst)
    try:
        prev = it.next()
    except StopIteration:
        return True
    for x in it:
        if prev > x:
            return False
        prev = x
    return True

En utilisant le benchmark de Nathan Farrington, cela permet d'obtenir un meilleur runtime que d'utiliser sorted (lst) dans tous les cas, sauf lors de l'exécution sur la grande liste triée.

Voici les résultats de référence sur mon ordinateur.

trié (lst) == lst solution

  • L1: 1.23838591576
  • L2: 4.19063091278
  • L3: 1,17996287346
  • L4: 4,68399500847

Deuxième solution:

  • L1: 0,81095790863
  • L2: 0.802397012711
  • L3: 1.06135106087
  • L4: 8,82761001587

2

Si vous voulez le moyen le plus rapide pour les tableaux numpy, utilisez numba , qui si vous utilisez conda devrait déjà être installé

Le code sera rapide car il sera compilé par numba

import numba
@numba.jit
def issorted(vec, ascending=True):
    if len(vec) < 2:
        return True
    if ascending:
        for i in range(1, len(vec)):
            if vec[i-1] > vec[i]:
                return False
        return True
    else:
        for i in range(1, len(vec)):
            if vec[i-1] < vec[i]:
                return False
        return True

puis:

>>> issorted(array([4,9,100]))
>>> True

2

Juste pour ajouter une autre manière (même si cela nécessite un module supplémentaire) iteration_utilities.all_monotone::

>>> from iteration_utilities import all_monotone
>>> listtimestamps = [1, 2, 3, 5, 6, 7]
>>> all_monotone(listtimestamps)
True

>>> all_monotone([1,2,1])
False

Pour vérifier l'ordre DESC:

>>> all_monotone(listtimestamps, decreasing=True)
False

>>> all_monotone([3,2,1], decreasing=True)
True

Il existe également un strictparamètre si vous devez vérifier des séquences strictement monotones (si les éléments successifs ne doivent pas être égaux).

Ce n'est pas un problème dans votre cas mais si vos séquences contiennent des nanvaleurs alors certaines méthodes échoueront, par exemple avec trié:

def is_sorted_using_sorted(iterable):
    return sorted(iterable) == iterable

>>> is_sorted_using_sorted([3, float('nan'), 1])  # definetly False, right?
True

>>> all_monotone([3, float('nan'), 1])
False

Notez que les iteration_utilities.all_monotoneperformances sont plus rapides par rapport aux autres solutions mentionnées ici en particulier pour les entrées non triées (voir benchmark ).


2

Paresseux

from itertools import tee

def is_sorted(l):
    l1, l2 = tee(l)
    next(l2, None)
    return all(a <= b for a, b in zip(l1, l2))

1
Absolument génial! Voici mon amélioration pour en faire une seule ligne - au lieu de iter () et next (), utilisez le tranchage avec le même résultat:all(a <= b for a, b in zip(l, l[1:]))
Matt

1
@LiborJelinek bien, mais ma version fonctionne quand lest un générateur et ne prend pas en charge le découpage.
Sergey11g

2

Python 3.6.8

from more_itertools import pairwise

class AssertionHelper:
    @classmethod
    def is_ascending(cls, data: iter) -> bool:
        for a, b in pairwise(data):
            if a > b:
                return False
        return True

    @classmethod
    def is_descending(cls, data: iter) -> bool:
        for a, b in pairwise(data):
            if a < b:
                return False
        return True

    @classmethod
    def is_sorted(cls, data: iter) -> bool:
        return cls.is_ascending(data) or cls.is_descending(data)
>>> AssertionHelper.is_descending((1, 2, 3, 4))
False
>>> AssertionHelper.is_ascending((1, 2, 3, 4))
True
>>> AssertionHelper.is_sorted((1, 2, 3, 4))
True

0

Manière la plus simple:

def isSorted(arr):
  i = 1
  while i < len(arr):
    if(result[i] < result[i - 1]):
      return False
    i += 1
  return True

0
from functools import reduce

# myiterable can be of any iterable type (including list)
isSorted = reduce(lambda r, e: (r[0] and (r[1] or r[2] <= e), False, e), myiterable, (True, True, None))[0]

La valeur de réduction dérivée est un tuple en 3 parties de ( sortedSoFarFlag , firstTimeFlag , lastElementValue ). Il commence d' abord avec ( True, True, None), qui est également utilisée comme résultat une liste vide (considérés comme classés parce qu'il n'y a pas d' éléments hors de commande). Lorsqu'il traite chaque élément, il calcule de nouvelles valeurs pour le tuple (en utilisant les valeurs de tuple précédentes avec l'élément suivant):

[0] (sortedSoFarFlag) evaluates true if: prev_0 is true and (prev_1 is true or prev_2 <= elementValue)
[1] (firstTimeFlag): False
[2] (lastElementValue): elementValue

Le résultat final de la réduction est un tuple de:

[0]: True/False depending on whether the entire list was in sorted order
[1]: True/False depending on whether the list was empty
[2]: the last element value

La première valeur est celle qui nous intéresse, nous utilisons donc [0]pour extraire cela du résultat de réduction.


Notez que cette solution fonctionne pour tous les types d'élément contenant itérables qui peuvent être comparés les uns aux autres. Cela inclut des listes de booléens (vérifie que les valeurs False se produisent avant les valeurs True), des listes de nombres, des listes de chaînes (ordre alphabétique), des listes d'ensembles (les sous-ensembles se produisent avant les supersets) etc.
Mr Weasel

0

Comme je ne vois pas cette option ci-dessus, je l'ajouterai à toutes les réponses. Notons la liste par l, puis:

import numpy as np

# Trasform the list to a numpy array
x = np.array(l)

# check if ascendent sorted:
all(x[:-1] <= x[1:])

# check if descendent sorted:
all(x[:-1] >= x[1:])

0

Une solution utilisant des expressions d'affectation (ajoutées dans Python 3.8):

def is_sorted(seq):
    seq_iter = iter(seq)
    cur = next(seq_iter, None)
    return all((prev := cur) <= (cur := nxt) for nxt in seq_iter)

z = list(range(10))
print(z)
print(is_sorted(z))

import random
random.shuffle(z)
print(z)
print(is_sorted(z))

z = []
print(z)
print(is_sorted(z))

Donne:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
True
[1, 7, 5, 9, 4, 0, 8, 3, 2, 6]
False
[]
True

-1

C'est en fait le moyen le plus court de le faire en utilisant la récursivité:

s'il est trié affichera True sinon affichera False

 def is_Sorted(lst):
    if len(lst) == 1:
       return True
    return lst[0] <= lst[1] and is_Sorted(lst[1:])

 any_list = [1,2,3,4]
 print is_Sorted(any_list)

Notez que cela augmentera RuntimeError: maximum recursion depth exceededpour les longues listes. Essayez any_list = range(1000).
timgeb

-1

Celui-ci, ça va ? Simple et direct.

def is_list_sorted(al):

    llength =len(al)


    for i in range (llength):
        if (al[i-1] > al[i]):
            print(al[i])
            print(al[i+1])
            print('Not sorted')
            return -1

    else :
        print('sorted')
        return  true

-3

Fonctionne définitivement en Python 3 et supérieur pour les entiers ou les chaînes:

def tail(t):
    return t[:]

letters = ['a', 'b', 'c', 'd', 'e']
rest = tail(letters)
rest.sort()
if letters == rest:
    print ('Given list is SORTED.')
else:
    print ('List NOT Sorted.')

=================================================== ====================

Une autre façon de savoir si la liste donnée est triée ou non

trees1 = list ([1, 4, 5, 3, 2])
trees2 = list (trees1)
trees2.sort()
if trees1 == trees2:
    print ('trees1 is SORTED')
else:
    print ('Not sorted')
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.