Принципы счёта: «и затем» перемножается, «или» складывается
Весь счёт стоит на двух законах: независимые выборы перемножаются (4 рубашки × 3 пары брюк = 12 нарядов), непересекающиеся случаи складываются (3 пасты + 4 пиццы = 7 ужинов). Факториалы, размещения, сочетания и размер CI-матрицы — эти два правила, применённые многократно.
На ревью однажды прозвучал невинный вопрос про CI-конфиг: в матрице перечислены 3 браузера, 4 операционные системы и 2 локали — сколько это джобов на каждый пуш? Половина комнаты ответила «9», сложив числа. Пайплайн запустил 24. Потом в список браузеров добавили мобильный Safari — и счётчик тихо стал 32; третья локаль подняла его до 48; ось node: [18, 20] снова удвоила всё до 96. Никто не принимал решения сжигать час компьюта на каждый коммит — матрица перемножалась, пока все думали, что просто добавляют по одной мелочи. Разрыв между угаданными 9 и реальными 24 — не причуда CI; это разрыв между двумя законами, которым подчиняется весь счёт. Умение видеть, какой закон применим — и замечать момент, когда не применим ни один, — пригодится вам на тестовых матрицах, парольных политиках, вложенных циклах и взрывах фиче-флагов до конца карьеры.
- Применять правило произведения к вложенным циклам и CI-матрицам
- Применять правило суммы к непересекающимся случаям и учитывать пересечение
- Выводить n!, числа размещений и C(n, k) из двух базовых законов
У вас 4 рубашки и 3 пары брюк. Сколько нарядов? Представьте дерево выборов: из корня растут 4 ветви — по одной на рубашку; из каждой из них растут 3 ветви — по одной на пару брюк. Посчитайте листья: 4 группы по 3, итого 12. Это правило произведения: когда задача распадается на последовательные этапы — сделай этот выбор, а затем тот — и число вариантов на каждом этапе не зависит от сделанного ранее, итог равен произведению количеств по этапам.
Когда вы видите вложенный цикл, спросите себя: каждый уровень — независимое «а затем»? Если да, вы можете перемножить количества на каждом уровне — и не запускать код. Код говорит на этом правиле свободно — каждый вложенный цикл и есть умножение:
const browsers = ["chrome", "firefox", "safari"]; // 3 варианта
const oses = ["ubuntu", "macos", "windows", "alpine"]; // 4 варианта
const locales = ["en", "ru"]; // 2 варианта
let jobs = 0;
for (const b of browsers) // выбрать браузер, А ЗАТЕМ…
for (const o of oses) // …выбрать ОС, А ЗАТЕМ…
for (const l of locales) // …выбрать локаль
jobs++;
console.log(jobs); // 3 · 4 · 2 = 24 — по джобу на каждый лист дереваПравило произведения оценивает пространство паролей. Пароль из 8 строчных букв — это 8 последовательных выборов из 26 букв: 26^8, около 2,1 × 10¹¹ строк. Расширьте алфавит до 62 символов (верхний и нижний регистр, цифры) и удлините до 12 знаков: 62^12, около 3,2 × 10²¹ — в десять миллиардов раз больше. Каждая добавленная позиция снова умножает всё пространство на размер алфавита — вот почему длина пароля бьёт хитрые замены символов: длина добавляет множители, а замены лишь слегка утолщают один из них.
Мелкий шрифт правила произведения: сами варианты могут меняться от ранних выборов — неизменным обязано быть только их число. Выбор 2 различных букв для кода: 26 вариантов на первую, затем 25 на вторую — 26 · 25 = 650 упорядоченных пар. Правило работает, потому что каждая ветвь дерева разветвляется на одинаковое число подветвей.
Ужин — одно блюдо. В меню 3 пасты и 4 пиццы. Вы не выбираете пасту, а затем пиццу — вы выбираете одно блюдо из двух непересекающихся случаев (ни одно блюдо не является пастой и пиццей одновременно), поэтому количества складываются: 3 + 4 = 7 возможных ужинов. Это правило суммы: когда исходы распадаются на случаи без общих элементов, итог равен сумме количеств по случаям.
Несущее слово — непересекающиеся. Посчитайте файлы, которые велики или недавно изменены: 80 больших плюс 50 свежих — это не 130, если 20 файлов и то и другое: их посчитали дважды. Вычтите двойной счёт один раз: 80 + 50 − 20 = 110. Правило суммы — счастливый частный случай, когда пересечение пусто.
▸Почему это работает
Реальная CI-матрица обычно несёт записи exclude: — скажем, safari на alpine исключён. Теперь этап ОС предлагает 4 варианта для двух браузеров, но лишь 3 для третьего: дерево выборов перекошено, и никакое одно произведение его не описывает. Размер считают как полное произведение минус исключённые листья: 3 · 4 · 2 − 1 · 1 · 2 = 22 джоба. Сначала перемножить, затем хирургически вычесть мёртвые ветви — так считается любая реальная матрица с исключениями.
В меню 3 пасты и 4 пиццы; комбо-обед — одно блюдо плюс один из 2 напитков. Сколько существует различных комбо?
Сколькими способами n различных предметов могут встать в ряд? Примените правило произведения n раз: n вариантов на первое место, а затем n − 1 на второе (один предмет занят), а затем n − 2, и так до 1. Произведение n · (n−1) · … · 1 записывается n! и читается «эн факториал». Пять сервисов, которые надо задеплоить по очереди: 5! = 120 возможных порядков.
Оборвите произведение раньше — получите упорядоченные выборки: пьедестал (золото, серебро, бронза) из 10 бегунов — это 10 · 9 · 8 = 720: три этапа, и стоп. k этапов = число размещений k из n, и порядок здесь действительно важен: «золото — Аня, серебро — Боря» — другой пьедестал, чем «золото — Боря, серебро — Аня».
Единственный по-настоящему новый ход. Выберем неупорядоченную команду из 3 человек из 10. Те 720 упорядоченных выборок её пересчитывают: команда из Ани, Бори и Светы появляется по разу на каждый способ упорядочить этих троих — а упорядочений 3 предметов ровно 3! = 6. Каждая команда посчитана ровно 6 раз, без исключений, поэтому команд 720 / 6 = 120.
Этот аргумент деления — сгруппировать упорядоченные выборки по тому, какое множество они образуют, заметить, что в каждой группе ровно k! членов, и поделить — вся теория сочетаний целиком. C(n, k) — не формула для зубрёжки, а «посчитай упорядоченно правилом произведения, затем подели на упорядочения, которые тебя не интересовали». Вопрос: считаются ли здесь две выборки, отличающиеся лишь порядком, одним и тем же исходом? Пьедестал: нет — оставьте произведение. Дежурная команда, рука карт: да — поделите на k!.
5 сервисов в случайном порядке деплоя = 5! = 120 вариантов.
Задача: сколько способов задеплоить пять независимых сервисов (A, B, C, D, E) в разном порядке?
Применяем правило произведения 5 раз:
- Первый деплой: 5 вариантов выбора сервиса
- Второй деплой (один занят): 4 варианта
- Третий деплой: 3 варианта
- Четвёртый деплой: 2 варианта
- Пятый деплой: 1 вариант
5 × 4 × 3 × 2 × 1 = 120 порядков деплоя.
Вот почему «просто перебрать все порядки» умирает уже около n = 10: 10! = 3 628 800.
Из 10 инженеров вы выбираете неупорядоченную тройку дежурных. Какой подсчёт верен и почему?
Счёт работает на двух законах. Правило произведения покрывает структуру «и затем»: у задачи из последовательных этапов, где число вариантов этапа не зависит от ранних выборов, исходов столько, каково произведение количеств — 4 рубашки и 3 пары брюк дают 12 нарядов, три вложенных цикла исполняются 3 · 4 · 2 раза, пароль из 8 строчных букв живёт в пространстве 26^8. Правило суммы покрывает структуру «или»: у одного выбора, разбитого на непересекающиеся случаи, исходов столько, какова сумма количеств — 3 пасты плюс 4 пиццы это 7 ужинов; а когда случаи пересекаются, пересечение посчитано дважды и вычитается один раз — это включения-исключения. Вся классика — повторение этих законов: n! упорядочивает n предметов, перемножая n сужающихся этапов; число размещений k из n обрывает произведение после k множителей (10 · 9 · 8 = 720 пьедесталов); сочетания делят его на k!, потому что неупорядоченная команда посчитана по разу на каждое упорядочение её членов — ровно k! раз, отсюда 720 / 6 = 120 команд. Спрашивайте, считаются ли здесь две выборки, отличающиеся лишь порядком, одним исходом: перемножайте, пока нет, и делите на k!, как только да.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.