Случайные числа без восходящего и нисходящего ордеров [дубликат]

Рекомендуется в качестве ответа:

Вот решение, использующее генераторы es2015 :

function* subsetSum(numbers, target, partial = [], partialSum = 0) {

  if(partialSum === target) yield partial

  if(partialSum >= target) return

  for(let i = 0; i < numbers.length; i++){
    const remaining = numbers.slice(i + 1)
        , n = numbers[i]

    yield* subsetSum(remaining, target, [...partial, n], partialSum + n)
  }

}

Использование генераторов действительно может быть очень полезным, поскольку оно позволяет вам чтобы приостановить выполнение скрипта сразу после нахождения действительного подмножества. Это контрастирует с решениями без генераторов (т. Е. С отсутствием состояния), которым приходится проходить через каждое подмножество numbers

654
задан Uwe Keim 3 May 2016 в 13:01
поделиться

18 ответов

Перемешать любой (I)List с использованием метода расширения, основанного на Shisherle 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;  
    }  
}

Использование:

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

код выше использует сильно критикуемый метод System.Random для выбора кандидатов подкачки. Это быстро, но не так произвольно, как должно быть. Если вам нужно лучшее качество случайности в ваших тасованиях, используйте генератор случайных чисел в System.Security.Cryptography следующим образом:

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;
    }
}

В этом блоге доступно простое сравнение (WayBack Machine).

Редактирование: поскольку я пишу этот ответ пару лет назад, многие люди комментировали или писали мне, чтобы указать на большой глупый недостаток в моем сравнении. Они, конечно, правы. Нет ничего плохого в System.Random, если он используется так, как он был предназначен. В моем первом примере выше я создаю экземпляр rng-переменной внутри метода Shuffle, который запрашивает проблемы, если метод будет вызываться повторно. Ниже приведен фиксированный полный пример, основанный на действительно полезном комментарии, полученном сегодня от @weston здесь, на 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;
      }
    }
  }
}
925
ответ дан Uwe Keim 18 August 2018 в 17:29
поделиться
  • 1
    Что делать, если list.Count является & gt; Byte.MaxValue? Если n = 1000, то 255/1000 = 0, поэтому цикл do будет бесконечным циклом, поскольку поле [0] & lt; 0 всегда false. – AndrewS 7 June 2011 в 11:47
  • 2
    Я хотел бы отметить, что сравнение ошибочно. Использование & lt; code & gt; new Random () & lt; / code & gt; в цикле - проблема, а не случайность & lt; code & gt; Random & lt; / code & gt; Описание – Sven 29 September 2011 в 14:43
  • 3
    Это хорошая идея передать экземпляр Random в Shuffle, а не создавать его внутри, как если бы вы вызывали Shuffle много раз подряд (например, перетасовывая множество коротких списков), списки все будут перетасованы в том же (например, первый элемент всегда перемещается в позицию 3). – Mark Heath 8 February 2012 в 00:43
  • 4
    Только что Random rng = new Random(); a static решит проблему в столбце сравнения. Поскольку каждый последующий вызов будет следовать из предыдущих вызовов, последний случайный результат. – weston 28 November 2012 в 15:58
  • 5
    # 2, неясно, работает ли версия с генератором Crypto, так как максимальный диапазон байта равен 255, поэтому любой список, который больше, не будет правильно перемещаться. – Mark Sowul 8 May 2013 в 15:37
    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;
    }
10
ответ дан Adam Tegen 18 August 2018 в 17:29
поделиться
  • 1
  • 2
    Разве вы не должны делать что-то вроде var listCopy = list.ToList(), чтобы не удалять все элементы из входящего списка? Я действительно не понимаю, почему вы хотите, чтобы эти списки были пустыми. – Chris Marisic 17 September 2014 в 18:38

Я обычно использую:

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);
}
4
ответ дан albertein 18 August 2018 в 17:29
поделиться
  • 1
    list.RemoveAt - это операция O (n), которая делает эту реализацию чрезмерно медленной. – George Polevoy 14 May 2017 в 21:09

Очень простой подход к этой проблеме состоит в том, чтобы использовать ряд случайных свопов в списке.

В псевдокоде это будет выглядеть так:

do 
    r1 = randomPositionInList()
    r2 = randomPositionInList()
    swap elements at index r1 and index r2 
