К содержанию

Задание 26

Обработка данных с сортировкой

Записи и рейтинг, жадный выбор при ограниченной сумме, цепочки и пары, ряды и серии, интервалы времени и моделирование процесса по правилам.

7 подтем · 59 задач + 13 дополнительных · высокий · ≈ 35 мин

Подтема 1 из 7

Записи, сортировка и рейтинг

Что спрашивают в задании 26, как прочитать файл, хранить объект записью и сортировать по ключу; рейтинг и граница прохода.

Как устроена тема

В задании 26 дают файл с данными о сотнях объектов — файлах, товарах, заявках — и правила, по которым их выбирают или обслуживают. Вот задача, которую мы решим в этой теме:

«Входной файл 26-12.txt содержит заявки пассажиров, которые сдают багаж в камеру хранения. В заявке указаны время сдачи багажа и время освобождения ячейки (в минутах от начала суток). Ячейки пронумерованы начиная с 1. Багаж кладут в свободную ячейку с наименьшим номером; освободившуюся ячейку можно занять, начиная со следующей минуты после её освобождения. Если свободных ячеек нет, пассажир уходит. В первой строке файла — количество ячеек K, во второй — количество пассажиров N, в каждой из следующих N строк — время сдачи и время освобождения. Определите, сколько пассажиров сдадут багаж, и номер ячейки, в которую положит багаж последний из них.»

Почти всегда решение — сортировка записей и один проход по ним: либо ты сам выбираешь лучшее, либо в точности исполняешь правила условия. Разберём, что значат слова условия:

В условииЧто это значит
заявка, строка файлаодин объект — запись из нескольких чисел: (сдача, освобождение)
в минутах от начала сутоквремя — целое число от 0 до 1440, его можно сравнивать и вычитать
свободная ячейка с наименьшим номеромпросматривать ячейки по порядку номеров и брать первую свободную
можно занять со следующей минутыячейка свободна, если освободилась строго раньше прихода: free_at < start
если свободных ячеек нет, пассажир уходитзаявку пропускают — ждать нельзя
максимальное количество, наибольшийвыбираем лучший вариант — помогают сортировка и жадный выбор
если таких несколько, …второй критерий: его применяют только среди равных по первому

Начнём с файла, записей и сортировки — на них держатся все задачи темы. Тема состоит из шести подтем и дополнительного урока — у каждой своя страница:

  1. Записи, сортировка и рейтинг — ты сейчас здесь: чтение файла, ключ сортировки, рейтинг.
  2. Жадный выбор — больше всего файлов на диске, скидки.
  3. Цепочки коробок и пары — матрёшка, грузы и контейнеры, детали двух видов.
  4. Ряды, серии и группы — свободные места в рядах, линии на экране.
  5. Интервалы и события — мероприятия в зале, простои, одновременная нагрузка.
  6. Моделирование процесса — камера хранения, очереди; решим задачу темы.
  7. Пары, окно и сложные цепочки — дополнительный урок для редких задач.

Один объект

Что в задаче считается одним объектом и как хранить его в программе?

Объект — то, о чём одна строка файла: файл, товар, заявка. Его характеристики хранят вместе, одной записью, и сортируют записи целиком. В задаче темы объект — заявка пассажира: время сдачи и время освобождения.

Выбери объект и посмотри, из каких полей состоит его запись:

файл
размер
товар
цена, тип
мероприятие
время начала, время окончания
участник
идентификатор, баллы, штраф
занятое место
номер ряда, номер места

Сведения об одном объекте будем называть записью. Например, meeting = (120, 180) — одна запись о мероприятии: оно началось на 120-й минуте и закончилось на 180-й. Здесь используется : несколько значений, объединённых в одну последовательность. Отдельные элементы кортежа нельзя заменить присваиванием — для неизменяемой записи это удобно.

Почему записи сортируют целиком? Если отдельно отсортировать начала мероприятий и отдельно окончания, могут получиться пары, которых в исходных данных вообще не было:

meetings = [(10, 50), (20, 30)]starts = sorted(m[0] for m in meetings)finishes = sorted(m[1] for m in meetings)print(*zip(starts, finishes))print(*sorted(meetings))
(10, 30) (20, 50) (10, 50) (20, 30)

