Choisir un élément aléatoire dans un ensemble


182

Comment choisir un élément aléatoire dans un ensemble? Je suis particulièrement intéressé par la sélection d'un élément aléatoire à partir d'un HashSet ou d'un LinkedHashSet, en Java. Les solutions pour d'autres langues sont également les bienvenues.


5
Vous devez spécifier certaines conditions pour voir si c'est vraiment ce que vous voulez. - Comment allez-vous choisir un élément aléatoire? - Les données doivent-elles être stockées dans un HashSet ou LinkedHashSet, et ne sont pas non plus accessibles de manière aléatoire. - Le hachage est-il volumineux? Les clés sont-elles petites?
David Nehme

Réponses:


88
int size = myHashSet.size();
int item = new Random().nextInt(size); // In real life, the Random object should be rather more shared than this
int i = 0;
for(Object obj : myhashSet)
{
    if (i == item)
        return obj;
    i++;
}

95
Si myHashSet est grand, alors ce sera une solution plutôt lente car en moyenne, (n / 2) itérations seront nécessaires pour trouver l'objet aléatoire.
daniel

6
si vos données sont dans un ensemble de hachage, vous avez besoin de temps O (n). Il n'y a aucun moyen de contourner cela si vous ne choisissez qu'un seul élément et que les données sont stockées dans un HashSet.
David Nehme

8
@David Nehme: C'est un inconvénient dans la spécification de HashSet en Java. En C ++, il est typique de pouvoir accéder directement aux buckets qui composent le hashset, ce qui nous permet de sélectionner plus efficacement un élément aléatoire. Si des éléments aléatoires sont nécessaires dans Java, il peut être intéressant de définir un ensemble de hachage personnalisé qui permet à l'utilisateur de regarder sous le capot. Voir [documentation de boost] [1] pour un peu plus à ce sujet. [1] boost.org/doc/libs/1_43_0/doc/html/unordered/buckets.html
Aaron McDaid

11
Si l'ensemble n'est pas muté sur plusieurs accès, vous pouvez le copier dans un tableau, puis accéder à O (1). Utilisez simplement myHashSet.toArray ()
ykaganovich

2
@ykaganovich ne serait-ce pas aggraver les choses, puisque l'ensemble devrait être copié dans un nouveau tableau? docs.oracle.com/javase/7/docs/api/java/util/… "cette méthode doit allouer un nouveau tableau même si cette collection est sauvegardée par un tableau"
anton1980

73

Un peu lié Le saviez-vous:

Il existe des méthodes utiles java.util.Collectionspour mélanger des collections entières: Collections.shuffle(List<?>)et Collections.shuffle(List<?> list, Random rnd).


Impressionnant! Ce n'est pas référencé nulle part dans la documentation java! Comme Python's random.shuffle ()
smci

27
Mais cela ne fonctionne qu'avec des listes, c'est-à-dire des structures qui ont une fonction .get ().
coneyhelixlake

4
@ bourbaki4481472 est absolument correct. Cela ne fonctionne que pour les collections étendant l' Listinterface, pas pour l' Setinterface discutée par l'OP.
Thomas

32

Solution rapide pour Java utilisant un ArrayListet unHashMap : [élément -> index].

Motivation: j'avais besoin d'un ensemble d'éléments avec des RandomAccesspropriétés, en particulier pour choisir un élément aléatoire dans l'ensemble (voir pollRandomméthode). La navigation aléatoire dans un arbre binaire n'est pas précise: les arbres ne sont pas parfaitement équilibrés, ce qui ne conduirait pas à une distribution uniforme.

public class RandomSet<E> extends AbstractSet<E> {

    List<E> dta = new ArrayList<E>();
    Map<E, Integer> idx = new HashMap<E, Integer>();

    public RandomSet() {
    }

    public RandomSet(Collection<E> items) {
        for (E item : items) {
            idx.put(item, dta.size());
            dta.add(item);
        }
    }

    @Override
    public boolean add(E item) {
        if (idx.containsKey(item)) {
            return false;
        }
        idx.put(item, dta.size());
        dta.add(item);
        return true;
    }

