Rendre aléatoire une liste <T>


855

Quelle est la meilleure façon de randomiser l'ordre d'une liste générique en C #? J'ai un ensemble fini de 75 numéros dans une liste à laquelle je voudrais attribuer un ordre aléatoire, afin de les dessiner pour une application de type loterie.


3
Il existe un problème ouvert pour intégrer cette fonctionnalité à .NET: github.com/dotnet/corefx/issues/461
Natan

5
Vous pouvez être intéressé par ce package NuGet , qui contient des méthodes d'extension pour mélanger IList <T> et IEnumerable <T> en utilisant l'algorithme Fisher-Yates mentionné ci-dessous
ChaseMedallion

3
@Natan, ils ont clos le problème parce que quelqu'un "a travaillé sur de nombreux projets et développé de nombreuses bibliothèques et n'a jamais eu besoin d'une telle méthode" qui m'a énervé. Maintenant, nous devons nous enquêter, rechercher les meilleures implémentations, perdre du temps pour simplement réinventer la roue.
Vitalii Isaenko

1
Suis-je en train de voir ça? Pas une seule réponse fonctionnelle valide après 10 ans? Peut-être que nous avons besoin d'une autre prime pour une solution qui traite de la quantité d'entropie nécessaire, pour mélanger une liste avec 75 numéros $ log2 (75!) = 364 $ et comment nous pouvons l'obtenir. Il faudrait réamorcer même un RNG cryptographiquement sécurisé avec 256 bits d'entropie au moins une fois pendant un remaniement de pêcheur.
Falco

1
Et si le codeur habituel ne peut pas résoudre ce problème, avons-nous tous joué pour toujours les mêmes 0,01% des jeux de solitaire possibles?
Falco

Réponses:


1137

Mélangez tout (I)Listavec une méthode d'extension basée sur le mélange Fisher-Yates :

private static Random rng = new Random();  

public static void Shuffle<T>(this IList<T> list)  
{  
    int n = list.Count;  
    while (n > 1) {  
        n--;  
        int k = rng.Next(n + 1);  
        T value = list[k];  
        list[k] = list[n];  
        list[n] = value;  
    }  
}

Usage:

List<Product> products = GetProducts();
products.Shuffle();

Le code ci-dessus utilise la méthode System.Random très critiquée pour sélectionner les candidats à l'échange. C'est rapide mais pas aussi aléatoire qu'il devrait l'être. Si vous avez besoin d'une meilleure qualité d'aléatoire dans vos shuffles, utilisez le générateur de nombres aléatoires dans System.Security.Cryptography comme ceci:

using System.Security.Cryptography;
...
public static void Shuffle<T>(this IList<T> list)
{
    RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
    int n = list.Count;
    while (n > 1)
    {
        byte[] box = new byte[1];
        do provider.GetBytes(box);
        while (!(box[0] < n * (Byte.MaxValue / n)));
        int k = (box[0] % n);
        n--;
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}

Une comparaison simple est disponible sur ce blog (WayBack Machine).

Edit: Depuis que j'ai écrit cette réponse il y a quelques années, beaucoup de gens m'ont commenté ou écrit, pour souligner le gros défaut idiot de ma comparaison. Ils ont bien sûr raison. Il n'y a rien de mal avec System.Random s'il est utilisé de la manière prévue. Dans mon premier exemple ci-dessus, j'instancie la variable rng à l'intérieur de la méthode Shuffle, qui demande des problèmes si la méthode va être appelée à plusieurs reprises. Ci-dessous est un exemple fixe et complet basé sur un commentaire vraiment utile reçu aujourd'hui de @weston ici sur SO.

Program.cs:

using System;
using System.Collections.Generic;
using System.Threading;

namespace SimpleLottery
{
  class Program
  {
    private static void Main(string[] args)
    {
      var numbers = new List<int>(Enumerable.Range(1, 75));
      numbers.Shuffle();
      Console.WriteLine("The winning numbers are: {0}", string.Join(",  ", numbers.GetRange(0, 5)));
    }
  }

  public static class ThreadSafeRandom
  {
      [ThreadStatic] private static Random Local;

      public static Random ThisThreadsRandom
      {
          get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
      }
  }

  static class MyExtensions
  {
    public static void Shuffle<T>(this IList<T> list)
    {
      int n = list.Count;
      while (n > 1)
      {
        n--;
        int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
      }
    }
  }
}

32
Que faire si list.Count est> Byte.MaxValue? Si n = 1000, alors 255/1000 = 0, la boucle do sera donc une boucle infinie car la case [0] <0 est toujours fausse.
AndrewS

18
Je voudrais souligner que la comparaison est erronée. L'utilisation du <code> nouveau Random () </code> dans une boucle est le problème, pas le caractère aléatoire du <code> Random </code> Explication
Sven

9
C'est une bonne idée de passer une instance de Random à la méthode Shuffle plutôt que de la créer à l'intérieur comme si vous appeliez Shuffle plusieurs fois de suite (par exemple en mélangeant beaucoup de listes courtes), les listes seront toutes mélangées de la même manière. (par exemple, le premier élément est toujours déplacé en position 3).
Mark Heath

7
Faire juste Random rng = new Random();un staticrésoudrait le problème dans le post de comparaison. Comme chaque appel suivant ferait suite aux appels précédents, dernier résultat aléatoire.
weston

5
# 2, il n'est pas clair que la version avec le générateur Crypto fonctionne parce que la plage maximale d'un octet est de 255, donc toute liste plus grande que celle-ci ne sera pas mélangée correctement.
Mark Sowul

336

Si nous avons seulement besoin de mélanger les éléments dans un ordre complètement aléatoire (juste pour mélanger les éléments dans une liste), je préfère ce code simple mais efficace qui commande les éléments par guid ...

var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();

40
Les GUID sont censés être uniques et non aléatoires. Une partie est basée sur la machine et une autre basée sur le temps partiel et seule une petite partie est aléatoire. blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx
Despertar

99
Ceci est une belle solution élégante. Si vous voulez que quelque chose d'autre qu'un guid génère un caractère aléatoire, commandez simplement par autre chose. Par exemple: var shuffledcards = cards.OrderBy(a => rng.Next()); compilr.com/grenade/sandbox/Program.cs
grenade

20
Je t'en prie, non. C'est faux. «ordonner par hasard» n'est absolument PAS un mélange: vous introduisez un biais et, pire, vous risquez de faire des boucles infinies
Vito De Tullio

78
@VitoDeTullio: Vous vous souvenez mal. Vous risquez des boucles infinies lorsque vous fournissez une fonction de comparaison aléatoire ; une fonction de comparaison est nécessaire pour produire une commande totale cohérente . Une clé aléatoire est très bien. Cette suggestion est erronée parce que les guides ne sont pas garantis d'être aléatoires , pas parce que la technique de tri par une clé aléatoire est erronée.
Eric Lippert

24
@Doug: NewGuidgarantit seulement qu'il vous donne un GUID unique. Il ne fait aucune garantie sur le caractère aléatoire. Si vous utilisez un GUID à des fins autres que la création d'une valeur unique , vous le faites mal.
Eric Lippert

120

Je suis un peu surpris par toutes les versions maladroites de cet algorithme simple ici. Fisher-Yates (ou Knuth shuffle) est un peu délicat mais très compact. Pourquoi est-ce difficile? Parce que vous devez faire attention à ce que votre générateur de nombres aléatoires r(a,b)renvoie une valeur où best inclus ou exclusif. J'ai également modifié la description de Wikipedia afin que les gens ne suivent pas aveuglément le pseudocode et créent des bogues difficiles à détecter. Pour .Net, Random.Next(a,b)renvoie un numéro exclusif de bso sans plus tarder, voici comment il peut être implémenté en C # /. Net:

public static void Shuffle<T>(this IList<T> list, Random rnd)
{
    for(var i=list.Count; i > 0; i--)
        list.Swap(0, rnd.Next(0, i));
}

public static void Swap<T>(this IList<T> list, int i, int j)
{
    var temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}

Essayez ce code .


Ne serait-il pas préférable de changer rnd (i, list.Count) en rnd (0, list.Count) afin que toute carte puisse être échangée?
Donuts

16
@Donuts - non. Si vous faites cela, vous ajouterez un biais dans la lecture aléatoire.
Shital Shah

2
En séparant Swap <T> vers une méthode distincte, il semble que vous causiez beaucoup d'allocations T inutiles pour temp.
Clay

2
Je dirais que LINQ pourrait potentiellement ralentir les performances du brassage, et ce serait une raison de ne pas l'utiliser, surtout compte tenu de la relative simplicité du code.
winglerw28

7
Quand i = list.Count - 1, c'est-à-dire la dernière itération, vous rnd.Next(i, list.Count)rendra i. Vous avez donc besoin i < list.Count -1comme condition de boucle. Eh bien, vous n'en avez pas `` besoin '', mais cela économise 1 itération;)
Pod

