В предыдущей главе мы начали работать с составными данными, представленными в виде таблиц, и познакомились с несколькими табличными операциями, с помощью которых мы можем быстро ответить на многие непростые вопросы о наших данных. У всех этих операций есть два общих для них свойства: во-первых, все операции являются построчными, ни одна из этих операций не позволяет задать вопрос сразу про какой-то конкретный столбец; во-вторых, каждая операция производит в результате всегда то же самое, что и получает --- таблицу. Как мы уже знаем, существует множество различных типов данных: числа, строки, булевы значения, картинки и т. д., и нам бы хотелось иметь возможность получать на основе данных таблицы не ещё одну --- новую --- таблицу, а данные какого-нибудь другого вида, например, число. В этой главе мы узнаем, как можно избавиться от этих двух ограничений, введя по ходу новый тип данных.
Есть ещё довольно много вопросов, которые можно было бы задать о наших данных. К примеру:
-
Самая популярная композиция в плейлисте;
-
Самый большой файл в файловой системе;
-
Самый низкий человек в классе;
-
Средняя цена за один товар в магазине;
-
Список различных имейлов отправителей во входящих на почте;
-
Город Свердловской области с наибольшим количеством заболевших COVID-19.
Для ответа на эти и другие похожие вопросы нам нужно будет вычислять: максимум, минимум, среднее значение, медиану, моду и т. д. В Pyret встроены некоторые из этих статистических функций, обнаружить которые можно в пакетах math и statistics.
Мы уже, в принципе, способны это сделать, использовав известный нам select
:
songs = table: title, artist, play-count
row: "Harry Styles", "Adore You", 0
row: "Blinding Lights", "The Weeknd", 5
row: "Memories", "Maroon 5", 97
row: "The Box", "Roddy Ricch", 25
end
select play-count from songs end
но в итоге мы получим столбец в таблице, т. е. по сути опять таблицу
(состоящую из одного столбца). Чтобы получить ответы на вопросы выше, нам нужны
операции, которые бы обрабатывали всю эту совокупность чисел (значения
play-count
) целиком, что при имеющихся операциях сделать невозможно, т. к.
каждая из них работает построчно, т. е. за один раз они могут обработать
только одно число, а не всю совокупность сразу.
Извлечённый столбец имеет структуру более общую, чем сама таблица, и называется
такая структура списком. Чтобы получить какой-то столбец таблицы в виде
списка, нужно воспользоваться операцией extract
:
>>> extract play-count from songs end
[list: 0, 5, 97, 25]
Отличие select
от extract
заключается в том, что select
производит
таблицу, а extract
--- список.
Списки и таблицы из одного столбца очень похожи:
-
во-первых, и на списках, и на таблицах задан порядок. Т. е. имеет смысл говорить о «первом», «втором», «последнем» и т. д. элементе списка;
-
во-вторых, у всех элементов списка, как и у всех значений в одном столбце, должен быть один и тот же тип.
Ключевое же отличие состоит в том, что у списка нет «имени столбца»; список анонимен. Т. е. список сам по себе не говорит о том, какую информацию он отражает (имена людей, балансы банковских счетов, количество товаров в магазине, ...).
На самом деле, анонимные данные не являются чем-то новым. Все виды данных, с которыми мы уже успели поработать, являются анонимными. Любое отдельно взятое число никак не говорит нам о том, представлением чего оно является. Мы сами интерпретируем конкретные значения. А чтобы не забыть об этом, даём значениям осмысленные имена путём их связывания. То же самое можно сказать и о булевых значениях, и о строках, и о картинках. Единственным исключением из этого являются таблицы, внутри которых уже заключена эта интерпретация.
Универсальность анонимных данных является, с одной стороны, их преимуществом,
а с другой, --- недостатком. Недостаток состоит в том, что в этих данных нет
заранее заложенной интерпретации, по этой причине данные могут быть истолкованы
ошибочно, например, по нашей же невнимательности. В то же время, универсальность
позволяет нам использовать одни и те же данные в разных контекстах. Значение
7
можно использовать в программах и как количество дней недели, и как
телефонный код страны, и как что угодно ещё, что вы можете себе вообразить.
Отвечая на вопросы выше, можно заметить, что мы пользуемся одними и теми же операциями --- максимум, минимум, среднее и т. д. --- на списках значений, независимо от того, что эти значения представляют (прослушивания, память, рост, цены). Некоторые из этих операций (например, среднее значение) относятся только ко спискам чисел, другие же (например, максимум) могут быть применены к спискам не только чисел, но и других значений, которые мы умеем сравнивать между собой (это могут быть строки с лексикографическим порядком).
Мы узнали, как можно создать список из таблицы, используя extract
. Очевидно,
мы также можем создавать списки и без таблиц напрямую:
[list: 1, 2, 3]
[list: -1, 5, 2.3, 10]
[list: "a", "b", "c"]
[list: "Это", "список", "слов"]
Списки являются значениями, поэтому мы можем давать им имена
shopping-list = [list: "muesli", "fiddleheads"]
, которые впоследствии можно передавать функциям в качестве аргументов и т. д.
Упражнение: основываясь на примерах определений списков выше, угадайте, как можно задать пустой список, т. е. список, в котором нет ни одного элемента.
Подключив библиотеки math
и statistics
include math
include statistics
мы получаем доступ к следующим полезным функциям:
-
max
вычисляет максимальный элемент списка; -
min
вычисляет минимальный элемент списка; -
mean
вычисляет среднее значение элементов списка; -
stdev
вычисляет стандартное отклонение значений списка.
И тогда код
pcs = extract play-count from songs end
most-played-count = max(pcs)
least-played-count = min(pcs)
вычислит наибольшее и наименьшее количество прослушиваний из таблицы с песнями.
Подобным же образом мы можем вычислить максимальный и минимальный рост из таблицы с людьми:
hts = extract height from people end
tallest-height = max(hts)
shortest-height = min(hts)
Упражнение: создайте таблицу с тремя людьми такую, что после выполнения кода
выше значения 72
и 42
будут связаны с именами tallest-height
и
shortest-height
соответственно.
Заметьте, что в вопросах речь шла всё-таки о другом: там не спрашивалось о наибольшем росте, там спрашивалось о человеке с наибольшим ростом, и не о количестве прослушиваний, а о песне с максимальным количеством прослушиваний. Поскольку для работы с ростами и прослушиваниями в формате списков мы вырезали их из контекста, в котором они были, находясь в таблице, нельзя теперь определить, к каким людям или песням относятся эти значения. Чтобы это сделать, нам необходимо вернуться к таблице и выбрать те строки, которые связаны с нужным значением:
tallest-people = sieve people using height:
height == tallest-height
end
Таким образом, для получения правильного ответа на поставленный вопрос нам нужно написать
hts = extract height from people end
tallest-height = max(hts)
sieve people using height:
height == tallest-height
end
Упражнение: напишите выражение для определения песен с наибольшим количеством прослушиваний по аналогии с кодом выше.
Упражнение: объясните, почему в имени tallest-people
используется
множественное число (people --- люди).