    /**
     * Override element at position <code>id</code> with last element.
     * @param id
     */
    public E removeAt(int id) {
        if (id >= dta.size()) {
            return null;
        }
        E res = dta.get(id);
        idx.remove(res);
        E last = dta.remove(dta.size() - 1);
        // skip filling the hole if last is removed
        if (id < dta.size()) {
            idx.put(last, id);
            dta.set(id, last);
        }
        return res;
    }

    @Override
    public boolean remove(Object item) {
        @SuppressWarnings(value = "element-type-mismatch")
        Integer id = idx.get(item);
        if (id == null) {
            return false;
        }
        removeAt(id);
        return true;
    }

    public E get(int i) {
        return dta.get(i);
    }

    public E pollRandom(Random rnd) {
        if (dta.isEmpty()) {
            return null;
        }
        int id = rnd.nextInt(dta.size());
        return removeAt(id);
    }

    @Override
    public int size() {
        return dta.size();
    }

    @Override
    public Iterator<E> iterator() {
        return dta.iterator();
    }
}

Eh bien, cela fonctionnerait, mais la question portait sur l'interface Set. Cette solution oblige les utilisateurs à avoir des références de type concrètes du RandomSet.
Johan Tidén

J'aime vraiment cette solution, mais ce n'est pas thread-safe, des inexactitudes entre la carte et la liste peuvent survenir, alors j'ajouterais des blocs synchronisés
Kostas Chalkias

@KonstantinosChalkias les collections intégrées ne sont pas non plus thread-safe. Seuls ceux avec le nom Concurrentsont vraiment sûrs, ceux enveloppés avec Collections.synchronized()sont semi-sûrs. De plus, l'OP n'a rien dit sur la concurrence, c'est donc une réponse valide et bonne.
TWiStErRob

L'itérateur retourné ici ne devrait pas pouvoir supprimer des éléments de dta(cela peut être réalisé via les goyaves Iterators.unmodifiableIteratorpar exemple). Sinon, les implémentations par défaut, par exemple removeAll et retentionAll dans AbstractSet et ses parents travaillant avec cet itérateur, vous gâcheront RandomSet!
muued

Belle solution. Vous pouvez en fait utiliser un arbre si chaque nœud contient le nombre de nœuds dans le sous-arbre qu'il racine. Ensuite, calculez un réel aléatoire en 0..1 et prenez une décision pondérée à 3 voies (sélectionnez le nœud actuel ou descendez dans le sous-arbre gauche ou droit) à chaque nœud en fonction du nombre de nœuds. Mais à mon avis, votre solution est bien meilleure.
Gene le

31

C'est plus rapide que la boucle for-each dans la réponse acceptée:

int index = rand.nextInt(set.size());
Iterator<Object> iter = set.iterator();
for (int i = 0; i < index; i++) {
    iter.next();
}
return iter.next();

La construction for-each appelle Iterator.hasNext()sur chaque boucle, mais depuisindex < set.size() , cette vérification est inutile. J'ai vu une augmentation de 10 à 20% de la vitesse, mais YMMV. (De plus, cela se compile sans avoir à ajouter une instruction return supplémentaire.)

Notez que ce code (et la plupart des autres réponses) peut être appliqué à n'importe quelle collection, pas seulement à Set. Sous forme de méthode générique:

public static <E> E choice(Collection<? extends E> coll, Random rand) {
    if (coll.size() == 0) {
        return null; // or throw IAE, if you prefer
    }

    int index = rand.nextInt(coll.size());
    if (coll instanceof List) { // optimization
        return ((List<? extends E>) coll).get(index);
    } else {
        Iterator<? extends E> iter = coll.iterator();
        for (int i = 0; i < index; i++) {
            iter.next();
        }
        return iter.next();
    }
}

15

Si vous voulez le faire en Java, vous devriez envisager de copier les éléments dans une sorte de collection à accès aléatoire (comme une ArrayList). Car, à moins que votre ensemble ne soit petit, l'accès à l'élément sélectionné sera coûteux (O (n) au lieu de O (1)). [ed: la copie de la liste est également O (n)]

Vous pouvez également rechercher une autre implémentation Set qui correspond plus étroitement à vos besoins. Le ListOrderedSet de Commons Collections semble prometteur.


8
La copie dans une liste coûtera O (n) en temps et utilisera également la mémoire O (n), alors pourquoi serait-ce un meilleur choix que de récupérer directement la carte?
mdma le