78

Méthode d'extension pour IEnumerable:

public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
    Random rnd = new Random();
    return source.OrderBy<T, int>((item) => rnd.Next());
}

3
Notez que ce n'est pas thread-safe, même s'il est utilisé sur une liste thread-safe
BlueRaja - Danny Pflughoeft

1
comment donner la liste <string> à cette fonction?
MonsterMMORPG

8
Il y a deux problèmes importants avec cet algorithme: - OrderByutilise une variante QuickSort pour trier les éléments par leurs clés (ostensiblement aléatoires). Les performances de QuickSort sont O (N log N) ; en revanche, un shuffle de Fisher-Yates est O (N) . Pour une collection de 75 éléments, ce n'est peut-être pas un gros problème, mais la différence deviendra prononcée pour les grandes collections.
John Beyer

10
... - Random.Next()peut produire une distribution de valeurs raisonnablement pseudo-aléatoire, mais cela ne garantit pas que les valeurs seront uniques. La probabilité de clés en double augmente (de façon non linéaire) avec N jusqu'à ce qu'elle atteigne la certitude lorsque N atteint 2 ^ 32 + 1. Le OrderByQuickSort est un type stable ; ainsi, si plusieurs éléments se voient attribuer la même valeur d'index pseudo-aléatoire, alors leur ordre dans la séquence de sortie sera le même que dans la séquence d'entrée; ainsi, un biais est introduit dans le "shuffle".
John Beyer

27
@JohnBeyer: Il y a des problèmes bien plus importants que cette source de biais. Il n'y a que quatre milliards de graines possibles pour Random, ce qui est beaucoup, beaucoup moins que le nombre de shuffles possibles d'un ensemble de taille moyenne. Seule une infime fraction des mélanges possibles peut être générée. Ce biais éclipse le biais dû à des collisions accidentelles.
Eric Lippert

16

L'idée est d'obtenir un objet anonyme avec un article et un ordre aléatoire, puis de réorganiser les articles en fonction de cet ordre et de renvoyer la valeur:

var result = items.Select(x => new { value = x, order = rnd.Next() })
            .OrderBy(x => x.order).Select(x => x.value).ToList()

3
meilleure solution one liner
vipin8169

1
Vous manquez un point-virgule à la fin fyi
reggaeguitar

Si quelqu'un n'est pas sûr de rnd, ajoutez ceci avant le code ci-dessus Random rnd = new Random ();
Greg Trevellick

10
    public static List<T> Randomize<T>(List<T> list)
    {
        List<T> randomizedList = new List<T>();
        Random rnd = new Random();
        while (list.Count > 0)
        {
            int index = rnd.Next(0, list.Count); //pick a random item from the master list
            randomizedList.Add(list[index]); //place it at the end of the randomized list
            list.RemoveAt(index);
        }
        return randomizedList;
    }


4
Ne devriez-vous pas faire quelque chose comme var listCopy = list.ToList()pour éviter de sauter tous les éléments de la liste entrante? Je ne vois pas vraiment pourquoi vous voudriez faire muter ces listes pour les vider.
Chris Marisic

9

EDITER C'est RemoveAtune faiblesse de ma version précédente. Cette solution permet de surmonter cela.

