JavaRush /Курсы /Kotlin SELF /Random — генерация случайных Int и Double

Random — генерация случайных Int и Double

Kotlin SELF
6 уровень , 4 лекция
Открыта

1. Случайные числа в программах

Случайные числа — это отличный способ сделать программу менее “деревянной”. Когда всё заранее предсказуемо, вы быстро упираетесь в потолок: игра всегда одинаковая, тесты скучные, симуляция не похожа на жизнь. Random позволяет добавлять вариативность: генерировать бросок кубика, случайную скидку, вероятность события, лутбоксы, “рандомный” сценарий. И да, это тот самый момент, когда компьютер внезапно делает вид, что он не детерминированная машина, а чуть-чуть волшебник.

Важно сразу договориться о двух вещах. Во‑первых, в программировании “случайность” обычно означает псевдослучайность: числа выглядят случайными, но внутри это хитрый алгоритм. Во‑вторых, вам нужны правила границ, иначе вы постоянно будете ловить ошибку “на 1” (самая обидная ошибка, потому что мозг уверяет: “ну все же правильно!”).

kotlin.random.Random: источник “случайности”

В Kotlin стандартный генератор псевдослучайных чисел находится в пакете kotlin.random. Обычно вы используете объект Random и вызываете у него методы вроде nextInt() или nextDouble().

Начнём самого простого: Random — это как “коробка с шариками”. Вы просите: “дай мне шарик с числом”, и он выдаёт очередной. Но чтобы вам было легче, у методов есть контракты: какие числа могут выпасть и какие не могут.

Мини‑пример: просто получить “какое-то” целое число.

import kotlin.random.Random

fun main() {
    val x = Random.nextInt()
    println("x = $x") // x = (какое-то целое, каждый запуск разное)
}

Здесь важно не ожидать “красивых” значений. Может быть и отрицательное, и большое, и маленькое. Если вам нужен определенный диапазон (а он вам почти всегда нужен), вы задаёте границы.

2. Random.nextInt(from, until): главное правило про границы

Когда вы хотите число “в пределах от А до Б”, обычно вы используете: Random.nextInt(from, until)

И вот тут спрятано ключевое правило: from включается, а until не включается.

То есть результат лежит в диапазоне: from .. (until - 1)

Это правило не “прихоть разработчиков Kotlin”, а очень практичная договорённость: так удобно работать с диапазонами, размерами и индексами (позже это будет особенно приятно). В документационных примерах Kotlin это видно на классическом “бросаем кубик, пока не выпадет 6”: там используется Random.nextInt(1, 7), и семёрка как раз выбрана, чтобы 6 была достижима.

Давайте зафиксируем это небольшой таблицей (чтобы было легче запомнить):

Вызов Возможные результаты
Random.nextInt(0, 10)
0..9
Random.nextInt(1, 7)
1..6
Random.nextInt(-3, 3)
-3..2

Заметьте, что последняя строка особенно “коварная”: если вы ожидали -3..3, то вы уже попали в классическую ловушку.

3. Ошибка “на +1”: почему кубик — лучший учитель

У каждой темы в программировании есть свой подводный камень. У циклов — бесконечный while, у строк — забытый пробел в конце, у null — печаль и философия. У Random символ — это кубик d6 (1..6). Он идеально показывает проблему границ.

Сейчас мы сделаем мини-кусочек консольного приложения (пусть это будет тренировочная программа “LuckyCafe”: она будет выдавать клиенту случайную скидку/бонус, просто чтобы нам было что развивать).

Сначала реализуем “бросок кубика” правильно:

import kotlin.random.Random

fun main() {
    val dice = Random.nextInt(from = 1, until = 7)
    println("Выпало: $dice") // Выпало: (1..6)
}

Почему until = 7? Потому что верхняя граница не включается, и так мы получаем 1..6.

А теперь неправильный вариант, который очень часто пишут новички (и иногда не только новички, но они потом делают вид, что “так и задумано”):

import kotlin.random.Random

fun main() {
    val dice = Random.nextInt(from = 1, until = 6)
    println("Выпало: $dice") // Выпало: (1..5) — шестерка НИКОГДА не выпадет
}

Это не “редко выпадает 6”. Это “никогда”. Такая ошибка может жить в проекте неделями, потому что кажется правдоподобной: “ну да, 6 просто пока не выпадала”. Поэтому полезная привычка: если вы делаете диапазон “включительно”, мысленно проговаривайте формулу until = max + 1.

4. Double-случайность

Random.nextDouble(): случайное число от 0 до 1

Если nextInt() — это “случайное целое”, то nextDouble() чаще всего воспринимают как “случайная доля”. По умолчанию:

Random.nextDouble() выдает вещественное число в диапазоне от 0.0 до 1.0 (единица при этом, как правило, не считается гарантированно достижимой — но на бытовом уровне думайте “почти до 1.0”).