12
Cela dépend du nombre de fois que vous souhaitez choisir parmi l'ensemble. La copie est une opération unique et vous pouvez ensuite choisir parmi l'ensemble autant de fois que nécessaire. Si vous ne choisissez qu'un seul élément, alors oui, la copie ne rend pas les choses plus rapides.
Dan Dyer le

Ce n'est qu'une opération unique si vous voulez pouvoir choisir avec répétition. Si vous voulez que l'élément choisi soit supprimé de l'ensemble, vous reviendrez à O (n).
TurnipEntropy

15

Dans Java 8:

static <E> E getRandomSetElement(Set<E> set) {
    return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}

9

En Java:

Set<Integer> set = new LinkedHashSet<Integer>(3);
set.add(1);
set.add(2);
set.add(3);

Random rand = new Random(System.currentTimeMillis());
int[] setArray = (int[]) set.toArray();
for (int i = 0; i < 10; ++i) {
    System.out.println(setArray[rand.nextInt(set.size())]);
}

11
Votre réponse fonctionne, mais elle n'est pas très efficace à cause de la partie set.toArray ().
Clue Less

12
vous devez déplacer le toArray en dehors de la boucle.
David Nehme

8
List asList = new ArrayList(mySet);
Collections.shuffle(asList);
return asList.get(0);

21
C'est extrêmement inefficace. Votre constructeur ArrayList appelle .toArray () sur l'ensemble fourni. ToArray (dans la plupart sinon toutes les implémentations de collection standard) itère sur toute la collection, remplissant un tableau au fur et à mesure. Ensuite, vous mélangez la liste, qui échange chaque élément avec un élément aléatoire. Vous feriez bien mieux d'itérer simplement sur l'ensemble vers un élément aléatoire.
Chris Bode

4

Ceci est identique à la réponse acceptée (Khoth), mais avec les variables inutiles sizeet isupprimées.

    int random = new Random().nextInt(myhashSet.size());
    for(Object obj : myhashSet) {
        if (random-- == 0) {
            return obj;
        }
    }

Bien que supprimant les deux variables susmentionnées, la solution ci-dessus reste toujours aléatoire car nous nous appuyons sur l'aléatoire (en commençant à un index choisi au hasard) pour se décrémenter à 0chaque itération.


1
La troisième ligne pourrait également être if (--random < 0) {, où randomatteint -1.
Salvador

3

Solution Clojure:

(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))

1
Cette solution est également linéaire, car pour obtenir l' nthélément, vous devez également traverser le seq.
Bruno Kim

1
Il est également linéaire car il s'intègre parfaitement dans une ligne: D
Krzysztof Wolny

2

Perl 5

@hash_keys = (keys %hash);
$rand = int(rand(@hash_keys));
print $hash{$hash_keys[$rand]};

Voici une façon de le faire.


2

C ++. Cela devrait être raisonnablement rapide, car il ne nécessite pas d'itération sur l'ensemble complet, ni de tri. Cela devrait fonctionner directement avec la plupart des compilateurs modernes, en supposant qu'ils prennent en charge tr1 . Sinon, vous devrez peut-être utiliser Boost.

Les documents Boost sont utiles ici pour expliquer cela, même si vous n'utilisez pas Boost.

L'astuce consiste à utiliser le fait que les données ont été divisées en seaux et à identifier rapidement un seau choisi au hasard (avec la probabilité appropriée).

//#include <boost/unordered_set.hpp>  
//using namespace boost;
#include <tr1/unordered_set>
using namespace std::tr1;
#include <iostream>
#include <stdlib.h>
#include <assert.h>
using namespace std;

int main() {
  unordered_set<int> u;
  u.max_load_factor(40);
  for (int i=0; i<40; i++) {
    u.insert(i);
    cout << ' ' << i;
  }
  cout << endl;
  cout << "Number of buckets: " << u.bucket_count() << endl;

  for(size_t b=0; b<u.bucket_count(); b++)
    cout << "Bucket " << b << " has " << u.bucket_size(b) << " elements. " << endl;

  for(size_t i=0; i<20; i++) {
    size_t x = rand() % u.size();
    cout << "we'll quickly get the " << x << "th item in the unordered set. ";
    size_t b;
    for(b=0; b<u.bucket_count(); b++) {
      if(x < u.bucket_size(b)) {
        break;
      } else
        x -= u.bucket_size(b);
    }
    cout << "it'll be in the " << b << "th bucket at offset " << x << ". ";
    unordered_set<int>::const_local_iterator l = u.begin(b);
    while(x>0) {
      l++;
      assert(l!=u.end(b));
      x--;
    }
    cout << "random item is " << *l << ". ";
    cout << endl;
  }
}