public static IEnumerable<T> Shuffle<T>(
        this IEnumerable<T> source,
        Random generator = null)
{
    if (generator == null)
    {
        generator = new Random();
    }

    var elements = source.ToArray();
    for (var i = elements.Length - 1; i >= 0; i--)
    {
        var swapIndex = generator.Next(i + 1);
        yield return elements[swapIndex];
        elements[swapIndex] = elements[i];
    }
}

Notez l'option Random generator, si l'implémentation du framework de base Randomn'est pas thread-safe ou cryptographiquement assez forte pour vos besoins, vous pouvez injecter votre implémentation dans l'opération.

Une implémentation appropriée pour une implémentation cryptographique forte thread-safe Randompeut être trouvée dans cette réponse.


Voici une idée, étendre IList d'une manière (espérons-le) efficace.

public static IEnumerable<T> Shuffle<T>(this IList<T> list)
{
    var choices = Enumerable.Range(0, list.Count).ToList();
    var rng = new Random();
    for(int n = choices.Count; n > 1; n--)
    {
        int k = rng.Next(n);
        yield return list[choices[k]];
        choices.RemoveAt(k);
    }

    yield return list[choices[0]];
}


Voir stackoverflow.com/questions/4412405/… . vous devez déjà être au courant.
nawfal

@nawfal voir ma mise en œuvre améliorée.
Jodrell

1
hmm assez juste. C'est GetNextou Next?
nawfal

4

Vous pouvez y parvenir en utilisant cette méthode d'extension simple

public static class IEnumerableExtensions
{

    public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
    {
        Random r = new Random();

        return target.OrderBy(x=>(r.Next()));
    }        
}

et vous pouvez l'utiliser en procédant comme suit

// use this on any collection that implements IEnumerable!
// List, Array, HashSet, Collection, etc

List<string> myList = new List<string> { "hello", "random", "world", "foo", "bar", "bat", "baz" };

foreach (string s in myList.Randomize())
{
    Console.WriteLine(s);
}

3
Je garderais l' Randominstance de classe en dehors de la fonction en tant que staticvariable. Sinon, vous pourriez obtenir la même graine de randomisation du minuteur si elle est appelée rapidement.
Lemonseed

Une note intéressante - si vous instanciez rapidement la classe Random dans une boucle, disons entre 0 ms et 200 ms l'une de l'autre, alors vous avez une très grande chance d'obtenir la même graine de randomisation - ce qui entraîne alors la répétition des résultats. Vous pouvez cependant contourner cela en utilisant Random rand = new Random (Guid.NewGuid (). GetHashCode ()); Cela force effectivement la randomisation à être dérivée du Guid.NewGuid ()
Baaleos

4

C'est ma méthode préférée de lecture aléatoire lorsqu'il est souhaitable de ne pas modifier l'original. C'est une variante de l' algorithme "inside-out" de Fisher – Yates qui fonctionne sur n'importe quelle séquence énumérable (la longueur de sourcen'a pas besoin d'être connue depuis le début).

public static IList<T> NextList<T>(this Random r, IEnumerable<T> source)
{
  var list = new List<T>();
  foreach (var item in source)
  {
    var i = r.Next(list.Count + 1);
    if (i == list.Count)
    {
      list.Add(item);
    }
    else
    {
      var temp = list[i];
      list[i] = item;
      list.Add(temp);
    }
  }
  return list;
}

Cet algorithme peut également être implémenté en allouant une plage de 0à length - 1et en épuisant aléatoirement les indices en échangeant l'index choisi au hasard avec le dernier index jusqu'à ce que tous les indices aient été choisis exactement une fois. Ce code ci-dessus accomplit exactement la même chose mais sans l'allocation supplémentaire. Ce qui est plutôt soigné.

En ce qui concerne la Randomclasse, c'est un générateur de nombres à usage général (et si je dirigeais une loterie, j'envisagerais d'utiliser quelque chose de différent). Il s'appuie également sur une valeur de départ basée sur le temps par défaut. Un petit soulagement du problème consiste à amorcer la Randomclasse avec le RNGCryptoServiceProviderou vous pouvez utiliser le RNGCryptoServiceProviderdans une méthode similaire à celle-ci (voir ci-dessous) pour générer des valeurs doubles à virgule flottante aléatoires uniformément choisies, mais le fonctionnement d'une loterie nécessite à peu près la compréhension du caractère aléatoire et de la nature de la source de hasard.

var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);

Le but de générer un double aléatoire (entre 0 et 1 exclusivement) est d'utiliser pour évoluer vers une solution entière. Si vous devez choisir quelque chose dans une liste basée sur un double aléatoire, xce sera toujours 0 <= x && x < 1simple.

return list[(int)(x * list.Count)];

Prendre plaisir!


4

Si cela ne vous dérange pas d'en utiliser deux Lists, c'est probablement la façon la plus simple de le faire, mais probablement pas la plus efficace ou imprévisible:

List<int> xList = new List<int>() { 1, 2, 3, 4, 5 };
List<int> deck = new List<int>();

foreach (int xInt in xList)
    deck.Insert(random.Next(0, deck.Count + 1), xInt);

3

Si vous avez un nombre fixe (75), vous pouvez créer un tableau avec 75 éléments, puis énumérer votre liste, en déplaçant les éléments vers des positions aléatoires dans le tableau. Vous pouvez générer le mappage du numéro de liste à l'index du tableau à l'aide du shuffle Fisher-Yates .


3

J'utilise habituellement:

var list = new List<T> ();
fillList (list);
var randomizedList = new List<T> ();
var rnd = new Random ();
while (list.Count != 0)
{
    var index = rnd.Next (0, list.Count);
    randomizedList.Add (list [index]);
    list.RemoveAt (index);
}

list.RemoveAt est une opération O (n), ce qui rend cette implémentation trop lente.
George Polevoy

1
    List<T> OriginalList = new List<T>();
    List<T> TempList = new List<T>();
    Random random = new Random();
    int length = OriginalList.Count;
    int TempIndex = 0;

    while (length > 0) {
        TempIndex = random.Next(0, length);  // get random value between 0 and original length
        TempList.Add(OriginalList[TempIndex]); // add to temp list
        OriginalList.RemoveAt(TempIndex); // remove from original list
        length = OriginalList.Count;  // get new list <T> length.
    }

    OriginalList = new List<T>();
    OriginalList = TempList; // copy all items from temp list to original list.

0

Voici un Shuffler efficace qui retourne un tableau d'octets de valeurs mélangées. Il ne mélange jamais plus que nécessaire. Il peut être redémarré là où il s'était précédemment arrêté. Mon implémentation réelle (non illustrée) est un composant MEF qui permet un shuffler de remplacement spécifié par l'utilisateur.

    public byte[] Shuffle(byte[] array, int start, int count)
    {
        int n = array.Length - start;
        byte[] shuffled = new byte[count];
        for(int i = 0; i < count; i++, start++)
        {
            int k = UniformRandomGenerator.Next(n--) + start;
            shuffled[i] = array[k];
            array[k] = array[start];
            array[start] = shuffled[i];
        }
        return shuffled;
    }

"


0

Voici un moyen sûr pour les threads:

public static class EnumerableExtension
{
    private static Random globalRng = new Random();

    [ThreadStatic]
    private static Random _rng;

    private static Random rng 
    {
        get
        {
            if (_rng == null)
            {
                int seed;
                lock (globalRng)
                {
                    seed = globalRng.Next();
                }
                _rng = new Random(seed);
             }
             return _rng;
         }
    }

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
    {
        return items.OrderBy (i => rng.Next());
    }
}

0
 public Deck(IEnumerable<Card> initialCards) 
    {
    cards = new List<Card>(initialCards);
    public void Shuffle() 
     }
    {
        List<Card> NewCards = new List<Card>();
        while (cards.Count > 0) 
        {
            int CardToMove = random.Next(cards.Count);
            NewCards.Add(cards[CardToMove]);
            cards.RemoveAt(CardToMove);
        }
        cards = NewCards;
    }

public IEnumerable<string> GetCardNames() 

{
    string[] CardNames = new string[cards.Count];
    for (int i = 0; i < cards.Count; i++)
    CardNames[i] = cards[i].Name;
    return CardNames;
}

