Многие интересные и важные данные имеют табличную форму --- т. е. выглядят как таблицы. Давайте сперва рассмотрим несколько примеров таких данных, прежде чем будем пытаться найти между ними что-нибудь общее.
-
Ваш почтовый ящик является по сути списком сообщений. О каждом из этих сообщений ваш ящик хранит кучу информации: его отправителя, тему сообщения, тело сообщения, дату и т. д.
-
Музыкальный плейлист. Для каждой композиции плеер содержит информацию об имени артиста, о названии композиции, о названии альбома, о продолжительности композиции и многое другое: дата издания, номер по списку в альбоме, текст песни, ...
-
Папка или директория. Для каждого файла ваша файловая система записывает его имя, дату последнего изменения, размер, права на чтение/запись/исполнение для конкретных пользователей и т. д.
Что общего между всеми этими примерами? Для табличных данных характерно следующее:
-
Они состоят из строк и столбцов. Например, каждая песня, или имейл, или файл --- это строка. Каждая из их характеристик --- название композиции, тема сообщения, имя файла --- это столбец.
-
У каждой строки столько же столбцов, сколько и у любой другой, причём в том же порядке.
-
Каждый столбец обладает своим типом, но типы разных столбцов могут быть разными. Например, сообщение на электропочте содержит в себе имя отправителя, которое является строкой; тему сообщения, которая также является строкой; дату отправки, которая имеет тип даты; прочитано это сообщение или нет --- тип
Boolean
; и так дальше. -
Строки обычно записаны в некотором определённом порядке. Имейлы, как правило, упорядочены по дате --- сначала идут те, которые были присланы недавно, а в конце --- те, которые пришли раньше всех остальных.
В этой главе мы узнаем, как можно работать с таблицами в Pyret и как использовать таблицы для решения различных задач.
Язык предоставляет нам несколько простых способов создания табличных данных. Самый простой --- определить данные в программе таким образом:
table: name, age
row: "Alice", 30
row: "Bob", 40
row: "Carol", 25
end
То есть написать слово table
, после которого записать имена столбцов в
определённом порядке, после которых выписать строки таблицы. Каждая строка
должна содержать ровно столько значений, сколько указано в столбцах, причём в
том же порядке.
Указание: попробуйте поизменять различные части примера выше --- например, удалите какое-нибудь значение из одной из строк, добавьте дополнительное значение к какой-нибудь строке, удалите запятую, добавьте запятую в конец строки --- и посмотрите, какие ошибки сгенерирует Pyret.
Заметьте, что в таблицах важен порядок столбцов: две таблицы с одинаковыми столбцами, расположенными в разном порядке, не считаются равными
check:
table: name, age
row: "Alice", 30
row: "Bob", 40
row: "Carol", 25
end
is-not
table: age, name
row: 30, "Alice"
row: 40, "Bob"
row: 25, "Carol"
end
end
Конструкция с check
работает так же, как и where
в определении функции ---
с помощью неё мы пишем тесты для нашей программы. Отличие состоит в том, что
where
используется для проверки какой-то одной конкретной функции, а check
можно использовать для проверки всей программы целиком, написав её в конце программы.
Также здесь вместо привычного слова is
используется is-not
, которое, как
легко догадаться, работает противоположным образом: тест считается пройденным,
если значение выражения слева от is-not
не равно значению выражения справа от
is-not
.
Табличные выражения дают нам при исполнении табличные значения, а значит, мы можем давать им имена так же, как уже давали имена строкам, числам, картинкам и т. д.
people = table: name, age
row: "Alice", 30
row: "Bob", 40
row: "Carol", 25
end
Таблицы, созданные при помощи table
, мы называем табличными литералами. Но
есть и другие способы --- например, можно импортировать таблицу из Google
Docs, указав идентификатор:
include gdrive-sheets
imported-my-table =
load-spreadsheet("1BAexzf08Q5o8bXb_k8PwuE3tMKezxRfbKBKT-4L6UzI")
Таким образом, вы можете:
-
сами создать таблицу,
-
редактировать таблицу совместно с кем-то в Google Docs, а затем импортировать её в программе,
-
найти таблицу где-нибудь в Интернете и импортировать её
-
создать опрос, выбрать для сохранения ответов респондентов какую-нибудь таблицу, а затем импортировать ответы уже в виде таблицы в вашу программу.
Давайте теперь разберёмся с тем, как мы можем обрабатывать данные внутри таблицы. Сам Pyret предоставляет нам множество операций для работы с таблицами. Однако, если этих операций нам будет недостаточно, мы имеем возможность написать свои собственные, но об этом мы поговорим позднее. А пока сфокусируемся на том, что даёт нам Pyret уже сейчас.
Давайте подумаем, какие вопросы у нас могут возникнуть к нашим данным:
-
Какие имейлы были присланы от конкретного человека?
-
Какие композиции есть у конкретного артиста?
-
У каких композиций в плейлисте больше всего прослушиваний?
-
Какие композиции слушали меньше остальных?
Видно, что некоторые из них относятся к отбору определённых строк, а другие --- к их упорядочиванию. Pyret даёт нам операции, которые позволят это сделать.
Пусть у нас есть таблица, которая представляет собой наш электронный почтовый ящик:
email = table: sender, recipient, subject
row: 'Саша', 'Маша', 'Очень срочно!!!!!!11'
row: 'Amazon', 'Маша', 'Verify your new Amazon account'
row: 'Саша', 'Маша', 'А3 распечатать'
row: 'Zoom', 'Маша', 'Активируйте свою учётную запись Zoom'
end
и мы хотим получить таблицу сообщений от Саши.
Сделать это мы можем так:
sieve email using sender:
sender == 'Саша'
end
--- здесь мы сообщаем о том, что хотим просеять таблицу email
, используя
столбец sender
. Эта операция обрабатывает каждую строку таблицы. Для каждой
строки переменная sender
означает значение столбца sender
в этой строке.
Значение выражения внутри (между :
и end
) должно иметь тип Boolean
. Если
это true
, то Pyret оставляет эту строку для итоговой таблицы, в противном
случае он её отбрасывает. Результатом выполнения такого запроса является
новая таблица с теми же самыми столбцами и лишь некоторыми --- отсеянными ---
строками (в принципе, в результате строк может не оказаться вообще).
Подобным же образом можно выбрать строки из таблицы с плейлистом по имени артиста:
sieve playlist using artist:
(artist == 'Sangam') or (artist == 'Kid Smpl')
end
Указание: напишите таблицу playlist
которая будет работать с sieve
выражением выше.
Указание: напишите sieve
выражение с таблицей email
, результатом которого
будет таблица с нулём строк.
Мы можем использовать order
выражение для упорядочивания строк. В результате
исполнения этого выражения получается таблица со строками, расположенными в
указанном порядке:
order playlist:
play-count ascending
end
--- это выражение упорядочивает композиции в плейлисте по количеству прослушиваний в возрастающем порядке, т. е. наверху будут те композиции, которые прослушивали меньше остальных.
Заметьте, что то, что записано между :
и end
, не является выражением,
поэтому мы не можем здесь написать любой код, который нам захочется. Мы можем
лишь написать названия столбцов и указать, в каком порядке они должны быть
упорядочены.
order playlist:
play-count descending,
artist ascending,
song ascending
end
Необязательно за один раз выполнять только одну из этих операций. Поскольку каждая из них получает таблицу и производит таблицу, мы легко можем их скомбинировать. Давайте сформулируем сначала то, что мы можем хотеть таким образом сделать.
-
Какое из писем от конкретного человека самое старое?
-
Какую из композиций конкретного артиста слушали реже всех остальных?
Вот первый пример:
sasha-emails = sieve email using sender:
sender == 'Саша'
end
order sasha-emails:
sent-date ascending
end
Заметьте, что в order
выражении мы упорядочиваем не таблицу email
, которая
является таблицей со всеми письмами, а только sasha-emails
--- таблицу с
письмами от одного конкретного человека.
Иногда нам может быть нужно добавить к таблице столбец, чьи значения будут
основаны на значениях других, уже имеющихся в таблице, столбцов. Например, наша
таблица может отражать записи о работниках какой-нибудь компании и иметь
столбцы hourly-wage
и hours-worked
, содержащие соответственно размер
почасовой оплаты и количество отработанных часов. И нам бы хотелось добавить
ещё один столбец, в котором будут записаны зарплаты для каждого работника:
extend employees using hourly-wage, hours-worked:
total-wage: hourly-wage * hours-worked
end
--- это создаёт новый столбец total-wage
, чьё значение в каждой строке равно
произведению значений названных выше столбцов. Pyret добавит этот столбец
справа в конце. Далее мы увидим, как можно менять порядок столбцов.
Разумеется, расширения мы можем комбинировать с другими табличными операциями.
Например, мы можем сначала создать столбец subject-length
, который содержит в
себе длины тем сообщений, а затем отсортировать сообщения по этой длине так,
чтобы сначала шли сообщения с наиболее подробными темами:
ext-email = extend email using subject:
subject-length: string-length(subject)
end
order ext-email:
subject-length descending
end
Бывает так, что таблица «почти правильная» и требует лишь небольшой доработки. Например, мы можем получить сведения о температуре воздуха из разных стран в разных форматах и хотим привести все эти температуры к одному виду (формату). Или у нас есть таблица с заказами какого-нибудь товара от разных людей, и мы хотим выставить ограничение на количество заказов для одного человека.
В обоих случаях мы хотим оставить у таблицы её исходную «форму» --- те же
столбцы, те же строки, всё в том же порядке ---, но слегка изменяя значения
некоторых столбцов. В Pyret это можно сделать с помощью transform
. Вот пример
с заказами:
transform orders using count:
count: num-min(count, 3)
end
в котором мы устанавливаем максимально возможное количество заказов для каждого отдельного покупателя.
Естественно, в преобразовании могут использоваться и те столбцы, которые изменениям не подвергаются:
transform weather using temp, unit:
temp:
if unit == "F":
fahrenheit-to-celsius(temp)
else:
temp
end
unit:
if unit == "F":
"C"
else:
unit
end
end
--- здесь мы для изменения значений temp
используем значения unit
и в целом
этим преобразованием конвертируем все температуры в градусы Цельсия.
Иногда бывает полезно иметь возможность посмотреть не всю таблицу целиком, а лишь некоторые её столбцы --- например, когда этих столбцов довольно много. Этим же способом можно поменять порядок столбцов, сгруппировав их более подходящим образом. В таблице, представляющей школьный журнал с оценками за какую-нибудь четверть по какому-нибудь предмету будет несколько десятков столбцов --- имя и фамилия ученика, учебные дни, итоговая оценка. К окончанию четверти, после выставления всех оценок, мы можем захотеть посмотреть итоговую оценку для каждого ученика, не просматривая все промежуточные оценки, выставленные в течение четверти:
select name, total-grade from gradebook end
Также мы можем группировать эту операцию с другими. Например, из таблицы плейлиста можем выбрать только имя артиста и название композиции, отсортировав по имени артиста:
ss = select artist, song from playlist end
order ss:
artist ascending
end
В этой главе мы познакомились со множеством различных операций, каждая из которых получает какую-то таблицу и производит в результате уже другую --- новую --- таблицу по определённым правилам. Полезно суммировать наши знания об этих операциях, взглянув на то, как каждая из них влияет на те ключевые свойства, которыми обладают все без исключения таблицы. («—» в таблице означает отсутствие каких-либо изменений.)
Операция | Содержимое ячейки | Порядок строк | Число строк | Порядок столбцов | Число столбцов |
---|---|---|---|---|---|
Отсеивание | — | — | уменьшает | — | — |
Упорядочивание | — | изменяет | — | — | — |
Расширение | не изменяет существующие, создаёт новые | — | — | — | увеличивает |
Преобразование | изменяет | — | — | — | — |
Выборка | — | — | — | изменяет | уменьшает |
Обратите внимание на то, что слова «изменяет» и «уменьшает» следует читать как «возможно изменяет» и «возможно уменьшает».
В зависимости от операции и содержимого таблицы, изменений может не быть вовсе
--- например, если таблица уже упорядочена по критерию, написанному в order
выражении, то порядок строк не поменяется.