2

La solution ci-dessus parle en termes de latence mais ne garantit pas une probabilité égale de chaque index sélectionné.
Si cela doit être pris en compte, essayez l'échantillonnage du réservoir. http://en.wikipedia.org/wiki/Reservoir_sampling .
Collections.shuffle () (comme certains l'ont suggéré) utilise un tel algorithme.


1

Puisque vous avez dit "Les solutions pour d'autres langages sont également les bienvenues", voici la version pour Python:

>>> import random
>>> random.choice([1,2,3,4,5,6])
3
>>> random.choice([1,2,3,4,5,6])
4

3
Seulement, [1,2,3,4,5,6] n'est pas un ensemble, mais une liste, car il ne prend pas en charge des choses comme les recherches rapides.
Thomas Ahle

Vous pouvez toujours faire: >>> random.choice (list (set (range (5)))) >>> 4 Ce n'est pas l'idéal mais ça le fera si vous en avez absolument besoin.
SapphireSun

1

Ne pouvez-vous pas simplement obtenir la taille / longueur de l'ensemble / tableau, générer un nombre aléatoire entre 0 et la taille / longueur, puis appeler l'élément dont l'index correspond à ce nombre? HashSet a une méthode .size (), j'en suis presque sûr.

Dans psuedocode -

function randFromSet(target){
 var targetLength:uint = target.length()
 var randomIndex:uint = random(0,targetLength);
 return target[randomIndex];
}

Cela ne fonctionne que si le conteneur en question prend en charge la recherche d'index aléatoire. De nombreuses implémentations de conteneurs ne le font pas (par exemple, les tables de hachage, les arbres binaires, les listes liées).
David Haley

1

PHP, en supposant que "set" est un tableau:

$foo = array("alpha", "bravo", "charlie");
$index = array_rand($foo);
$val = $foo[$index];

Les fonctions Mersenne Twister sont meilleures mais il n'y a pas d'équivalent MT de array_rand en PHP.


La plupart des implémentations d'ensembles n'ont pas d'opérateur get (i) ou d'indexation, donc id suppose que c'est pourquoi OP a spécifié son ensemble
DownloadPizza

1

L'icône a un type d'ensemble et un opérateur d'élément aléatoire, unaire "?", Donc l'expression

? set( [1, 2, 3, 4, 5] )

produira un nombre aléatoire entre 1 et 5.

La graine aléatoire est initialisée à 0 lorsqu'un programme est exécuté, donc pour produire des résultats différents à chaque exécution, utilisez randomize()


1

En C #

        Random random = new Random((int)DateTime.Now.Ticks);

        OrderedDictionary od = new OrderedDictionary();

        od.Add("abc", 1);
        od.Add("def", 2);
        od.Add("ghi", 3);
        od.Add("jkl", 4);


        int randomIndex = random.Next(od.Count);

        Console.WriteLine(od[randomIndex]);

        // Can access via index or key value:
        Console.WriteLine(od[1]);
        Console.WriteLine(od["def"]);

On dirait qu'ils ont voté contre parce que le dictionnaire java merdique (ou soi-disant LinkedHashSet, quoi que ce soit) ne peut pas être "accédé au hasard" (auquel on accède par clé, je suppose). La merde java me fait tellement rire
Federico Berasategui

1

Solution Javascript;)

function choose (set) {
    return set[Math.floor(Math.random() * set.length)];
}

var set  = [1, 2, 3, 4], rand = choose (set);

Ou bien:

Array.prototype.choose = function () {
    return this[Math.floor(Math.random() * this.length)];
};

[1, 2, 3, 4].choose();

Je préfère la deuxième alternative. :-)
marcospereira

ooh, j'aime étendre l'ajout de la nouvelle méthode de tableau!
matt lohkamp le

1