Практически это удобно как “вероятность” или “коэффициент”. Например: “с вероятностью 20% выдаём бонус” или “случайно чуть-чуть меняем цену/вес”.

Пример: сгенерируем "коэффициент удачи" как число от 0.0 до 1.0:

import kotlin.random.Random

fun main() {
    val luck = Random.nextDouble()
    println("Удача = $luck") // 	Удача = 0.483920...
}

Почти всегда это будет длинная дробь. Это нормально: компьютер не обязан заботиться о красоте вывода, это наша работа.

Масштабирование случайной дроби: делаем “0..1” полезным

Сама по себе дробь 0..1 не всегда удобна. Чаще хочется “случайное число в диапазоне”, например скидку 5%..25% или случайную цену 100.0..150.0.

Есть два базовых пути, и оба полезны. Мы начнём с самого понятного “математического” способа: берём случайное число и растягиваем его на нужный диапазон.

Формула такая: value = min + Random.nextDouble() * (max - min)

Пример: случайная скидка от 5% до 25% (то есть 0.05..0.25 как диапазон):

import kotlin.random.Random

fun main() {
    val min = 0.05
    val max = 0.25
    val discount = min + Random.nextDouble() * (max - min)

    println("Скидка = $discount") 	// Скидка = 0.1372...
}

Да, это снова длинная дробь. Человеку приятнее проценты, поэтому добавим вывод в процентах.

Random + округление для вывода: делаем результат человеческим

Случайные значения почти всегда получаются “некрасивыми”: 0.1372049182. Это отличное число для математика и ужасное для пользователя. Поэтому нормальный паттерн такой: сгенерировали → преобразовали масштаб → округлили для вывода.

Мы не будем углубляться в новые правила округления, но покажем типичный приём: округление до 1 знака после запятой через round.

import kotlin.math.round
import kotlin.random.Random

fun main() {
    val discount = 0.05 + Random.nextDouble() * 0.20 // 0.05..0.25
    val percent = discount * 100.0

    val prettyPercent = round(percent * 10.0) / 10.0
    println("Скидка = $prettyPercent%") // Скидка = 13.7%
}

И вот теперь наш “LuckyCafe” начинает выглядеть как программа, а не как протокол измерений с космического корабля.

5. Мини‑пример: “LuckyCafe” — бонус к заказу

Сейчас мы соберём небольшой проект, который использует и nextInt(...), и nextDouble(), и главное правило границ. Сценарий такой: мы печатаем чек “кофейни”, а затем случайно определяем бонус дня: либо скидка, либо бесплатный сироп, либо “увы, сегодня без бонусов” (кофейня тоже должна уметь говорить “нет”, иначе разорится).

Случайный выбор варианта через nextInt(0, 3)

Когда нужно выбрать один из нескольких вариантов, удобно генерировать число-индекс: 0, 1, 2. И вот тут правило until снова играет роль: если вариантов 3, то until должен быть 3, потому что результат будет 0..2.

import kotlin.random.Random

fun main() {
    val bonusCase = Random.nextInt(from = 0, until = 3)

    println("bonusCase = $bonusCase") // bonusCase = 0/1/2
}

Здесь уже можно вешать логику на if/else (мы это умеем). Да, позже будет when, но пока мы играем с теми кубиками, которые уже выдали на предыдущих уроках.

Превращаем номер варианта в текст через if/else

Теперь сделаем наш первый вывод:

import kotlin.random.Random

fun main() {
    val bonusCase = Random.nextInt(from = 0, until = 3)

    val message = if (bonusCase == 0) {
        "Бонус: скидка на заказ"
    } else if (bonusCase == 1) {
        "Бонус: бесплатный сироп"
    } else {
        "Бонус: сегодня без бонусов (но вы всё равно молодец)"
    }

    println(message)
}

Обратите внимание: мы специально используем 0..2, потому что так удобнее, чем 1..3. Индексы и “счёт с нуля” сначала раздражают, потом становятся как родные.

Если выпала скидка — генерируем её через nextDouble()

Теперь сделаем скидку не просто текстом, а числом. Допустим, скидка от 5% до 20% (это диапазон 0.05..0.20).

import kotlin.math.round
import kotlin.random.Random

fun main() {
    val bonusCase = Random.nextInt(from = 0, until = 3)

    if (bonusCase == 0) {
        val discount = 0.05 + Random.nextDouble() * 0.15
        val percent = round(discount * 1000.0) / 10.0
        println("Бонус: скидка $percent%") // Бонус: скидка 12.3%
    } else if (bonusCase == 1) {
        println("Бонус: бесплатный сироп")
    } else {
        println("Бонус: сегодня без бонусов")
    }
}

