добавлен исходный код и ссылки

This commit is contained in:
Alexander Zhirov 2023-01-22 15:43:59 +03:00
parent c83ed6cf42
commit 4be7b9c4ce
15 changed files with 438 additions and 1 deletions

View File

@ -66,6 +66,9 @@ import std.stdio;
Следующие разделы это стремительная поездка по Дибургу. Небольшие показательные программы дают общее представление о языке. Основная цель повествования на данном этапе обрисовать общую картину, а не дать ряд педантичных определений. Позже все аспекты языка будут рассмотрены с должным вниманием в деталях.
[Исходный код](src/chapter-1/)
[В начало ⮍](#1-знакомство-с-языком-d) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.1. Числа и выражения
Интересовались ли вы когда-нибудь ростом иностранцев? Давайте напишем простую программу, которая переводит наиболее распространенные значения роста в футах и дюймах в сантиметры.
@ -146,6 +149,9 @@ writefln("%s'%s''\t%s", feet, inches, (feet * inchesPerFoot + inches) * cmPerInc
Если вы использовали `printf` прежде, то могли бы почувствовать себя как дома, когда б не маленькая особенность: мы ведь выводим значения переменных типа `int` и `double` как же получилось, что и те и другие описаны с помощью спецификатора `%s`, обычно применяемого для вывода строк? Ответ прост. Средства D для работы с переменным количеством аргументов дают `writefln` доступ к информации об исходных типах переданных аргументов. Благодаря такому подходу программа получает ряд преимуществ: 1) значение `%s` может быть расширено до «строкового представления по умолчанию для типа переданного аргумента» и 2) если не удалось сопоставить спецификатор формата с типами переданных аргументов, вы получите ошибку в чистом виде, а не загадочное поведение, присущее вызовам `printf` с неверно заданным форматом (не говоря уже о подрыве безопасности, возможном при вызове `printf` с непроверяемыми заранее форматирующими строками).
[Исходный код](src/chapter-1-1/)
[В начало ⮍](#1-1-числа-и-выражения) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.2. Инструкции
В языке D, как и в других родственных ему языках, любое выражение, после которого стоит точка с запятой, это инструкция (например в программе «Hello, world!» сразу после вызова `writeln` есть ;). Действие инструкции сводится к вычислению выражения.
@ -179,6 +185,9 @@ if (‹выражение›) инструкция1 else ‹инструк
Чисто теоретический вывод, известный как принцип структурного программирования, гласит, что все алгоритмы можно реализовать с помощью составных инструкций, `if`-проверок и циклов а-ля `for` и `foreach`. Разумеется, любой адекватный язык (как и D) предлагает гораздо больше, но мы пока постановим, что с нас довольно и этих инструкций, и двинемся дальше.
[Исходный код](src/chapter-1-2/)
[В начало ⮍](#1-2-инструкции) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.3. Основы работы с функциями
Оставим пока в стороне обязательное определение функции `main` и посмотрим, как определяются другие функции на D. Определение функции соответствует модели, характерной и для других Алгол-подобных языков: сначала пишется возвращаемый тип, потом имя функции и, наконец, заключенный в круглые скобки список формальных аргументов, разделенных запятыми. Например, определение функции с именем `pow`, которая принимает значения типа `double` и `int`, а возвращает `double`, записывается так:
@ -219,10 +228,15 @@ void main()
О функциях можно еще долго рассказывать. Можно передавать функции другим функциям, встраивать одну в другую, разрешать функции сохранять свою локальную среду (полнофункциональная синтаксическая клауза), создавать анонимные функции (лямбда-функции), с удобством манипулировать ими и еще множество дополнительных «вкусностей». Со временем мы доберемся до каждой из них.
[Исходный код](src/chapter-1-3/)
[В начало ⮍](#1-3-основы-работы-с-функциями) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.4. Массивы и ассоциативные массивы
Массивы и ассоциативные массивы (которые обычно называют хеш-таблицами, или хешами) пожалуй, наиболее часто используемые сложные структуры данных за всю историю машинных вычислений, завистливо преследуемые списками языка Лисп. Множество полезных программ не требуют ничего, кроме массива или ассоциативного массива. Так что пришло время посмотреть, как D их реализует.
[В начало ⮍](#1-4-массивы-и-ассоциативные-массивы) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.1. Работаем со словарем
Для примера напишем простенькую программку, следуя такой спецификации:
@ -321,6 +335,9 @@ b = a.dup; // Полностью скопировать a в b
++b[10]; // В b[10] теперь 2, а в a[10] остается 1
```
[Исходный код](src/chapter-1-4-1/)
[В начало ⮍](#1-4-1-работаем-со-словарем) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.2. Получение среза массива. Функции с обобщенными типами параметров. Тесты модулей
Получение среза массива это мощное средство, позволяющее ссылаться на часть массива, в действительности не копируя данные массива. В качестве иллюстрации напишем функцию двоичного поиска, реализующую одноименный алгоритм: получив упорядоченный массив и значение, двоичный поиск быстро возвращает логический результат, сообщающий, есть ли заданное значение в массиве. Функция из стандартной библиотеки D возвращает более информативный ответ, чем просто булево значение, но знакомство с ней придется отложить, так как для этого необходимо более глубокое знание языка. Позволим себе, однако, приподнять планку, задавшись целью написать функцию, которая будет работать не только с массивами целых чисел, но с массивами элементов любого типа, допускающего сравнение с помощью операции `<`. Оказывается, реализовать эту задумку можно без особого труда. Вот как выглядит функция обобщенного двоичного поиска `binarySearch`:
@ -382,6 +399,9 @@ bool binarySearchR(T)(T[] input, T value)
Рекурсивная реализация явно проще и концентрированнее по сравнению со своим итеративным собратом. Кроме того, она ничуть не менее эффективна, так как рекурсивные вызовы оптимизируются благодаря популярной среди компиляторов технике, известной как *оптимизация хвостовой рекурсии*. В двух словах: если функция возвращает просто вызов самой себя (но с другими аргументами), компилятор модифицирует аргументы и инициирует переход к началу функции.
[Исходный код](src/chapter-1-4-2/)
[В начало ⮍](#1-4-2-получение-среза-массива-функции-с-обобщенными-типами-параметров-тесты-модулей) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.4.3. Подсчет частот. Лямбда-функции
Поставим себе задачу написать еще одну полезную программу, которая будет подсчитывать частоту употребления слов в заданном тексте. Хотите знать, какие слова употребляются в «Гамлете» чаще всего? Тогда вы как раз там, где надо.
@ -419,7 +439,7 @@ void main()
}
```
А теперь, скачав из Сети файл `hamlet.txt`[^6] (который вы найдете по прямой [ссылке](hamlet.txt)) и запустив нашу маленькую программу с шекспировским шедевром в качестве аргумента, вы получите:
А теперь, скачав из Сети файл `hamlet.txt`[^6] (который вы найдете по прямой [ссылке](src/hamlet.txt)) и запустив нашу маленькую программу с шекспировским шедевром в качестве аргумента, вы получите:
```sh
1 outface
@ -489,6 +509,9 @@ sort!(‹аргументы времени компиляции›)(аргу
Что и ожидалось: самые часто употребляемые слова набрали больше всего «очков». Настораживает лишь «Ham»[^7]. Это слово в пьесе вовсе не отражает кулинарные предпочтения героев. «Ham» всего лишь сокращение от «Hamlet» (Гамлет), которым помечена каждая из его реплик. Явно у него был повод высказаться 358 раз больше, чем любой другой герой пьесы. Далее по списку следует король всего 116 реплик, меньше трети сказанного Гамлетом. А Офелия с ее 58 репликами просто молчунья.
[Исходный код](src/chapter-1-4-3/)
[В начало ⮍](#1-4-3-подсчет-частот-лямбда-функции) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.5. Основные структуры данных
Раз уж мы взялись за «Гамлета», проанализируем этот текст чуть глубже. Например, соберем кое-какую информацию о главных героях: сколько всего слов было произнесено каждым персонажем и насколько богат его (ее) словарный запас. Для этого с каждым действующим лицом понадобится связать несколько фактов. Чтобы сосредоточить эту информацию в одном месте, определим такую структуру данных:
@ -662,6 +685,9 @@ Ambassador 41 34
В выводе есть немного шума (например, `"Both [Mar"`), который прилежный программист легко устранит и который вряд ли статистически влияет на то, что действительно представляет для нас интерес. Тем не менее исправление последних огрехов поучительное (и рекомендуемое) упражнение.
[Исходный код](src/chapter-1-5/)
[В начало ⮍](#1-5-основные-структуры-данных) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.6. Интерфейсы и классы
Объектно-ориентированные средства важны для больших проектов; так что, знакомя вас с ними на примере маленьких программ, я рискую выставить себя недоумком. Прибавьте к этому большое желание избежать заезженных примеров с животными и работниками, и сложится довольно неприятная картина. Да, забыл еще кое-что: в маленьких примерах обычно не видны проблемы создания полиморфных объектов, а это очень важно. Что делать бедному автору! К счастью, реальный мир снабдил меня полезным примером в виде относительно небольшой задачи, которая в то же время не имеет удовлетворительного процедурного решения. Обсуждаемый ниже код это переработка небольшого полезного скрипта на языке awk, который вышел далеко за рамки задуманного. Мы вместе пройдем путь до объектно-ориентированного решения одновременно компактного, полного и изящного.
@ -762,6 +788,9 @@ auto newStat = cast(Stat) Object.factory("stats." ~ arg);
Оглянувшись назад, мы заметим, что львиная доля того, что делает скрипт, выполняется в первых пяти его строках. Эта самая сложная часть, которая полностью определяет весь остальной код. Второй цикл читает по одному числу за раз (об этом заботится функция `readf`) и вызывает `accumulate` для всех объектов, собирающих статистику. Функция `readf` возвращает число объектов, успешно прочитанных согласно заданной строке формата. В нашем случае формат задан в виде строки `" %s "`, что означает «один элемент, окруженный любым количеством пробелов». (Тип элемента определяется типом считанного элемента, в нашем случае `x` принимает значение типа `double`.) Последнее, что делает программа, выводит результаты вычислений на печать.
[Исходный код](src/chapter-1-6/)
[В начало ⮍](#1-6-интерфейсы-и-классы) [Наверх ⮍](#1-знакомство-с-языком-d)
### 1.6.1. Больше статистики. Наследование
Реализация `Max` так же тривиальна, как и реализация `Min`; за исключением небольших изменений в `accumulate`, эти классы ничем не отличаются друг от друга[^9]. Даже если новое задание до боли напоминает предыдущее, в голову должна приходить мысль «интересно», а не «о боже, какая скука». Рутинные задачи это возможность для повторного использования, и «правильные» языки, способные лучше эксплуатировать различные преимущества подобия, по некоторой абстрактной шкале качества должны оцениваться выше. Нам придется выяснить, что именно общего у функций `Min` и `Max` (и, в идеале, у прочих статистических функций). Присмотревшись к ним, можно заметить, что обе принадлежат к разряду статистических функций, результат которых вычисляется шаг за шагом и может быть вычислен всего по одному числу. Назовем такую категорию статистических функций *пошаговыми функциями*.
@ -830,6 +859,9 @@ class Average : IncrementalStat
Начнем с того, что в `Average` вводится еще одно поле, `items`, которое инициализируется нулем с помощью синтаксиса `items = 0` (только для того, чтобы показать, как надо инициализировать переменные, но, как отмечалось выше, целые числа и так инициализируются нулем по умолчанию). Второе, что необходимо отметить: `Average` определяет конструктор, который присваивает переменной `_result` ноль. Так сделано, потому что, в отличие от минимума или максимума, при отсутствии аргументов среднее арифметическое считается равным нулю. И хотя может показаться, что инициализировать `_result` значением NaN только для того, чтобы тут же записать в эту переменную ноль, бессмысленное действие, уход от так называемого «мертвого присваивания» представляет собой легкую добычу для любого оптимизатора. Наконец, `Average` переопределяет метод `postprocess`, несмотря на то что в классе `IncrementalStat` он уже определен. В языке D по умолчанию можно переопределить (унаследовать и заново определить) методы любого класса, но надо обязательно добавлять директиву `override`, чтобы избежать всевозможных несчастных случаев (таких как неудача переопределения в связи с какой-нибудь опечаткой или изменением в базовом типе, либо переопределение чего-нибудь по ошибке). Если вы поставите перед методом класса ключевое слово `final`, то запретите классам-потомкам переопределять эту функцию (что эффективно останавливает механизм динамического поиска методов по дереву классов).
[Исходный код](src/chapter-1-6-1/)
[В начало ⮍](#1-6-1-больше-статистики-наследование) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.7. Значения против ссылок
Проведем небольшой эксперимент:
@ -872,6 +904,9 @@ void main()
В завершение хочется сказать, что структуры пожалуй, наиболее гибкое проектное решение. Определив структуру, вы можете вдохнуть в нее любую семантику. Вы можете сделать так, что значение будет копироваться постоянно, реализовать ленивое копирование, а-ля копирование при записи, или подсчитывать ссылки, или выбрать что-то среднее между этими способами. Вы даже можете определить ссылочную семантику, используя классы или указатели *внутри* своей структуры. С другой стороны, некоторые из этих альтернатив требуют подкованности в техническом плане; использование классов, напротив, подразумевает простоту и унифицированность.
[Исходный код](src/chapter-1-7/)
[В начало ⮍](#1-7-значения-против-ссылок) [Наверх ⮍](#1-знакомство-с-языком-d)
## 1.8. Итоги
Эта глава вводная, поэтому какие-то детали отдельных примеров и концепций остались за кадром или были рассмотрены вскользь. При этом опытный программист легко поймет, как можно завершить и усовершенствовать код примеров.
@ -880,6 +915,8 @@ void main()
Как водится, полный рассказ гораздо длиннее. И все же полезно время от времени вернуться к основам и удостовериться, что простые вещи остаются простыми.
[В начало ⮍](#1-8-итоги) [Наверх ⮍](#1-знакомство-с-языком-d)
[^1]: «Shebang» (от shell bang: shell консоль, bang восклицательный знак), или «shabang» (# sharp) обозначение пути к компилятору или интерпретатору в виде `#!/путь/к/программе`. *Прим. пер.*
[^2]: В этой книге под «параметром» понимается значение, используемое внутри функции, а под «аргументом» значение, передаваемое в функцию извне.
[^3]: `.idup` свойство любого массива, возвращающее неизменяемую (immutable) копию массива. Про неизменяемость будет рассказано позже, пока же следует знать, что ключ ассоциативного массива должен быть неизменяемым. *Прим. науч. ред.*

View File

@ -0,0 +1,16 @@
import std.stdio;
void main()
{
// Зна­че­ния, ко­то­рые ни­ко­гда не из­ме­нят­ся
immutable inchesPerFoot = 12;
immutable cmPerInch = 2.54;
// Пе­ре­би­ра­ем и пи­шем
foreach (feet; 5 .. 7)
{
foreach (inches; 0 .. inchesPerFoot)
{
writeln(feet, "'", inches, "''\t", (feet * inchesPerFoot + inches) * cmPerInch);
}
}
}

View File

@ -0,0 +1,12 @@
import std.stdio;
void main()
{
// Зна­че­ния, ко­то­рые ни­ко­гда не из­ме­нят­ся
immutable inchesPerFoot = 12;
immutable cmPerInch = 2.54;
// Пе­ре­би­ра­ем и пи­шем
foreach (feet; 5 .. 7)
foreach (inches; 0 .. inchesPerFoot)
writeln(feet, "'", inches, "''\t", (feet * inchesPerFoot + inches) * cmPerInch);
}

View File

@ -0,0 +1,15 @@
import std.stdio;
void fun(ref uint x, double y)
{
x = 42;
y = 3.14;
}
void main()
{
uint a = 1;
double b = 2;
fun(a, b);
writeln(a, " ", b);
}

View File

@ -0,0 +1,19 @@
import std.stdio, std.string;
import std.algorithm;
void main()
{
size_t [string] dictionary;
foreach (line; stdin.byLine())
{
// Раз­бить стро­ку на сло­ва
// До­ба­вить ка­ж­дое сло­во стро­ки в сло­варь
foreach (word; line.strip.splitter)
{
if (word in dictionary) continue; // Ни­че­го не де­лать
auto newID = dictionary.length;
dictionary[word.idup] = newID;
writeln(newID, '\t', word);
}
}
}

View File

@ -0,0 +1,35 @@
import std.array;
bool binarySearch(T)(T[] input, T value)
{
while (!input.empty)
{
auto i = input.length / 2;
auto mid = input[i];
if (mid > value) input = input[0 .. i];
else if (mid < value) input = input[i + 1 .. $];
else return true;
}
return false;
}
// рекурсивная версия
bool binarySearchR(T)(T[] input, T value)
{
if (input.empty) return false;
auto i = input.length / 2;
auto mid = input[i];
if (mid > value) return binarySearch(input[0 .. i], value);
if (mid < value) return binarySearch(input[i + 1 .. $], value);
return true;
}
unittest
{
assert(binarySearch([ 1, 3, 6, 7, 9, 15 ], 6));
assert(!binarySearch([ 1, 3, 6, 7, 9, 15 ], 5));
assert(binarySearchR([ 1, 3, 6, 7, 9, 15 ], 6));
assert(!binarySearchR([ 1, 3, 6, 7, 9, 15 ], 5));
// Ука­зать T яв­но (на­при­мер, для на­деж­но­сти)
assert(binarySearch!(int)([ 1, 3, 6, 7, 9, 15 ], 6));
}

View File

@ -0,0 +1,28 @@
import std.algorithm, std.stdio, std.string;
void main()
{
// Рас­счи­тать таб­ли­цу час­тот
uint[string] freqs;
foreach (line; stdin.byLine())
{
foreach (word; line.strip.splitter)
{
++freqs[word.idup];
}
}
// На­пе­ча­тать таб­ли­цу час­тот
foreach (key, value; freqs)
{
writefln("%6u\t%s", value, key);
}
// На­пе­ча­тать таб­ли­цу час­тот с сортировкой
string[] words = freqs.keys;
sort!((a, b) { return freqs[a] > freqs[b]; })(words);
foreach (word; words)
{
writefln("%6u\t%s", freqs[word], word);
}
}

View File

@ -0,0 +1,68 @@
import std.algorithm, std.conv, std.regex, std.range, std.stdio, std.string, std.ascii;
struct PersonaData
{
uint totalWordsSpoken;
uint[string] wordCount;
}
void addParagraph(string line, ref PersonaData[string] info)
{
// Вы­де­лить имя пер­со­на­жа и его ре­п­ли­ку
line = strip(line);
// auto sentence = std.algorithm.find(line, ". ");
auto sentence = line.find(". ");
if (sentence.empty)
{
return;
}
auto persona = line[0 .. $ - sentence.length];
sentence = toLower(strip(sentence[2 .. $]));
// Вы­де­лить про­из­не­сен­ные сло­ва
auto words = split(sentence, regex("[ \t,.;:?]+"));
// Вставка или обновление информации
if (!(persona in info))
{
// Пер­вая ре­п­ли­ка пер­со­на­жа
info[persona] = PersonaData();
}
info[persona].totalWordsSpoken += words.length;
foreach (word; words)
{
++info[persona].wordCount[word];
}
}
void printResults(PersonaData[string] info)
{
foreach (persona, data; info)
{
writefln("%20s %6u %6u", persona, data.totalWordsSpoken, data.wordCount.length);
}
}
void main()
{
// На­ка­пл­и­ва­ет ин­фор­ма­цию о глав­ных ге­ро­ях
PersonaData[string] info;
// За­пол­нить info
string currentParagraph;
foreach (line; stdin.byLine())
{
// 4 символа отступа
if (line.startsWith(" ") && line.length > 4 && isAlpha(line[4]))
{
// Пер­со­наж про­дол­жа­ет вы­ска­зы­ва­ние
currentParagraph ~= line[3 .. $];
}
// 2 символа отступа
else if (line.startsWith(" ") && line.length > 2 && isAlpha(line[2]))
{
// Пер­со­наж толь­ко что на­чал го­во­рить
addParagraph(currentParagraph, info);
currentParagraph = to!string(line[2 .. $]);
}
}
// За­кон­чи­ли, те­перь на­пе­ча­та­ем со­б­ран­ную ин­фор­ма­цию
printResults(info);
}

View File

@ -0,0 +1,26 @@
import std.stdio : writeln, stdin;
import std.exception : enforce;
import stats;
void main(string[] args)
{
Stat[] stats;
foreach (arg; args[1 .. $])
{
auto newStat = cast(Stat) Object.factory("stats." ~ arg);
enforce(newStat, "Invalid statistics function: " ~ arg);
stats ~= newStat;
}
for (double x; stdin.readf(" %s ", &x) == 1;)
{
foreach (s; stats)
{
s.accumulate(x);
}
}
foreach (s; stats)
{
s.postprocess();
writeln(s.result());
}
}

View File

@ -0,0 +1,76 @@
module stats;
interface Stat
{
void accumulate(double x);
void postprocess();
double result();
}
class IncrementalStat : Stat
{
protected double _result;
abstract void accumulate(double x);
void postprocess() {}
double result()
{
return _result;
}
}
class Min : IncrementalStat
{
this()
{
_result = double.max;
}
override void accumulate(double x)
{
if (x < _result)
{
_result = x;
}
}
}
class Max : IncrementalStat
{
this()
{
_result = double.min_normal;
}
override void accumulate(double x)
{
if (x > _result)
{
_result = x;
}
}
}
class Average : IncrementalStat
{
private uint items = 0;
this()
{
_result = 0;
}
override void accumulate(double x)
{
_result += x;
++items;
}
override void postprocess()
{
if (items)
{
_result /= items;
}
}
}

View File

@ -0,0 +1,26 @@
import std.stdio : writeln, stdin;
import std.exception : enforce;
import stats;
void main(string[] args)
{
Stat[] stats;
foreach (arg; args[1 .. $])
{
auto newStat = cast(Stat) Object.factory("stats." ~ arg);
enforce(newStat, "Invalid statistics function: " ~ arg);
stats ~= newStat;
}
for (double x; stdin.readf(" %s ", &x) == 1;)
{
foreach (s; stats)
{
s.accumulate(x);
}
}
foreach (s; stats)
{
s.postprocess();
writeln(s.result());
}
}

View File

@ -0,0 +1,48 @@
module stats;
interface Stat
{
void accumulate(double x);
void postprocess();
double result();
}
class Min : Stat
{
private double min = double.max;
void accumulate(double x)
{
if (x < min)
{
min = x;
}
}
void postprocess() {} // Ни­че­го не де­лать
double result()
{
return min;
}
}
class Max : Stat
{
private double max = double.min_normal;
void accumulate(double x)
{
if (x > max)
{
max = x;
}
}
void postprocess() {} // Ни­че­го не де­лать
double result()
{
return max;
}
}

View File

@ -0,0 +1,25 @@
import std.stdio;
struct MyStruct
{
int data;
}
class MyClass
{
int data;
}
void main()
{
// Иг­ра­ем с объ­ек­том ти­па MyStruct
MyStruct s1;
MyStruct s2 = s1;
++s2.data;
writeln(s1.data); // Пе­ча­та­ет 0
// Иг­ра­ем с объ­ек­том ти­па MyClass
MyClass c1 = new MyClass;
MyClass c2 = c1;
++c2.data;
writeln(c1.data); // Пе­ча­та­ет 1
}

View File

@ -0,0 +1,6 @@
import std.stdio;
void main()
{
writeln("Hello, world!");
}