Dans lisp

(defun pick-random (set)
       (nth (random (length set)) set))

Cela ne fonctionne que pour les listes, non? Avec ELTcela pourrait fonctionner pour n'importe quelle séquence.
Ken

1

Dans Mathematica:

a = {1, 2, 3, 4, 5}

a[[ ⌈ Length[a] Random[] ⌉ ]]

Ou, dans les versions récentes, simplement:

RandomChoice[a]

Cela a reçu un vote défavorable, peut-être parce qu'il manque d'explication, alors en voici une:

Random[]génère un flottant pseudo-aléatoire entre 0 et 1. Celui-ci est multiplié par la longueur de la liste, puis la fonction de plafond est utilisée pour arrondir au nombre entier suivant. Cet index est ensuite extrait de a.

Étant donné que la fonctionnalité de table de hachage est souvent effectuée avec des règles dans Mathematica et que les règles sont stockées dans des listes, on peut utiliser:

a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};

1

Que diriez-vous juste

public static <A> A getRandomElement(Collection<A> c, Random r) {
  return new ArrayList<A>(c).get(r.nextInt(c.size()));
}

1

Pour le plaisir, j'ai écrit un RandomHashSet basé sur un échantillonnage de rejet. C'est un peu piraté, car HashMap ne nous permet pas d'accéder directement à sa table, mais cela devrait fonctionner très bien.

Il n'utilise aucune mémoire supplémentaire et le temps de recherche est O (1) amorti. (Parce que java HashTable est dense).

class RandomHashSet<V> extends AbstractSet<V> {
    private Map<Object,V> map = new HashMap<>();
    public boolean add(V v) {
        return map.put(new WrapKey<V>(v),v) == null;
    }
    @Override
    public Iterator<V> iterator() {
        return new Iterator<V>() {
            RandKey key = new RandKey();
            @Override public boolean hasNext() {
                return true;
            }
            @Override public V next() {
                while (true) {
                    key.next();
                    V v = map.get(key);
                    if (v != null)
                        return v;
                }
            }
            @Override public void remove() {
                throw new NotImplementedException();
            }
        };
    }
    @Override
    public int size() {
        return map.size();
    }
    static class WrapKey<V> {
        private V v;
        WrapKey(V v) {
            this.v = v;
        }
        @Override public int hashCode() {
            return v.hashCode();
        }
        @Override public boolean equals(Object o) {
            if (o instanceof RandKey)
                return true;
            return v.equals(o);
        }
    }
    static class RandKey {
        private Random rand = new Random();
        int key = rand.nextInt();
        public void next() {
            key = rand.nextInt();
        }
        @Override public int hashCode() {
            return key;
        }
        @Override public boolean equals(Object o) {
            return true;
        }
    }
}

1
Exactement ce à quoi je pensais! Meilleure réponse!
mmm

En fait, pour y revenir, je suppose que ce n'est pas tout à fait uniforme, si le hashmap a de nombreuses collisions et que nous faisons beaucoup de requêtes. En effet, le hashmap java utilise des buckets / chaining et ce code renverra toujours le premier élément du bucket particulier. Cependant, nous sommes toujours uniformes sur le caractère aléatoire de la fonction de hachage.
Thomas Ahle

1

Le plus simple avec Java 8 est:

outbound.stream().skip(n % outbound.size()).findFirst().get()

nest un entier aléatoire. Bien sûr, il est moins performant que celui avec lefor(elem: Col)


1

Avec Guava, nous pouvons faire un peu mieux que la réponse de Khoth:

public static E random(Set<E> set) {
  int index = random.nextInt(set.size();
  if (set instanceof ImmutableSet) {
    // ImmutableSet.asList() is O(1), as is .get() on the returned list
    return set.asList().get(index);
  }
  return Iterables.get(set, index);
}

0

PHP, en utilisant MT:

$items_array = array("alpha", "bravo", "charlie");
$last_pos = count($items_array) - 1;
$random_pos = mt_rand(0, $last_pos);
$random_item = $items_array[$random_pos];

0

vous pouvez également transférer l'ensemble vers un tableau d'utilisation du tableau, il fonctionnera probablement à petite échelle.Je vois de toute façon la boucle for dans la réponse la plus votée est O (n)

Object[] arr = set.toArray();

int v = (int) arr[rnd.nextInt(arr.length)];

0

Si vous voulez vraiment juste choisir "n'importe quel" objet dans le Set, sans aucune garantie sur le caractère aléatoire, le plus simple est de prendre le premier retourné par l'itérateur.

    Set<Integer> s = ...
    Iterator<Integer> it = s.iterator();
    if(it.hasNext()){
        Integer i = it.next();
        // i is a "random" object from set
    }

1
Ce ne sera cependant pas un choix aléatoire. Imaginez effectuer la même opération sur le même ensemble plusieurs fois. Je pense que l'ordre sera le même.
Menezes Sousa

0

Une solution générique utilisant la réponse de Khoth comme point de départ.

/**
 * @param set a Set in which to look for a random element
 * @param <T> generic type of the Set elements
 * @return a random element in the Set or null if the set is empty
 */
public <T> T randomElement(Set<T> set) {
    int size = set.size();
    int item = random.nextInt(size);
    int i = 0;
    for (T obj : set) {
        if (i == item) {
            return obj;
        }
        i++;
    }
    return null;
}

0

Malheureusement, cela ne peut pas être fait efficacement (mieux que O (n)) dans l'un des conteneurs d'ensemble de la bibliothèque standard.

C'est étrange, car il est très facile d'ajouter une fonction de sélection aléatoire aux ensembles de hachage ainsi qu'aux ensembles binaires. Dans un ensemble de hachage pas trop clairsemé, vous pouvez essayer des entrées aléatoires, jusqu'à ce que vous obteniez un résultat. Pour un arbre binaire, vous pouvez choisir aléatoirement entre le sous-arbre gauche ou droit, avec un maximum de O (log2) étapes. J'ai implémenté une démo de ce qui suit ci-dessous:

import random

class Node:
    def __init__(self, object):
        self.object = object
        self.value = hash(object)
        self.size = 1
        self.a = self.b = None

class RandomSet:
    def __init__(self):
        self.top = None

    def add(self, object):
        """ Add any hashable object to the set.
            Notice: In this simple implementation you shouldn't add two
                    identical items. """
        new = Node(object)
        if not self.top: self.top = new
        else: self._recursiveAdd(self.top, new)
    def _recursiveAdd(self, top, new):
        top.size += 1
        if new.value < top.value:
            if not top.a: top.a = new
            else: self._recursiveAdd(top.a, new)
        else:
            if not top.b: top.b = new
            else: self._recursiveAdd(top.b, new)

    def pickRandom(self):
        """ Pick a random item in O(log2) time.
            Does a maximum of O(log2) calls to random as well. """
        return self._recursivePickRandom(self.top)
    def _recursivePickRandom(self, top):
        r = random.randrange(top.size)
        if r == 0: return top.object
        elif top.a and r <= top.a.size: return self._recursivePickRandom(top.a)
        return self._recursivePickRandom(top.b)

if __name__ == '__main__':
    s = RandomSet()
    for i in [5,3,7,1,4,6,9,2,8,0]:
        s.add(i)

    dists = [0]*10
    for i in xrange(10000):
        dists[s.pickRandom()] += 1
    print dists

J'ai eu [995, 975, 971, 995, 1057, 1004, 966, 1052, 984, 1001] comme sortie, donc la distribution semble bonne.

J'ai lutté avec le même problème pour moi-même, et je n'ai pas encore décidé que le gain de performances de ce choix plus efficace vaut la peine d'utiliser une collection basée sur python. Je pourrais bien sûr l'affiner et le traduire en C, mais c'est trop de travail pour moi aujourd'hui :)


1
Une raison pour laquelle je pense que cela n'est pas implémenté dans un arbre binaire est qu'une telle méthode ne choisirait pas les éléments de manière uniforme. Comme il s'agit de nœuds sans enfants gauche / droit, une situation peut se produire où l'enfant gauche contient plus d'éléments que l'enfant droit (ou vice versa), ce qui rendrait plus probable le choix d'un élément à l'enfant droit (ou gauche).
Willem Van Onsem

1
@CommuSoft: C'est pourquoi je stocke la taille de chaque sous-arbre, afin que je puisse choisir mes probabilités en fonction de celles-ci.
Thomas Ahle
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.