Мероприятий (10, 30) и (20, 50) не было: раздельная сортировка их выдумала. Исключение — условие прямо говорит о двух независимых наборах. Например, массы грузов и вместимости контейнеров могут быть двумя отдельными списками, несмотря на то что записаны в одних строках; такую задачу разберём в подтеме 3.

Ограничения и цель

Как понять, что именно нужно сделать лучшим и что важнее, если критериев несколько?

Выписать ограничения, цель и порядок критериев: сначала решает первый критерий, второй — только среди равных по первому.

Эти три вещи определяют алгоритм. Ограничение — правило, которое нельзя нарушать, например: суммарный размер файлов ≤ вместимость диска. Цель — величина, которую нужно сделать как можно больше или как можно меньше: количество сохранённых файлов → max. Вариант, удовлетворяющий ограничениям, называется допустимым. Лучший допустимый вариант по указанному критерию называется оптимальным.

Рассмотрим условие:

Сохранить максимальное количество файлов. Среди таких вариантов найти максимально возможный размер самого большого сохранённого файла.

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

ВариантКоличество файловСамый большой файл
А630
Б645
В5100

Лучший вариант — Б. Вариант В проигрывает по первому, более важному критерию, и его 100 уже ничего не решает.

Такое сравнение называют лексикографическим: сначала сравнивают первый показатель, при равенстве — второй, затем третий и так далее. Термин связан со сравнением слов в словаре: сначала первая буква, при равенстве — следующая. Python именно так, поэтому вариант можно записать парой «количество, размер»:

variants = [(6, 30), (6, 45), (5, 100)]print(max(variants))
(6, 45)

Для любой задачи полезно заранее ответить на семь вопросов:

  1. Что является объектом? Файл, товар, заявка…
  2. Какие у него характеристики? Размер, цена, время…
  3. Можно ли менять порядок — или порядок задан условием?
  4. Что нельзя нарушать? Вместимость, совместимость, сроки…
  5. Что оптимизируем первым? Количество, стоимость, длину…
  6. Что оптимизируем при равенстве? Размер, номер, остаток…
  7. Что именно выводим? Количество, значение, ID, позицию…

Порядок чисел в ответе и порядок критериев — разные вещи. В задаче могут сначала требовать максимального общего количества товаров, а в ответ просить количество товаров типа А и остаток денег.

Задачи бывают двух видов: в одних ты сам выбираешь лучший вариант, в других обязан выполнить описанную процедуру — например, принять пассажиров в порядке прихода, как в задаче темы. Первому виду посвящены подтемы 2–5, второму — подтема 6.

Чтение файла

Как прочитать файл задачи темы?

Строго по описанию формата: в 26-12.txt сначала два числа в отдельных строках — K и N, — потом N строк по два числа. Вот начало файла:

301000397 404378 408753 822…

30 ячеек, 1000 пассажиров, первый сдаёт багаж в минуту 397 и забирает в 404. Читаем так же, строку за строкой:

with open('26-12.txt') as f:    k = int(f.readline())    n = int(f.readline())    requests = []    for i in range(n):        start, finish = map(int, f.readline().split())        requests.append((start, finish)) print(k, n, requests[:3])
30 1000 [(397, 404), (378, 408), (753, 822)]

открывает файл и сам закроет его после блока. читает очередную строку, и следующее чтение продолжает со следующей. делит строку '397 404' на части, превращает их в числа, а start, finish = ... раскладывает по двум переменным. Заявка сохраняется — одной записью.

Универсального заголовка у файлов задания 26 нет: где-то в первой строке два числа, где-то они в разных строках, где-то заголовка нет вовсе. Сначала читай описание формата — выбери вариант:

вместимость и количество
S, n = map(int, f.readline().split())
количество и вместимость
n, S = map(int, f.readline().split())
два числа в разных строках
k = int(f.readline()), затем n = int(f.readline())
три числа в первой строке
n, m, k = map(int, f.readline().split())

Сортировка

Зачем сортировать записи и как задать, по какому полю?

