$ sudo teach IT
Модуль 10 · Полезные модули

Урок 10.1 — Класс Random

Случайные числа в C#: Random, Next, NextDouble, Shuffle, seed для воспроизводимости.

Компьютеры — детерминированные машины. Они не умеют быть «случайными» по-настоящему. Вместо этого они используют генераторы псевдослучайных чисел (PRNG) — сложные математические формулы, которые выдают последовательность чисел, выглядящую как случайная. В C# за это отвечает класс Random из пространства имён System.

🎲

Random.Next() — случайные целые числа

У класса Random есть три версии метода Next():

C# · Random.Next()
using System;

Random rand = new Random();

// 1. Next() — случайное целое от 0 до int.MaxValue - 1
int a = rand.Next();
Console.WriteLine(a);  // например: 14329142

// 2. Next(int maxValue) — от 0 (включительно) до maxValue (исключительно)
int b = rand.Next(100);
Console.WriteLine(b);  // от 0 до 99

// 3. Next(int minValue, int maxValue) — в заданном диапазоне
int c = rand.Next(1, 7);   // как бросок кубика: 1,2,3,4,5,6
Console.WriteLine(c);

// Бросок двух кубиков
int dice1 = rand.Next(1, 7);
int dice2 = rand.Next(1, 7);
Console.WriteLine($"Выпало: {dice1} + {dice2} = {dice1 + dice2}");

Важно помнить: верхняя граница не включается. Next(1, 7) вернёт числа от 1 до 6, но не 7. Это соглашение действует во многих языках программирования (C#, Java, Python) — диапазон [min, max), то есть min включается, max исключается.

🔢

Random.NextDouble() — случайные дробные числа

NextDouble() возвращает случайное число типа double в диапазоне [0.0, 1.0) — то есть от 0.0 включительно до 1.0 исключительно.

C# · Random.NextDouble()
Random rand = new Random();

double d = rand.NextDouble();
Console.WriteLine(d);  // например: 0.347829341...

// Преобразование в нужный диапазон: [0, 1) → [0, 100)
double percentage = rand.NextDouble() * 100;
Console.WriteLine($"{percentage:F2}%");  // например: 73.52%

// Температура от -10 до +40
double temperature = rand.NextDouble() * 50 - 10;
Console.WriteLine($"{temperature:F1}°C");

// Симуляция: событие с вероятностью 30%
if (rand.NextDouble() < 0.3)
    Console.WriteLine("Событие произошло!");
else
    Console.WriteLine("Событие не произошло");
💡 Для случайного double в произвольном диапазоне [min, max) используй формулу: rand.NextDouble() * (max - min) + min.
🔀

Shuffle() и GetItems() — фишки C# 9+

Начиная с .NET 5 (C# 9), у Random появились два полезных метода: Shuffle() — перемешивание коллекции, и GetItems() — выбор нескольких случайных элементов.

C# · Shuffle и GetItems
Random rand = new Random();

// Shuffle() — перемешивает массив/список на месте
string[] cards = { "2♠", "3♠", "4♠", "5♠", "6♠", "7♠" };
rand.Shuffle(cards);
Console.WriteLine(string.Join(", ", cards));
// Например: 5♠, 2♠, 7♠, 3♠, 6♠, 4♠

// Shuffle<T> работает с любым типом
int[] numbers = { 1, 2, 3, 4, 5 };
rand.Shuffle(numbers);
Console.WriteLine(string.Join(", ", numbers));

// GetItems() — выбирает k случайных элементов из коллекции (C# 9+)
char[] letters = { 'A', 'B', 'C', 'D', 'E' };
char[] sample = rand.GetItems(letters, 3);
Console.WriteLine($"Случайные буквы: {string.Join(", ", sample)}");
// Например: C, A, E

// GetItems() — полезен для выбора случайных подарков, вопросов и т.д.
string[] gifts = { "Книга", "Флешка", "Наушники", "Сертификат" };
string[] prizes = rand.GetItems(gifts, 2);
Console.WriteLine($"Призы: {string.Join(" и ", prizes)}");

Обрати внимание: GetItems() может вернуть один и тот же элемент несколько раз (выбор с возвращением). Зато Shuffle() — перемешивает без дубликатов.

🔐

Безопасные случайные числа: RandomNumberGenerator

System.Random — это псевдослучайный генератор. Он не подходит для криптографических целей (пароли, ключи шифрования, токены). Для безопасности используй System.Security.Cryptography.RandomNumberGenerator:

C# · Безопасные случайные числа
using System.Security.Cryptography;

// Создание криптостойкого генератора
using var rng = RandomNumberGenerator.Create();

// Генерация случайных байтов
byte[] randomBytes = new byte[32];  // 32 байта = 256 бит
rng.GetBytes(randomBytes);
Console.WriteLine($"Случайные байты: {BitConverter.ToString(randomBytes)}");

// Генерация случайного целого в диапазоне [min, max)
int GetSecureRandom(int min, int max)
{
    uint range = (uint)(max - min);
    uint result;
    byte[] buffer = new byte[4];

    do
    {
        rng.GetBytes(buffer);
        result = BitConverter.ToUInt32(buffer, 0);
    } while (result > uint.MaxValue - (uint.MaxValue % range));

    return (int)(result % range) + min;
}

Console.WriteLine($"Безопасное случайное: {GetSecureRandom(1, 100)}");
💡 Для игр, тестов и симуляций используй Random. Для паролей, токенов, ключей — RandomNumberGenerator.

⚠️ Важно: Random предсказуем. Если известна seed-последовательность, можно восстановить все «случайные» числа. Поэтому Random никогда не используется в криптографии.

🎯

Фиксация Seed для воспроизводимости

Каждый экземпляр Random использует seed (зерно) — начальное число для генерации. По умолчанию seed берётся от системного таймера. Но ты можешь задать свой seed:

C# · Random с фиксированным seed
// Одинаковый seed → одинаковая последовательность чисел
Random rand1 = new Random(42);
Random rand2 = new Random(42);

Console.WriteLine(rand1.Next(1, 100));  // 67
Console.WriteLine(rand2.Next(1, 100));  // 67 (то же самое!)

Console.WriteLine(rand1.Next(1, 100));  // 30
Console.WriteLine(rand2.Next(1, 100));  // 30

// Без seed — каждый раз разные числа
Random rand3 = new Random();
Random rand4 = new Random();
Console.WriteLine(rand3.Next(1, 100));  // например: 23
Console.WriteLine(rand4.Next(1, 100));  // например: 87 (другое!)

Когда это полезно:

  • Тестирование — чтобы тесты были воспроизводимыми
  • Демо-игры — одинаковый мир для всех игроков
  • Отладка — чтобы ошибка воспроизводилась стабильно
  • Симуляции — научные расчёты с повторяемостью
🎮

Применение: игры, тесты, симуляции

Random используется повсюду. Вот несколько реальных сценариев:

🎮 Игры

  • Бросок кубика: rand.Next(1, 7)
  • Случайное выпадение предмета (loot): if (rand.NextDouble() < 0.05)
  • Генерация имени NPC: выбор из массива имён
  • Перемешивание колоды карт: rand.Shuffle(cards)
  • Случайные повреждения: damage * rand.NextDouble()

🧪 Тесты

  • Генерация тестовых данных: 1000 случайных пользователей
  • Нагрузочное тестирование: случайные запросы
  • Проверка корректности сортировки: 10 000 случайных чисел
  • Фаззинг: случайные входные данные для поиска багов

📊 Симуляции

  • Монте-Карло: оценка числа π через случайные точки
  • Моделирование очередей: случайные интервалы прибытия
  • Погодные симуляции: случайные колебания температуры
  • Финансовые модели: случайные изменения цен

Классический пример — оценка числа π методом Монте-Карло:

C# · Монте-Карло для π
using System;

class Program
{
    static void Main()
    {
        Random rand = new Random();
        int totalPoints = 10_000_000;
        int insideCircle = 0;

        for (int i = 0; i < totalPoints; i++)
        {
            double x = rand.NextDouble();
            double y = rand.NextDouble();

            if (x * x + y * y <= 1.0)
                insideCircle++;
        }

        double piEstimate = 4.0 * insideCircle / totalPoints;
        Console.WriteLine($"Оценка π: {piEstimate:F6}");
        Console.WriteLine($"Реальное π: {Math.PI:F6}");
        Console.WriteLine($"Погрешность: {Math.Abs(piEstimate - Math.PI):F6}");
    }
}

// Вывод (примерный):
// Оценка π: 3.141342
// Реальное π: 3.141593
// Погрешность: 0.000250

Чем больше точек, тем точнее оценка. Для 10 млн точек погрешность обычно < 0.001.

⚠️

Типичные ошибки с Random

Вот самые частые ошибки, которые допускают новички (и не только):

❌ Создание Random в цикле

// ПЛОХО: в цикле создаётся много Random с одинаковым seed (по таймеру)
for (int i = 0; i < 10; i++)
{
    Random r = new Random();
    Console.Write(r.Next(0, 10) + " ");  // 3 3 3 3 3 4 4 4 4 4 — повторения!
}

// ХОРОШО: один Random на весь цикл
Random rand = new Random();
for (int i = 0; i < 10; i++)
    Console.Write(rand.Next(0, 10) + " ");  // 7 2 0 4 8 9 1 5 3 6

Random по умолчанию использует Environment.TickCount как seed. Если создать несколько Random в быстрой последовательности, у них будет одинаковый seed → одинаковые числа.

❌ Непотокобезопасность Random

// ПЛОХО: Random не потокобезопасен! В многопоточном коде — баги.
Random shared = new Random();
Parallel.For(0, 100, i => {
    shared.Next();  // может упасть или дать повторяющиеся числа
});

// ХОРОШО: ThreadLocal<Random> — свой Random на каждый поток
var localRandom = new ThreadLocal<Random>(() => new Random());
Parallel.For(0, 100, i => {
    localRandom.Value.Next();
});
🧪

Мини-тест

Проверь свои знания о Random.

Вопрос 1

Какой диапазон чисел вернёт rand.Next(1, 5)?

A) 1,2,3,4,5   B) 1,2,3,4   C) 0,1,2,3,4

Вопрос 2

Что вернёт rand.NextDouble()?

A) int от 0 до 100   B) double от 0.0 до 1.0   C) double от 0.0 до 1.0 (исключая 1.0)

Вопрос 3

Как получить случайное число double от 0 до 50?

A) rand.NextDouble(50)   B) rand.Next() * 50   C) rand.NextDouble() * 50

Вопрос 4

Зачем используют фиксированный seed у Random?

A) Чтобы числа были более случайными   B) Для воспроизводимости результатов (тесты, отладка)   C) Для ускорения генерации

Вопрос 5

Какой класс использовать для генерации криптостойких случайных чисел?

A) System.Random   B) System.Security.Cryptography.RandomNumberGenerator   C) System.CryptoRandom

💡 Ответы: 1 — B, 2 — C, 3 — C, 4 — B, 5 — B
🎮

Задача

Задание: Розыгрыш призов

Напиши программу «Розыгрыш призов» со следующими функциями:

  1. Создай массив из 10 призов (строки)
  2. Создай массив из 20 участников (имена)
  3. Перемешай список участников с помощью Shuffle()
  4. Выбери случайных победителей с помощью GetItems()
  5. Каждому победителю — случайный приз
  6. Выведи результаты: «Победитель Иван получает Наушники»

Дополнительно:

  • Добавь вероятность выигрыша (например, 30% участников получат приз)
  • Сделай так, чтобы один участник не мог получить два приза
  • Используй фиксированный seed для воспроизводимости

Подсказка: используй rand.NextDouble() < 0.3 для вероятности 30%.

💡 Задача объединяет все методы Random: Next, NextDouble, Shuffle, GetItems. Для исключения дубликатов победителей можно перемешать список и взять первые K элементов.
📌

Что важно запомнить

1️⃣
Random.Next() — целые числа. Next(1, 7) — от 1 до 6 (верхняя граница исключается).
2️⃣
NextDouble() — double от 0.0 до 1.0 (1.0 не включается). Для произвольного диапазона: NextDouble() * (max - min) + min.
3️⃣
Shuffle() — перемешивает массив на месте. GetItems() — выбирает K случайных элементов (с возвращением).
4️⃣
Фиксированный seed — для воспроизводимости тестов. RandomNumberGenerator — для криптографии (пароли, токены).
5️⃣
Ошибки: не создавай Random в цикле (будет одинаковый seed), не используй один Random в многопоточном коде (не потокобезопасен).

Тест: 10.1: Класс Random

3 вопроса

Random

Premium