Мы округлили процент до 1 знака после запятой. Можно и до целого — зависит от задачи. Здесь важно увидеть связку: nextDouble() → масштабирование → округление.

Мини-игра внутри: “бросаем кубик, пока не выпадет 6”

Иногда случайность используют не для бонусов, а для повторяющихся попыток, пока не случится событие. В официальных примерах Kotlin часто показывают идею “бросаем кубик до шестерки” через do-while и Random.nextInt(1, 7).

Мы добавим это как “рекламную акцию”: бариста бросает кубик, и если выпала 6 — выдаём дополнительный бонус.

import kotlin.random.Random

fun main() {
    var roll: Int

    do {
        roll = Random.nextInt(1, 7)
        println("Бариста бросил кубик: $roll") // Бариста бросил кубик: 2
    } while (roll != 6)

    println("Выпала 6! Добавляем печеньку к заказу!") // Выпала 6! ...
}

6. Шпаргалка по until

С Random.nextInt(from, until) вы почти всегда задумывайтесь в одном и том же месте: “а включается ли верхняя граница?”. Поэтому полезно держать в голове “шпаргалку-формулу” и сверять с ней любую генерацию.

Если вам нужно:

  • от a до b включительно, вы пишете nextInt(a, b + 1).
  • от 0 до (n - 1) (например индекс), вы пишете nextInt(0, n).

Именно поэтому часто говорят: “until — это как size”. Если у вас 10 вариантов, until = 10, и вы получите 0..9.

Кстати, это же мышление видно и в других частях стандартной библиотеки: многие операции опираются на тот же принцип “верхняя граница не включается”, потому что так проще не выходить за пределы.

7. Типичные ошибки при работе с Random

Ошибка №1: перепутать границы и получить “никогда не выпадает”.
Самая частая ловушка — написать Random.nextInt(1, 6) и ждать 1..6. Но по контракту верхняя граница не включается, поэтому получится 1..5. Это ломает статистику, игровую механику и веру в справедливость мира. Лечится одной привычкой: если вам нужно “до b включительно”, используйте until = b + 1. В примере с кубиком это хорошо видно: Random.nextInt(1, 7) даёт 1..6.

Ошибка №2: ожидать “красивых” чисел от nextDouble().
Random.nextDouble() почти всегда даёт длинную дробь, и это нормально. Ошибка — печатать её пользователю “как есть” и удивляться, что программа выглядит странно. Правильный стиль: либо переводить в проценты, либо округлять до нужного количества знаков (как мы сделали через round).

Ошибка №3: смешивать “вероятность” и “проценты” без преобразования.
Очень частая путаница: вы получили p = Random.nextDouble(), решили, что это “проценты”, и печатаете “скидка 0.42%”. На самом деле это 42% как доля (0.42), и для процентов нужно умножать на 100. И наоборот: если вы храните проценты как 42, а потом используете как вероятность, логика разваливается. Договоритесь с собой: либо вы храните долю (0.0..1.0), либо проценты (0..100), и каждый раз явно переводите.

Ошибка №4: думать, что Random — “волшебная настоящая случайность”.
В учебных задачах это почти никогда не важно, но полезно помнить: Random выдаёт псевдослучайные числа. Это значит, что для игр, симуляций и учебных примеров он идеален, а для криптографии и безопасности — нужны другие подходы (но это вообще не тема этого дня, просто держим в голове, чтобы не строить “генератор паролей банка” на nextInt()).

Ошибка №5: делать диапазон через “сложную формулу”, хотя можно проще.
Новички иногда пишут что-то вроде a + Random.nextInt() % (b - a) и получают отрицательные числа, странное распределение и много боли. Если вам нужен диапазон — используйте nextInt(from, until) и живите спокойно: он как раз создан, чтобы не изобретать велосипед (а потом чинить велосипед, потому что у него квадратные колёса).

Ошибка №6: забыть, что until — это “как размер”, и сломать выбор из вариантов.
Когда вы выбираете вариант из трёх (0, 1, 2), нужно nextInt(0, 3). Если написать nextInt(0, 2), третий вариант никогда не выпадет. В маленьком примере это заметно сразу, а в большом приложении может проявиться как “почему пользователи никогда не получают бонус №3”, и вы потратите полдня, чтобы понять, что вы просто отрезали ветку случайности.

1
Задача
Kotlin SELF, 6 уровень, 4 лекция
Недоступна
Бросок кубика
Бросок кубика
1
Задача
Kotlin SELF, 6 уровень, 4 лекция
Недоступна
Бонус дня
Бонус дня
1
Задача
Kotlin SELF, 6 уровень, 4 лекция
Недоступна
Скидка витрины
Скидка витрины
1
Задача
Kotlin SELF, 6 уровень, 4 лекция
Недоступна
Угадай число
Угадай число
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