for a certain number of times
-5
ответ дан Aleris 18 August 2018 в 17:29
поделиться
  • 1
    Одна из проблем с этим подходом - знать, когда остановиться. Он также имеет тенденцию преувеличивать любые предубеждения в генераторе псевдослучайных чисел. – Mark Bessey 7 November 2008 в 20:58
  • 2
    Да. Очень неэффективен. Нет оснований использовать такой подход, когда существуют более быстрые и быстрые подходы, которые так же просты. – PeterAllenWebb 7 November 2008 в 22:25
  • 3
    не очень эффективный или эффективный ... Выполнение этого N раз, вероятно, оставит много элементов в их исходном положении. – NSjonas 7 December 2012 в 23:46

Идея получает анонимный объект с элементом и случайным порядком, а затем переупорядочивает элементы этим порядком и возвращает значение:

var result = items.Select(x => new { value = x, order = rnd.Next() })
            .OrderBy(x => x.order).Select(x => x.value).ToList()
0
ответ дан Andrey Kucher 18 August 2018 в 17:29
поделиться

Вот эффективный Shuffler, который возвращает массив байтов перетасованных значений. Он никогда не перемешивает больше, чем нужно. Он может быть перезапущен с того места, где он ранее был остановлен. Моя фактическая реализация (не показана) является компонентом MEF, который позволяет пользователю задавать замененный shuffler.

    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;
    }