Deck deck1;
Deck deck2;
Random random = new Random();

public Form1() 
{

InitializeComponent();
ResetDeck(1);
ResetDeck(2);
RedrawDeck(1);
 RedrawDeck(2);

}



 private void ResetDeck(int deckNumber) 
    {
    if (deckNumber == 1) 
{
      int numberOfCards = random.Next(1, 11);
      deck1 = new Deck(new Card[] { });
      for (int i = 0; i < numberOfCards; i++)
           deck1.Add(new Card((Suits)random.Next(4),(Values)random.Next(1, 14)));
       deck1.Sort();
}


   else
    deck2 = new Deck();
 }

private void reset1_Click(object sender, EventArgs e) {
ResetDeck(1);
RedrawDeck(1);

}

private void shuffle1_Click(object sender, EventArgs e) 
{
    deck1.Shuffle();
    RedrawDeck(1);

}

private void moveToDeck1_Click(object sender, EventArgs e) 
{

    if (listBox2.SelectedIndex >= 0)
    if (deck2.Count > 0) {
    deck1.Add(deck2.Deal(listBox2.SelectedIndex));

}

    RedrawDeck(1);
    RedrawDeck(2);

}

2
Bienvenue dans Stack Overflow! Veuillez envisager d'ajouter quelques explications à votre réponse, plutôt qu'un simple bloc de code. Notre objectif ici est d'éduquer les gens afin qu'ils comprennent la réponse et puissent l'appliquer dans d'autres situations. Si vous commentez votre code et ajoutez une explication, vous rendrez votre réponse plus utile non seulement à la personne qui a posé la question cette fois, mais à toute personne future qui pourrait avoir le même problème.
starsplusplus

4
La plupart de ce code est totalement hors de propos pour la question, et la seule partie utile répète essentiellement la réponse d'Adam Tegen d'il y a presque 6 ans.
TC

0

Une simple modification de la réponse acceptée qui renvoie une nouvelle liste au lieu de travailler sur place, et accepte la plus générale IEnumerable<T>comme le font de nombreuses autres méthodes Linq.

private static Random rng = new Random();

/// <summary>
/// Returns a new list where the elements are randomly shuffled.
/// Based on the Fisher-Yates shuffle, which has O(n) complexity.
/// </summary>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list) {
    var source = list.ToList();
    int n = source.Count;
    var shuffled = new List<T>(n);
    shuffled.AddRange(source);
    while (n > 1) {
        n--;
        int k = rng.Next(n + 1);
        T value = shuffled[k];
        shuffled[k] = shuffled[n];
        shuffled[n] = value;
    }
    return shuffled;
}


-5

Ancien poste à coup sûr, mais j'utilise juste un GUID.

Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();

Un GUID est toujours unique et puisqu'il est régénéré à chaque fois que le résultat change à chaque fois.


Compact, mais avez-vous une référence sur le tri des newGuids consécutifs pour être aléatoire de haute qualité? Certaines versions de quid / uuid ont des horodatages et d'autres parties non aléatoires.
Johan Lundberg

8
Cette réponse a déjà été donnée, et pire encore, elle est conçue pour être unique et non aléatoire.
Alex Angas

-7

Une approche très simple de ce type de problème consiste à utiliser un certain nombre de permutation d'éléments aléatoires dans la liste.

En pseudo-code, cela ressemblerait à ceci:

do 
    r1 = randomPositionInList()
    r2 = randomPositionInList()
    swap elements at index r1 and index r2 
for a certain number of times

1
Un problème avec cette approche est de savoir quand s'arrêter. Il a également tendance à exagérer les biais dans le générateur de nombres pseudo-aléatoires.
Mark Bessey

3
Oui. Très inefficace. Il n'y a aucune raison d'utiliser une approche comme celle-ci lorsqu'il existe des approches meilleures et plus rapides qui sont tout aussi simples.
PeterAllenWebb

1
pas très efficace ou efficace ... Le faire tourner N fois laisserait probablement de nombreux éléments dans leur position d'origine.
NSjonas
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.