Сортировка даёт порядок, на который опирается рассуждение: в отсортированном списке каждое следующее число не меньше предыдущего. Записи сравниваются по первому полю, при равенстве — по второму; другое поле задаёт ключ.

После список [19, 8, 22, 12, 15] становится [8, 12, 15, 19, 22]. Поэтому, например, если очередной файл уже не помещается в оставшееся место, все следующие, ещё более крупные файлы тоже не поместятся. Для убывания пишут a.sort(reverse=True); создаёт новый отсортированный список и не меняет a.

Записи-кортежи обычная сортировка сравнивает по очереди: сначала первые числа, вторые — только при равенстве первых. Если нужно сортировать по второму числу, задают — функцию, которая для каждой записи говорит, по какому значению её сравнивать. Короче всего это записать через :

records = [(3, 8), (1, 9), (3, 2)]print(*sorted(records))print(*sorted(records, key=lambda record: record[1]))
(1, 9) (3, 2) (3, 8) (3, 2) (3, 8) (1, 9)

Во второй строке записи упорядочены по второму числу: 2, 8, 9. Та же функция с именем выглядела бы так: def second_value(record): return record[1] и key=second_value.

Заявки задачи темы обрабатывают в порядке прихода — по времени сдачи. Это первое поле записи, поэтому хватает обычной сортировки:

requests.sort()print(requests[:3])
[(1, 64), (2, 77), (3, 42)]
Сколько работы делает сортировка

Сортировку N записей Python делает примерно за N · log₂ N сравнений, а перебор всех пар — это N(N − 1)/2 пар:

from math import log2 for n in (1000, 100_000):    print(n, round(n * log2(n)), n * (n - 1) // 2)
1000 9966 499500 100000 1660964 4999950000

При N = 100 000 сортировка — 1,7 миллиона сравнений, все пары — почти пять миллиардов. Поэтому решения темы строятся на сортировке и одном-двух проходах, а не на переборе всех наборов.

Ключ рейтинга

Как перевести правила рейтинга — «больше баллов выше, при равенстве меньше штраф» — в сортировку?

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

Пусть записи имеют вид (student_id, score, penalty), а правила такие:

  1. Больше баллов — выше.
  2. При равенстве меньше штраф — выше.
  3. При полном равенстве меньше ID — выше.
students = [    (31, 80, 12),    (7, 95, 20),    (19, 95, 12),    (4, 95, 12)]students.sort(key=lambda x: (-x[1], x[2], x[0]))print(*[s[0] for s in students])
4 19 7 31

Почему перед баллами минус? Потому что 95 > 80, но −95 < −80. Обычная сортировка по возрастанию поставит -95 раньше -80, значит, участник с 95 баллами окажется раньше участника с 80. Почему 4 раньше 19? Баллы и штраф совпадают, решает третий показатель.

reverse=True переворачивает порядок всего ключа. Если направления полей различаются, меняют знак у конкретных числовых показателей. С key=lambda x: (x[1], -x[2], -x[0]) и reverse=True получился бы тот же порядок, но такую запись легко перепутать; ключ с минусами читается как сами правила.

Нужно различать три числа:

  • ID участника — значение из файла;
  • индекс в списке — позицию, с которой работает Python;
  • место в рейтинге — обычно индекс плюс один.

students[2][0] — это ID участника на третьем месте, здесь 7.

Вычисляемые показатели

А если рейтинг строится по показателям, которых нет в файле, — сумме, среднему, числу двоек?

Сначала вычислить показатели для каждого участника, сложить их в запись вместе с ID и уже её сортировать.

Пусть за каждый вопрос викторины участник получил баллы: положительные за верный ответ, отрицательные за неверный, ноль — если не отвечал. Для одного участника:

scores = [5, -2, 0, 3, -1]total = 0positive = 0answered = 0for score in scores:    total += score    if score > 0:        positive += score    if score != 0:        answered += 1print(total, positive, answered)
5 8 4

Здесь total — сумма всех баллов, positive — сумма положительных баллов, answered — количество ненулевых ответов. После этого все показатели сохраняют вместе с ID в одну запись и сортируют записи.

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

marks = [    (10, 4, 4, 4, 5), (3, 5, 5, 4, 2),    (6, 5, 4, 4, 4), (1, 2, 2, 3, 3),    (8, 5, 5, 5, 4), (2, 3, 2, 4, 5),] def rating_key(r):    twos = r[1:].count(2)    if twos == 0:        return (0, -sum(r[1:]), r[0])    return (twos, 0, r[0]) print(*[r[0] for r in sorted(marks, key=rating_key)])
8 6 10 2 3 1

Первый элемент ключа — число двоек: у сдавших он 0, поэтому они впереди. Второй — сумма с минусом, только для сдавших; у несдавших там 0, и при равном числе двоек решает ID. Студенты 6 и 10 набрали по 17, поэтому выше тот, у кого меньше ID.

Граница прохода

Кто проходит, если «проходят первые 30 %», а у следующего участника те же баллы, что у последнего прошедшего?

Решает точная формулировка: доля — это целая часть, а «и все с такими же показателями» сдвигает границу дальше, пока показатели совпадают.

Две разные формулировки:

Проходят первые 25 участников рейтинга.

и:

Проходят все, набравшие не меньше баллов, чем участник на 25-м месте.

Во втором случае пройти может больше 25 человек. Если граница задана долей — «первые 30 % мест», — её считают целой частью: k = n * 30 // 100. Затем, если условие добавляет «а также все с такими же показателями, как у последнего из них», границу сдвигают дальше, пока показатели совпадают. В таблице ниже записи — пары «сумма, количество ответов», уже упорядоченные:

table = [(90, 6), (85, 7), (80, 5), (80, 5),         (80, 6), (75, 4), (70, 8), (70, 9),         (60, 5), (55, 7)]k = len(table) * 30 // 100last = table[k - 1]while k < len(table) and table[k] == last:    k += 1print(len(table) * 30 // 100, k)
3 4

Первые 30 % — это 3 участника, последний из них — (80, 5). Четвёртый с теми же показателями тоже проходит, а (80, 6) на пятом месте — нет: сумма та же, но число ответов другое. Равенство для прохода иногда определяется только баллами, хотя сама таблица дополнительно упорядочена по ID. Какие показатели должны совпасть, решает условие, и смешивать эти правила нельзя.

Похожее правило — проходной и полупроходной балл. Мест S; группы с одинаковой суммой баллов берут целиком сверху, пока группа помещается в оставшиеся места. Сумма последней целиком поместившейся группы — проходной балл. Следующая сумма, группа которой уже не помещается целиком, — полупроходной балл: из этой группы оставшиеся места делят по дополнительным показателям, например по баллу за математику.

Пусть мест 4, а суммы — 250, 240, 240, 235, 235, 235: группы 250 и 240 занимают три места, проходной балл 240; трое с 235 на одно место целиком не помещаются, и 235 — полупроходной балл.

Итого

  • Записьполя одного объекта — вместе, сортируют целиком
  • Чтение файластрого по описанию формата
  • Критериивторой решает только среди равных по первому
  • Ключkey=lambda x: (-x[1], x[2], x[0])
  • Показателисначала вычислить, потом сортировать
  • Граница проходадоля — целая часть; равные последнему

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1
Что выведет print(*sorted([(2, 5), (1, 7), (2, 3)]))?
ОтветСкрыть ответ
Ответ
(1, 7) (2, 3) (2, 5): при равных первых числах решает второе.
Вопрос 2
Записи (id, score, time). Правила: больше баллов — выше; при равенстве меньше время — выше; при полном равенстве больше ID — выше. Запиши ключ сортировки.
ОтветСкрыть ответ
Ответ
key=lambda x: (-x[1], x[2], -x[0]): минус у баллов и у ID — по убыванию, время — по возрастанию.
Вопрос 3
Мероприятия (5, 9), (1, 20), (7, 8). Начала и окончания отсортировали отдельно и собрали пары по порядку. Какие пары получились и каких из них не было в данных?
ОтветСкрыть ответ
Ответ
Получились (1, 8), (5, 9), (7, 20); мероприятий (1, 8) и (7, 20) в данных не было.
Вопрос 4
В файле 20 000 чисел. Сколько всего пар разных элементов придётся проверить перебором всех пар и примерно сколько сравнений займёт сортировка (log₂ 20 000 ≈ 14,3)?
ОтветСкрыть ответ
Ответ
199 990 000 пар, сортировка — около 286 000 сравнений: 20 000 · 19 999 / 2 против 20 000 · 14,3.
Вопрос 5
Ячейка освободилась в минуту 404, следующий пассажир пришёл тоже в 404. Можно ли положить его багаж в эту ячейку по правилу задачи темы?
ОтветСкрыть ответ
Ответ
Нет: ячейку можно занять со следующей минуты, то есть с 405. Проверка — free_at < start, а 404 < 404 — ложь.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Лучший вариант

Варианты заданы парами «количество, самый большой элемент»: (5, 40), (6, 12), (6, 15), (4, 100). Какой вариант лучший, если сначала важно количество, а затем самый большой элемент?

Введите два числа лучшего варианта через пробел.

Порядок по трём полям

Записи имеют вид «ID баллы штраф»: 12 80 5, 3 90 8, 7 90 4, 2 90 4. Порядок: баллы убывают, штраф возрастает, ID возрастает. Запиши порядок ID.

Введите ID через пробел в порядке рейтинга.

Повышенная стипендия

В файле 26-1.txt записаны результаты сессии: в первой строке — количество студентов N, в каждой следующей — ID студента и четыре оценки от 2 до 5. Рейтинг составляют так: сначала студенты без двоек по убыванию суммы оценок, при равной сумме — по возрастанию ID; затем студенты с двойками по возрастанию количества двоек, при равном количестве — по возрастанию ID. Повышенную стипендию получают студенты, занявшие первые 15 % мест рейтинга (целая часть от 0,15 · N); гарантируется, что у всех них нет двоек. Запиши ID последнего студента с повышенной стипендией, затем количество студентов без двоек, сумма оценок которых равна сумме оценок этого студента.

26-1.txtРезультаты сессии: ID и четыре оценкиТекстовый файл · 54,3 КБ

Введите два числа через пробел в том порядке, в каком их спрашивают.

Победитель соревнования

Записи о решённых задачах (минута, участник): (14, 7), (3, 2), (25, 7), (9, 5), (31, 2), (12, 2), (40, 5), (18, 5), (44, 7), (52, 2), (36, 5), (61, 9). Побеждает тот, кто решил больше всех задач и первым набрал это число. Сколько задач решил победитель и какой у него номер?

Введите два числа через пробел в том порядке, в каком их спрашивают.

Сумма, положительные, ответы

Баллы участника за вопросы: 3, −2, 0, 5, −1, 0, 4. Найди сумму, сумму положительных и количество ответов.

Введите три числа через пробел в том порядке, в каком их спрашивают.

Первые 25 % и равные последнему

Суммы баллов 12 участников в таблице по убыванию: 90, 85, 80, 80, 80, 75, 70, 70, 65, 60, 55, 50. Проходят первые 25 % мест. Сколько участников пройдёт, если условие добавляет «и все с такой же суммой, как у последнего из них», и сколько — без этой добавки?

Введите два числа через пробел: с добавкой, затем без неё.

Финалисты викторины

В файле 26-2.txt записаны результаты викторины: в первой строке — количество участников N, в каждой следующей — ID участника и баллы за 10 вопросов (положительный балл — верный ответ, отрицательный — неверный, 0 — ответа нет). Для каждого участника считают сумму баллов и количество данных ответов (ненулевых баллов). Таблица упорядочена по убыванию суммы, при равной сумме — по возрастанию количества ответов, при равенстве обоих показателей — по возрастанию ID. В финал проходят участники, занявшие первые 20 % мест таблицы (целая часть), а также все участники, у которых оба показателя такие же, как у последнего из них. Запиши количество финалистов, затем ID первого в таблице участника, не прошедшего в финал.

26-2.txtРезультаты викторины: ID и баллы за 10 вопросовТекстовый файл · 81,7 КБ

Введите два числа через пробел в том порядке, в каком их спрашивают.