`

1
ответ дан BSalita 18 August 2018 в 17:29
поделиться

Это безопасный для потоков способ:

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
ответ дан Christopher Stevenson 18 August 2018 в 17:29
поделиться

EDIT RemoveAt - слабость в моей предыдущей версии. Это решение преодолевает это.

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];
    }
}

Обратите внимание на необязательный параметр Random generator, если реализация базовой фреймворка Random не является потокобезопасной или криптографически достаточно сильной для ваших нужд, вы можете ввести свою реализацию в операция.

В этом ответе можно найти подходящую реализацию для криптографически сильной реализации Random с потоковой безопасностью.


Вот идея, расширить IList в (надеюсь) эффективный путь.

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]];
}

8
ответ дан Community 18 August 2018 в 17:29
поделиться

Если у вас есть фиксированное число (75), вы можете создать массив из 75 элементов, а затем перечислить список, перемещая элементы в рандомизированные позиции в массиве. Вы можете сгенерировать сопоставление номера списка с индексом массива с помощью Fisher-Yates shuffle .

3
ответ дан dmo 18 August 2018 в 17:29
поделиться

Простая модификация принятого ответа , который возвращает новый список вместо работы на месте и принимает более общий IEnumerable<T>, как это делают многие другие методы 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;
}
0
ответ дан Extragorey 18 August 2018 в 17:29
поделиться

Это мой предпочтительный метод перетасовки, когда желательно не изменять оригинал. Это вариант алгоритма Fisher-Yates «наизнанку» , который работает над любой перечислимой последовательностью (длина source не обязательно должна быть известна с начала).

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;
}

Этот алгоритм также может быть реализован путем выделения диапазона от 0 до length - 1 и случайного исчерпания индексов путем замены случайно выбранного индекса последним индексом до тех пор, пока все индексы не будут выбраны ровно один раз. Этот выше код выполняет то же самое, но без дополнительного распределения. Это довольно опрятно.

Что касается класса Random, это генератор чисел общего назначения (и если бы я запускал лотерею, я бы подумал об использовании чего-то другого). По умолчанию он также полагается на начальное значение по времени. Небольшое облегчение проблемы состоит в том, чтобы засеять класс Random с помощью RNGCryptoServiceProvider, или вы можете использовать RNGCryptoServiceProvider в методе, подобном этому (см. Ниже), чтобы генерировать равномерно выбранные случайные значения двойной с плавающей запятой, но запускать лотерею в значительной степени требует понимания случайности и характера источника случайности.

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

Точка генерации случайного двойника (между 0 и 1 исключительно) заключается в использовании масштабирования для целочисленного решения. Если вам нужно выбрать что-то из списка на основе случайного двойного x, который всегда будет 0 <= x && x < 1, это прямо.

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

Наслаждайтесь!

3
ответ дан John Leidegren 18 August 2018 в 17:29
поделиться

Если вы не возражаете использовать два Lists, то это, вероятно, самый простой способ сделать это, но, вероятно, не самый эффективный или непредсказуемый:

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);
2
ответ дан Lemonseed 18 August 2018 в 17:29
поделиться

Если нам нужно только перетасовать элементы в полностью случайном порядке (просто для смешивания элементов в списке), я предпочитаю этот простой, но эффективный код, который заказывает элементы по guid ...

var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
250
ответ дан Matthew Lock 18 August 2018 в 17:29
поделиться
  • 1
    GUID должны быть уникальными, а не случайными. Часть из них является машинной, а другая часть основана на времени, и только небольшая часть является случайной. blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx – Despertar 5 May 2013 в 08:00
  • 2
    Это приятное элегантное решение. Если вы хотите что-то другое, кроме руководства, чтобы генерировать случайность, просто закажите что-нибудь еще. Например: var shuffledcards = cards.OrderBy(a => rng.Next()); compilr.com/grenade/sandbox/Program.cs – grenade 27 May 2013 в 11:54
  • 3
    Пожалуйста, нет. Это не верно. "упорядочение случайным" абсолютно НЕ тасование: вы вводите предвзятость и, что еще хуже, вы рискуете идти в бесконечные петли – Vito De Tullio 16 August 2013 в 11:07
  • 4
    @VitoDeTullio: Ты забываешь. Вы рискуете бесконечными циклами, когда вы предоставляете случайную функцию сравнения ; для выполнения согласованного полного порядка требуется функция сравнения. Случайный ключ в порядке. Это предложение неверно, потому что не гарантируется случайным образом , а не потому, что метод сортировки случайным ключом неверен. – Eric Lippert 13 September 2013 в 22:30
  • 5
    @Doug: NewGuid гарантирует, что он дает уникальный идентификатор GUID. Он не дает никаких гарантий относительно случайности. Если вы используете GUID для целей, отличных от создания значения unique , вы делаете это неправильно. – Eric Lippert 13 September 2013 в 22:31

Метод расширения для IEnumerable:

public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
    Random rnd = new Random();
    return source.OrderBy<T, int>((item) => rnd.Next());
}
64
ответ дан rbm 18 August 2018 в 17:29
поделиться
  • 1
    Обратите внимание, что это не является потокобезопасным, даже если оно используется в потокобезопасном списке – BlueRaja - Danny Pflughoeft 25 September 2012 в 04:05
  • 2
    как нам указать список & lt; string & gt; к этой функции? – MonsterMMORPG 7 March 2013 в 14:27
  • 3
    Есть две существенные проблемы с этим алгоритмом: - OrderBy использует вариант QuickSort для сортировки элементов по их (якобы случайным) ключам. Производительность QuickSort - O (N log N) ; напротив, перетасовка Фишера-Йейта O (N) . Для коллекции из 75 элементов это не может быть большой проблемой, но разница станет заметной для больших коллекций. – John Beyer 26 June 2013 в 17:47
  • 4
    ... - Random.Next() может привести к обоснованному псевдослучайному распределению значений, но not гарантирует, что значения будут уникальными. Вероятность дублирования ключей растет (нелинейно) с N до тех пор, пока не достигнет определенности, когда N достигнет 2 ^ 32 + 1. OrderBy QuickSort является стабильной сортировкой; таким образом, если нескольким элементам присваивается одно и то же псевдослучайное значение индекса, то их порядок в выходной последовательности будет такой же , что и во входной последовательности; таким образом, смещение вводится в «перетасовку». – John Beyer 26 June 2013 в 18:06
  • 5
    @JohnBeyer: Есть гораздо большие проблемы, чем этот источник предвзятости. Есть только четыре миллиарда возможных семян для Random, что намного меньше, чем количество возможных перетасовки умеренного размера. Можно создать лишь небольшую часть возможных тасований. Это смещение затмевает смещение из-за случайных столкновений. – Eric Lippert 13 September 2013 в 22:33

Старый пост наверняка, но я просто использую GUID.

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

GUID всегда уникален, и поскольку он регенерируется каждый раз, когда результат изменяется каждый раз.

-1
ответ дан S.L. Barth 18 August 2018 в 17:29
поделиться
  • 1
    Компактный, но есть ли у вас ссылка на сортировку последовательных newGuids, чтобы быть качественным случайным? Некоторые версии quid / uuid имеют отметки времени и другие неслучайные части. – Johan Lundberg 10 December 2015 в 15:47
  • 2
    Этот ответ уже дан, и хуже он предназначен для уникальности, а не для случайности. – Alex Angas 4 January 2016 в 23:21

Вы можете достичь этого, используя этот простой метод расширения

public static class IEnumerableExtensions
{

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

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

, и вы можете использовать его, выполнив следующие

// 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);
}
6
ответ дан Shehab Fawzy 18 August 2018 в 17:29
поделиться
  • 1
    Я бы сохранил экземпляр класса Random вне функции в качестве переменной static. В противном случае вы можете получить такое же рандомизированное семя из таймера, если оно вызвано быстро. – Lemonseed 2 June 2016 в 16:11
  • 2
    Интересное замечание: если вы быстро создаете экземпляр класса Random в пределах цикла, скажем, между 0 мс и 200 мс друг от друга, у вас есть очень высокая вероятность получить одно и то же рандомизированное семя, которое затем приводит к повторению результатов. Однако вы можете обойти это, используя Random rand = new Random (Guid.NewGuid (). GetHashCode ()); Это эффективно заставляет рандомизацию быть выведенной из Guid.NewGuid () – Baaleos 16 February 2018 в 17:28

Я немного удивлен всеми неуклюжими версиями этого простого алгоритма. Fisher-Yates (или Knuth shuffle) немного сложный, но очень компактный. Если вы перейдете в Википедию, вы увидите версию этого алгоритма, которая имеет обратную связь, и многие люди действительно не понимают, почему это происходит наоборот. Основная причина заключается в том, что в этой версии алгоритма предполагается, что генератор случайных чисел Random(n) в вашем распоряжении имеет следующие два свойства:

  1. Он принимает n как один входной параметр.
  2. Он возвращает число от 0 до n включительно .

Однако генератор случайных чисел .Net не удовлетворяет свойству # 2. Random.Next(n) вместо этого возвращает число от 0 до n-1 включительно. Если вы попытаетесь использовать for-loop в обратном порядке, вам нужно будет вызвать Random.Next(n+1), который добавит еще одну операцию.

Однако генератор случайных чисел .Net имеет другую приятную функцию Random.Next(a,b), которая возвращает a в b-1 включительно. Это действительно прекрасно сочетается с реализацией этого алгоритма, который имеет нормальный цикл for. Итак, без дальнейших церемоний, вот правильная, эффективная и компактная реализация:

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

public static void Swap<T>(this IList<T> list, int i, int j)
{
    var temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}
85
ответ дан ShitalShah 18 August 2018 в 17:29
поделиться
  • 1
    Не было бы лучше изменить rnd (i, list.Count) на rnd (0, list.Count), чтобы можно было поменять любую карту? – Donuts 7 July 2014 в 07:04
  • 2
    @Donuts - нет. Если вы это сделаете, вы добавите смещение в случайном порядке. – ShitalShah 19 July 2014 в 08:16
  • 3
    Разделив Swap & lt; T & gt; к отдельному методу, похоже, вы вызываете много ненужных T-ассигнований для temp. – Clay 3 December 2015 в 15:52
  • 4
    Я бы сказал, что LINQ может потенциально замедлить производительность перетасовки, и это было бы причиной не использовать его, особенно учитывая относительную простоту кода. – winglerw28 12 February 2016 в 02:26
  • 5
    Когда i = list.Count - 1, т. Е. Последняя итерация, rnd.Next(i, list.Count) вернет вас. Поэтому вам необходимо i < list.Count -1 в качестве условия цикла. Ну, вам это не нужно, но это экономит 1 итерацию;) – Pod 28 May 2016 в 20:14
 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);

}
1
ответ дан sumit laddha 18 August 2018 в 17:29
поделиться
  • 1
    Добро пожаловать в переполнение стека! Пожалуйста, подумайте над тем, чтобы добавить какое-то объяснение к вашему ответу, а не просто огромный блок кода. Наша цель здесь - просвещать людей, чтобы они поняли ответ и могли применять его в других ситуациях. Если вы прокомментируете свой код и добавите объяснение, вы сделаете свой ответ более полезным не только для человека, который задал вопрос на этот раз, а для любого в будущем, у кого может быть такая же проблема. – starsplusplus 16 June 2014 в 20:14
  • 2
    Большая часть этого кода совершенно не имеет отношения к вопросу, и единственная полезная часть в основном повторяет ответ Адама Тегена почти 6 лет назад. – T.C. 16 June 2014 в 20:16
Другие вопросы по тегам:

Похожие вопросы: