product для int64

This commit is contained in:
Mikhalkovich Stanislav 2025-11-13 16:10:09 +03:00
parent 16a8066a5b
commit 6d14df7ffc
15 changed files with 33 additions and 2067 deletions

View file

@ -15,7 +15,7 @@ internal static class RevisionClass
public const string Major = "3";
public const string Minor = "11";
public const string Build = "0";
public const string Revision = "3699";
public const string Revision = "3700";
public const string MainVersion = Major + "." + Minor;
public const string FullVersion = Major + "." + Minor + "." + Build + "." + Revision;

View file

@ -1,4 +1,4 @@
%MINOR%=11
%REVISION%=3699
%COREVERSION%=0
%REVISION%=3700
%MINOR%=11
%MAJOR%=3

View file

@ -13,7 +13,7 @@
Приведем формулировку этого задания.
</p>
<p><b>a1</b>. Закрасить помеченные клетки.</p>
<p><b>RBa1</b>. Закрасить помеченные клетки.</p>
<blockquote>
<img border="0" src="gif/RBa1-1.png">
</blockquote>

View file

@ -1,654 +0,0 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title></title>
<link rel="StyleSheet" href="../../default.css">
</head>
<body>
<a name="main"></a>
<h1>Задания, связанные с ЕГЭ по информатике
</h1>
<hr>
<h2>Пример 1. Простая задача на реализацию базовых алгоритмов
</h2>
<hr>
<p>Группа заданий ExamBegin посвящена базовым алгоритмическим задачам,
включенным в кодификатор ЕГЭ по информатике. Процесс выполнения подобных заданий мы рассмотрим на примере
одной из простых задач, связанных с нахождением максимумов и минимумов из двух, трех или четырех чисел
без использования массивов и циклов.
</p>
<p class="task">ExamBegin2&#176;. На вход подаются три вещественных числа; числа расположены в одной строке.
Вывести вначале минимальное, а затем максимальное из них. Каждое число должно выводиться на новой строке
и снабжаться комментарием: &#171;MIN=&#187; для минимального, &#171;MAX=&#187;
для максимального.
</p>
<hr>
<h3>Создание программы-заготовки и знакомство с заданием
</h3>
<p>Напомним, что программу-заготовку для решения этого задания можно создать с помощью
команды
меню &laquo;Модули | Создать шаблон программы&raquo;, кнопки
<img border="0" src="gif/Load.gif" width="16" height="17"> или клавиатурной
комбинации <b>Shift+Ctrl+L</b>. Приведем текст созданной заготовки:
</p>
<blockquote><code><b>uses</b> PT4Exam;
<b><br><br>begin</b>
<br>&nbsp;
Task('ExamBegin2');
<b><br>end</b>.
</code></blockquote>
<p>После запуска программы на экране появится <a href="../PT4/window.html">окно задачника</a>:
</p>
<blockquote>
<img border="0" src="gif/ExamBegin2-1.png">
</blockquote>
<p>Обсудим особенности программы-заготовки и окна задачника.
</p>
<p>В программе-заготовке вместо модуля <code>PT4</code>
подключается модуль <code>PT4Exam</code>, специально предназначенный для использования при выполнении
заданий групп Exam. Данный модуль содержит реализацию единственной процедуры <code>Task</code>,
инициализирующей задание. Никакие дополнительные процедуры, связанные с вводом-выводом, в него не включены.
Это обусловлено тем, что ввод-вывод при выполнении заданий групп Exam надо выполнять, используя
<i>стандартные процедуры языка Pascal</i>.
</p>
<p>Основной особенностью окна задачника является то, что в разделе исходных данных отсутствуют данные,
выделенные желтым цветом (напомним, что желтый цвет используется для выделения данных, которые необходимо
вводить с помощью специальных процедур ввода задачника). Вместо этого в окне отображается
строка бирюзового цвета, содержащая числовые данные. Вид строки подчеркивает то обстоятельство,
что вводить данные требуется не с помощью специальных процедур ввода, имеющихся в задачнике,
а с помощью стандартных процедур языка Pascal. Отметим, что бирюзовый цвет используется в окне задачника
для отображения &laquo;внешних&raquo; данных (содержащихся в файлах или динамических структурах), доступ к которым
должен осуществляться с помощью стандартных средств используемого языка программирования.
</p>
<p>Пример верного решения выделяется серым цветом
(в отличие от &laquo;настоящих&raquo; результатов, выведенных программой учащегося, которые, как и входные данные,
выделяются бирюзовым цветом), однако <i>представление</i> выходных данных совпадает с
представлением входных: это набор строк,
содержащих числовые данные (дополненные комментариями). Вид данных в разделе результатов показывает,
что для их вывода, как и для ввода исходных данных, необходимо использовать стандартные процедуры языка Pascal.
</p>
<p>
<b>Примечание.</b>
Если вы уже выполняли задания, связанные с обработкой файлов, то можете заметить, что отображение данных
в заданиях групп Exam
в точности соответствует способу отображения содержимого <i>текстовых файлов</i>. Это совпадение не случайно.
На самом деле во всех заданиях групп Exam все исходные данные хранятся в специальном входном текстовом файле,
а все результаты должны записываться в специальный выходной текстовый файл. Однако при этом не требуется
выполнять особых действий, связанных с определением имен этих файлов, связыванием файлов с файловыми
переменными, открытием и закрытием файлов (все эти действия выполняются задачником автоматически).
Для программы, выполняющей задание, эти файлы играют роль <i>стандартных потоков ввода-вывода</i>,
поэтому для доступа к ним достаточно использовать обычные процедуры ввода-вывода языка Pascal.
</p>
<hr>
<h3>Ввод исходных данных и их обработка
</h3>
<p>Приступим к выполнению задания. В данном случае следует использовать алгоритм, не требующий
применения массивов, поэтому опишем три простые переменные вещественного типа и введем в них исходные данные:
</p>
<blockquote><code><b>uses</b> PT4Exam;
<b><br><br>begin</b> <br>&nbsp;
Task('ExamBegin2');
<br>&nbsp;
<b>var</b>
(a,b,c) := ReadReal3;
<b><br>end</b>.
</code></blockquote>
<p>Мы воспользовались стандартной функцией ввода <code>ReadReal3</code>, введя все три исходных числа за один ее вызов.
</p>
<p>При запуске приведенного выше варианта программы вид окна не изменится, поскольку мы не вывели никаких данных.
В заданиях групп Exam запуск программы считается ознакомительным до тех пор, пока программа не выведет
хотя бы один элемент результирующих данных. Кроме того, задачник не контролирует,
каким образом программа читает исходные данные (например, мы могли бы ввести всю исходную строку
в переменную типа <code>string</code>, а затем &laquo;разобрать&raquo; эту строку, выделив из нее три числа
и преобразовав их к типу <code>real</code>). Отмеченные особенности характерны именно для заданий групп Exam,
в которых для ввода данных не используются специальные средства задачника.
</p>
<p>Реализуем алгоритм нахождения минимального и максимального элемента. Для этого опишем еще две
переменные <code>min</code> и <code>max</code> типа <code>real</code> и добавим в конец программы следующие операторы:
</p>
<blockquote><code><b>if</b> a &lt; b <b>then</b>
<b><br>begin</b>
<br>&nbsp;
min := a;
<br>&nbsp; max := b;
<b><br>end</b>
<b><br>else</b>
<b><br>begin</b> <br>&nbsp;
min := b;
<br>&nbsp; max := a;
<b><br>end</b>;
<b><br>if</b> c &lt; min <b>then</b> <br>&nbsp;
min := c
<b><br>else</b>
<b>if</b> c &gt; max <b>then</b> <br>&nbsp;
max := c;
</code></blockquote>
<p>Таким образом, для одновременного нахождения минимального и максимального из трех чисел требуется
не более трех операций сравнения и не более трех операций присваивания.
</p>
<hr>
<h3>Вывод результатов и их форматирование
</h3>
<p>Осталось вывести полученные результаты. Вывод, как и ввод, следует выполнять с помощью стандартных процедур
языка Pascal, учитывая их особенности.
</p>
<p>Вначале, в качестве примера, организуем вывод, не соответствующий условиям задачи. Для этого добавим в конец программы
следующий оператор:
</p>
<blockquote><code>Println(min, max); </code></blockquote>
<p>Приведем вид окна задачника при запуске полученной программы:
</p>
<blockquote>
<img border="0" src="gif/ExamBegin2-2.png">
</blockquote>
<p>Мы видим, что минимум и максимум найдены правильно, однако выведены не в том формате,
который требовался (требуемый формат приводится в разделе с примером верного решения).
Мы допустили при выводе три ошибки: во-первых, вывели оба числа в одной строке
(при этом они &laquo;слились&raquo;, поскольку мы не предусмотрели вывод пробела-разделителя), во-вторых,
не снабдили числа комментариями и, в-третьих, не настроили их отображение в формате
с двумя дробными знаками (первое число было выведено с единственным знаком после точки).
</p>
<p>
<b>Примечание.</b> Следует обратить внимание на панель индикаторов, которая отображается между информационной панелью
и разделом с формулировкой задания в случае, если запуск программы не является
ни демонстрационным, ни ознакомительным. Обычно на этой панели выводятся три индикатора: первый указывает
количество введенных исходных данных, второй &#151; количество выведенных результатов, а третий &#151;
количество успешно пройденных тестовых испытаний. При выполнении заданий, связанных с ЕГЭ, первые два
индикатора являются неактивными, поскольку, как было отмечено выше, для получения исходных данных
и записи результатов не используются средства задачника, и поэтому он не в состоянии
проконтролировать каждую операцию ввода-вывода.
</p>
<p>Для исправления первых двух ошибок достаточно изменить вывод следующим образом:
</p>
<blockquote><code>Println('MIN=', min); <br>Println('MAX=', max); </code></blockquote>
<p>Однако в этом случае числа по-прежнему могут содержать неверное число дробных знаков.
Для исправления этой последней ошибки проще всего использовать
<i>атрибуты форматирования</i>, начинающиеся с символа &laquo;:&raquo; (двоеточие):
</p>
<blockquote><code>Println('MIN=', min:0:2); <br>Println('MAX=', max:0:2); </code></blockquote>
<p>Первый атрибут определяет <i>ширину поля вывода</i> (если ширина равна 0, то используется минимально
необходимое поле вывода). Наличие второго атрибута (допустимого только для вещественных чисел) означает,
что число надо вывести в формате с фиксированной точкой, причем его значение равно количеству дробных знаков.
</p>
<p>При запуске исправленной программы будет выведено сообщение о верном решении, а после трех
запусков &#151; сообщение о том, что задание выполнено:
</p>
<blockquote>
<img border="0" src="gif/ExamBegin2-3.png">
</blockquote>
<p>В случае успешного прохождения тестового испытания в окно задачника не включается
раздел с примером верного решения, поскольку данные этого раздела совпадают
с результатами, полученными программой.
</p>
<p>
<b>Примечание.</b>
В системе PascalABC.NET,
благодаря специальному механизму перенаправления данных, стандартные процедуры <code>read-write</code> можно использовать
и при выполнении заданий из других групп, однако важно учитывать ряд особенностей использования этих процедур
в заданиях групп Exam. Во-первых, только в заданиях групп Exam будут проявляться различия в использовании
процедур <code>read-write</code> и <code>readln-writeln</code> (в остальных группах для ввода-вывода можно использовать как вариант
процедуры с суффиксом &laquo;ln&raquo;, так и вариант без этого суффикса). Во-вторых, только в заданиях
групп Exam можно при необходимости <i>использовать атрибуты форматирования</i> при выводе результатов,
а также <i>выводить дополнительные комментарии</i>, если этого требует условие задачи. В-третьих,
только в заданиях групп Exam можно вводить и выводить элементы данных несколькими способами, с использованием переменных
различных типов; например, исходную строку можно либо сразу прочесть в строковую переменную, либо вводить
посимвольно в цикле (в других группах заданий проводится более строгая проверка на соответствие типа переменной
типу элемента исходных или результирующих данных).
</p>
<hr>
<h2>Пример 2. Ввод и вывод массивов
</h2>
<hr>
<p>Рассмотрим еще одно задание группы ExamBegin, особенностью которого является вывод в качестве результата
элементов двумерного массива.
</p>
<p class="task">ExamBegin28&#176;. На вход в первой строке подаются два целых положительных числа <i>M</i> и <i>N</i>,
во второй строке &#8212; вещественное число <i>D</i>, а в третьей строке &#8212; набор из <i>M</i> вещественных чисел.
Сформировать и вывести двумерный вещественный массив размера <i>M</i>&nbsp;&#215;&nbsp;<i>N</i>, у которого первый
столбец совпадает с исходным набором чисел, а элементы каждого следующего столбца равны сумме соответствующего
элемента предыдущего столбца и числа <i>D</i> (в результате каждая строка массива будет содержать
элементы <i>арифметической прогрессии</i>). Каждую строку элементов массива выводить на новой экранной строке,
для каждого числа отводить 7 экранных позиций.
</p>
<p>При запуске программы-заготовки, созданной для этого задания, окно задачника примет следующий вид:
</p>
<blockquote>
<img border="0" src="gif/ExamBegin28-1.png">
</blockquote>
<p>Анализируя исходные данные, можно заметить, что полученная матрица должна иметь 8 строк, тогда как
на экране отображаются только первые пять.
Это связано с тем, что по умолчанию используется режим &laquo;свернутого&raquo; отображения данных, при котором
на экране выводится только несколько начальных строк. Признаком того, что имеются данные,
не выведенные на экране, является кнопка, которая отображается в правом верхнем углу раздела исходных данных
(на этой кнопке изображается стилизованная стрелка, направленная вниз).
Для вывода всех данных достаточно нажать эту кнопку; можно также нажать клавишу <b>Ins</b> или выполнить
щелчок мышью в любом месте раздела с данными задания (кроме раздела, содержащего формулировку).
Если выполнить эти действия для нашего окна, то оно изменится следующим образом:</p>
<blockquote>
<img border="0" src="gif/ExamBegin28-2.png">
</blockquote>
<p>Повторный щелчок мышью, нажатие клавиши <b>Ins</b> или кнопки (на которой в данной ситуации будет отображаться
стрелка, направленная вверх &#151; см. рисунок) восстанавливает &laquo;сокращенное&raquo;
отображение данных. Заметим, что в режиме &laquo;сокращенного&raquo; отображения нумеруется только первая строка данных,
а в режиме полного отображения нумерацией снабжаются все строки.</p>
<p>
<a href="#scroll">Дополнительные возможности</a>, связанные с просмотром
данных большого размера, будут описаны далее, в пункте, посвященном задачам повышенной сложности.
</p>
<p>Если закрыть окно задачника, находясь в режиме отображения всех данных,
то при последующих запусках программы окно будет сразу отображаться в этом режиме.
</p>
<p>Приведем вариант правильного решения данной задачи (в этом варианте учитывается, что результирующий
двумерный массив имеет не более 10 строк и столбцов; соответствующее условие приведено в
преамбуле к описанию
группы ExamBegin):
</p>
<blockquote><code><pre><b>uses</b> PT4Exam;
<b>var</b>
m, n, i, j: integer;
d: real;
a: array[1..10, 1..10] of real;
<b>begin</b>
Task('ExamBegin28');
readln(m, n, d);
<b>for</b> i := 1 <b>to</b> m <b>do</b>
read(a[i, 1]);
<b>for</b> j := 2 <b>to</b> n <b>do</b>
<b>for</b> i := 1 <b>to</b> m <b>do</b>
a[i, j] := a[i, j - 1] + d;
<b>for</b> i := 1 <b>to</b> m <b>do</b>
<b>begin</b>
<b>for</b> j := 1 <b>to</b> n <b>do</b>
write(a[i, j]:7:2);
writeln;
<b>end</b>;
<b>end</b>.
</pre></code></blockquote>
<p>В приведенном решении следует обратить особое внимание на организацию ввода-вывода.
Укажем две особенности, связанные с вводом. Во-первых, несмотря
на то что по условию число <code>d</code> находится во второй строке, мы смогли включить его в один список с
предшествующими числами <code>m</code> и <code>n</code> (поскольку при чтении числовых данных переход на новую строку выполняется
автоматически). Во-вторых, при чтении элементов исходного набора <i>необходимо</i> использовать вариант
процедуры <code>read</code> без суффикса &laquo;ln&raquo;, чтобы не пропустить оставшиеся в этой строке числа.
</p>
<p>При выводе полученного двумерного массива необходимо обеспечить его правильное <i>форматирование</i>:
каждый элемент должен выводиться на семи экранных позициях с двумя дробными знаками и, кроме того,
каждая строка массива должна выводиться на новой экранной строке. Это достигается за счет использования
соответствующих атрибутов форматирования и явного перехода на новую строку с помощью процедуры
<code>writeln</code> без параметров.
</p>
<p>
<b>Примечание.</b>
Заметим, что в заданиях группы Matrix,
также посвященной обработке двумерных массивов, специальные действия по форматированию полученных массивов
выполнять не требуется, так как задачник автоматически форматирует все полученные результаты.
Таким образом, задания группы ExamBegin &laquo;более приближены&raquo; к реальной экзаменационной ситуации,
в которой программа учащегося должна не только обрабатывать исходные данные, но и обеспечивать наглядное
отображение результатов.
</p>
<hr>
<a name="taskc"></a>
<h2>Пример 3. Обработка сложных наборов данных
</h2>
<hr>
<p>Группа ExamTaskC содержит 100 типовых заданий, аналогичных заданиям,
которые предлагаются на ЕГЭ по информатике
в качестве задач повышенной сложности (задача C4). Основную часть данной группы составляют
задания на обработку сложных наборов данных (записей) с элементами-полями различных типов.
В подобных заданиях требуется правильно выбрать способ хранения данных и организовать их эффективную обработку;
при этом обычно требуется применить <i>несколько</i> базовых алгоритмов, например, алгоритм суммирования
или нахождения минимума/максимума и алгоритм поиска нужного элемента или сортировки набора данных по требуемому ключу.
В группу ExamTaskC включены также задания повышенной сложности на обработку текстовых данных
(подобные задания содержатся в завершающем разделе данной группы).
</p>
<p>Следует заметить, что возможность автоматической генерации
больших наборов исходных данных, предоставляемая задачником Programming Taskbook, позволяет существенно ускорить
тестирование учебных программ и сделать его более надежным, что, в свою очередь, повышает эффективность изучения
типовых приемов решения задач группы C.
</p>
<p>В заданиях группы ExamTaskC ввод и вывод имеет те же особенности,
что и в заданиях группы ExamBegin.
</p>
<p>Рассмотрим следующее задание.
</p>
<p class="task">ExamTaskC25&#176;. На вход подаются сведения об абитуриентах. В первой строке
указывается количество абитуриентов&nbsp;<i>N</i>, каждая из последующих <i>N</i>&nbsp;строк имеет
формат<br> <tt>&lt;Номер школы&gt; &lt;Год поступления&gt; &lt;Фамилия&gt;</tt><br>
Номер школы содержит не более двух цифр, годы лежат в диапазоне от 1990 до 2010. Для каждого года,
присутствующего в исходных данных, вывести общее число абитуриентов, поступивших в этом году
(вначале выводить год, затем число абитуриентов). Сведения о каждом годе выводить на новой строке
и упорядочивать по возрастанию номера года.</p>
<p>Программа-заготовка, созданная для этого задания, подобно заготовкам для заданий группы ExamBegin,
будет использовать специальный модуль <code>PT4Exam</code>:
</p>
<blockquote><code><pre><b>uses</b> PT4Exam;
<b>begin</b>
Task('ExamTaskC25');
<b>end</b>.
</pre></code></blockquote>
<p>При запуске этой программы на экране появится окно задачника, содержащее следующие данные:
</p>
<blockquote>
<img border="0" src="gif/ExamTaskC25-1.png">
</blockquote>
<p>Окно будет иметь такой вид, если при его предшествующем закрытии оно находилось в режиме
отображения всех данных.
Для отображения всех данных на экране может потребоваться увеличить высоту окна; для этого достаточно
зацепить мышью заголовок окна и переместить его вверх (для перемещения заголовка окна задачника вверх и вниз
можно также воспользоваться клавиатурными комбинациями <b>Ctrl+Up</b> и <b>Ctrl+Down</b>).
</p>
<p>При первом тестовом испытании программы ей будет предложен для обработки
набор данных не слишком большого размера (порядка 10&#150;20 элементов).
</p>
<p>Вначале следует определиться со структурами данных, которые будут использоваться в программе. Поскольку
требуется найти одну характеристику для каждого года, а число лет невелико, можно использовать
числовой массив <code>year</code>, каждый элемент которого соответствует определенному году. Так как в языке Pascal можно
использовать произвольные границы индексов, удобно в качестве диапазона индексов указать диапазон лет,
который требуется проанализировать: <code>1990..2010</code>. В начале программы выполним инициализацию элементов массива,
положив их значения равными <code>0</code>
(заметим, что если после обработки исходных данных некоторые элементы массива <code>year</code>
останутся нулевыми, то это будет означать, что соответствующие годы не были представлены в наборе исходных данных,
и выводить информацию о них не следует).
</p>
<p>После инициализации массива следует прочесть информацию о количестве абитуриентов и организовать цикл,
в котором будут обрабатываться данные о каждом абитуриенте и соответствующим образом корректироваться
элементы массива <code>year</code>. В дальнейшем сведения об уже обработанном абитуриенте нам не будут нужны, поэтому сохранять
их в специальном наборе данных (например, массиве) не требуется. Заметим также, что фамилия абитуриента
для решения задачи не требуется, поэтому после чтения двух числовых данных можно сразу переходить к новой строке,
пропуская строковый элемент данных (фамилию). В задаче не нужно использовать
и номера школ, однако их придется считывать, так как только после номера школы указывается интересующий
нас год поступления абитуриента.
</p>
<p>Когда данные обо всех абитуриентах будут обработаны,
в массиве <code>year</code> будет содержаться вся необходимая информация, которую останется
вывести в формате, указанном в условии задачи.
</p>
<p>Приведем первый вариант решения (этот вариант содержит одну ошибку):
</p>
<blockquote><code><pre><b>uses</b> PT4Exam;
<b>var</b>
n, i, k, m: integer;
year: array[1990..2010] of integer;
<b>begin</b>
Task('ExamTaskC25');
<b>for</b> i := 1990 <b>to</b> 2010 <b>do</b>
year[i] := 0;
readln(n);
<b>for</b> i := 1 <b>to</b> n <b>do</b>
<b>begin</b>
readln(k, m); <i>{ k - номер школы, m - год поступления }</i>
Inc(year[m]);
<b>end</b>;
<b>for</b> i := 1990 <b>to</b> 2010 <b>do</b>
writeln(i, ' ', year[i]);
<b>end</b>.
</pre></code></blockquote>
<p>Ошибка связана с тем, что на экран выводится информация о годах, отсутствующих в наборе исходных данных.
Поэтому она сразу будет выявлена при обработке наборов данных небольшого размера,
предлагаемых программе при первом тестовом запуске
(для большей наглядности приведем окно задачника в режиме сокращенного отображения данных, при котором
выводятся только пять первых элементов из каждого набора данных):
</p>
<blockquote>
<img border="0" src="gif/ExamTaskC25-2.png">
</blockquote>
<p>Для исправления ошибки достаточно добавить в последний цикл условный оператор:
</p>
<blockquote><code><pre><b>for</b> i := 1990 <b>to</b> 2010 <b>do</b>
<b>if</b> year[i] > 0 <b>then</b>
writeln(i, ' ', year[i]);
</pre></code></blockquote>
<p>Теперь все 9 тестовых испытаний программы, требуемых для того, чтобы решение было зачтено как выполненное,
будут пройдены успешно.
</p>
<a name="scroll"></a><p>Завершая рассмотрение этого задания, опишем некоторые дополнительные возможности, связанные
с просмотром больших наборов данных.
</p>
<p>Начиная со второго испытания,
программе может быть предложен для обработки набор исходных данных большего размера (порядка 50&#150;100
элементов). При этом уже не удастся отобразить на экране все данные, связанные с заданием.
В подобной ситуации у правой границы окна задачника появится <i>полоса прокрутки</i>, позволяющая
перемещаться к той части данных, которая первоначально не отображается на экране.
Прокрутку данных можно выполнять не только с помощью полосы прокрутки, но и используя
клавиши со стрелками, <b>PgUp</b>, <b>PgDn</b>, <b>Home</b>, <b>End</b>, а также колесико мыши.
</p>
<p>Помимо стандартных действий по прокрутке данных, в окне задачника предусмотрены возможности
&laquo;интеллектуальной&raquo; прокрутки,
позволяющие быстро перейти к началу каждого раздела задания, а также сравнить соответствующие фрагменты
полученных результатов и примера верного решения. Для циклического перебора разделов сверху вниз
предназначена клавиша <b>[+]</b> (а также комбинация <b>Ctrl+PgDn</b>), для циклического перебора разделов снизу вверх
&#151; клавиша <b>[&#150;]</b> (а также комбинация <b>Ctrl+PgUp</b>). Для быстрого переключения между
соответствующими фрагментами разделов с результатами и с примером верного решения предназначена клавиша <b>[/]</b>
(а также комбинация <b>Ctrl+Tab</b>). Все эти действия можно выполнить и с помощью мыши; для этого
предусмотрены кнопки в левом верхнем углу прокручиваемой области окна, отведенной под отображение разделов задания
(эти кнопки отображаются
на экране, если размер данных, связанных с заданием, превышает размеры окна).
Приведем вид окна задачника с полосой прокрутки и дополнительными кнопками:
</p>
<blockquote>
<img border="0" src="gif/ExamTaskC25-3.png">
</blockquote>
<p>Обозначения на кнопках совпадают с клавишами,
выполняющими те же действия; при наведении мышью на кнопку рядом с ней появляется всплывающая подсказка.
</p>
<p>Кроме трех кнопок, связанных с &laquo;интеллектуальной&raquo; прокруткой,
в приведенном на рисунке окне отображаются еще две дополнительные кнопки.
Первая из них располагается в правом верхнем углу раздела с формулировкой
и позволяет временно скрыть (а в дальнейшем опять отобразить)
раздел с формулировкой (эти же действия можно выполнить с помощью клавиши <b>Del</b> или щелчка мышью
на разделе с формулировкой).
Вторая дополнительная кнопка расположена в правом верхнем углу раздела с исходными данными.
Как уже отмечалось ранее, эта кнопка позволяет переключаться между полным
и сокращенным отображением наборов данных. Напомним, что
изображение на этой кнопке показывает текущий режим отображения данных. Например, если на кнопке изображена
стилизованная стрелка, направленная вверх (как на приведенном выше рисунке),
значит, в данный момент в окне отображаются все данные, а нажатие на эту кнопку переведет окно
в режим отображения нескольких начальных (как правило, пяти) элементов каждого набора данных.
</p>
<p>Дополнительная информация о возможностях
режима окна с динамической компоновкой приведена в <a href="../PT4/window.html#dynmode">соответствующем разделе</a>
страницы, посвященной описанию окна задачника.
</p>
<hr>
<h2>Пример 4. Более сложное задание на обработку наборов данных
</h2>
<hr>
<p class="task">ExamTaskC53&#176;. На вход подаются сведения о ценах на бензин на автозаправочных станциях (АЗС).
В первой строке содержится значение&nbsp;<i>M</i> одной из марок бензина, во второй строке указывается
целое число&nbsp;<i>N</i>, а каждая из последующих <i>N</i>&nbsp;строк имеет формат<br>
<tt>&lt;Марка бензина&gt; &lt;Улица&gt; &lt;Компания&gt; &lt;Цена 1 литра (в копейках)&gt;</tt><br>
Имеется не более 20 различных компаний и не более 30 различных улиц; названия компаний и улиц
не содержат пробелов. В качестве марки бензина указываются числа 92, 95 или 98. Цена задается целым числом
в диапазоне от&nbsp;2000 до&nbsp;3000. Каждая компания имеет не более одной АЗС на каждой улице;
цены на разных АЗС одной и той же компании могут различаться. Для каждой улицы, на которой имеются
АЗС с бензином марки&nbsp;<i>M</i>, определить максимальную цену бензина этой марки (вначале выводить максимальную
цену, затем название улицы). Сведения о каждой улице выводить на новой строке и упорядочивать
по возрастанию максимальной цены, а для одинаковой цены &#8212; по названиям улиц в алфавитном порядке.
Если ни одной АЗС с бензином марки&nbsp;<i>M</i> не найдено, то вывести текст &#171;Нет&#187;.
</p>
<p>Приведем окно задачника, которое появится на экране при запуске программы-заготовки для данного задания
(в данном окне скрыт раздел с формулировкой; в результате оказались скрытыми и кнопки,
отвечающие за &laquo;интеллектуальную&raquo; прокрутку, поскольку в окне полностью отображается
содержимое оставшихся разделов):
</p>
<blockquote>
<img border="0" src="gif/ExamTaskC53-1.png">
</blockquote>
<p>Выясним, какая структура является наиболее подходящей для хранения информации, необходимой для решения задачи.
Нам требуется информация, связанная с различными улицами, которых по условию не более 30, причем для каждой улицы
надо хранить сведения двух видов: ее название и максимальную цену бензина марки <i>M</i>. Поэтому мы можем либо завести
массив из 30 элементов-записей с двумя полями, либо два массива: один содержащий названия улиц, а другой &#151;
максимальные цены. Учитывая, что в конце программы нам потребуется выполнять сортировку полученных данных,
целесообразнее использовать массив записей, поскольку это позволит записать алгоритм сортировки
в более компактной форме.
</p>
<p>Определим запись <code>Street</code> с двумя полями <code>name</code> и <code>max</code> и опишем массив <code>s</code>
из 30 элементов типа <code>Street</code>.
Следует также завести переменную <code>ns</code>, в которой будет храниться количество заполненных элементов массива <code>s</code>.
</p>
<p>При обработке каждой строки с исходными данными нам будут нужны прежде всего сведения о марке бензина.
Если марка бензина не равна <code>M</code>, то оставшуюся часть строки обрабатывать не требуется, и можно сразу перейти
к разбору следующей строки. Если марка бензина равна <code>M</code>, то необходимо узнать название улицы <code>s0</code> и цену бензина <code>p</code>.
Заметим, что название компании для решения задачи не требуется, однако его необходимо прочесть, чтобы определить
следующий элемент данных &#151; цену бензина.
</p>
<p>Если улица с названием <code>s0</code> еще не была включена в массив <code>s</code>, то ее необходимо включить в массив, присвоив
полю <code>max</code> значение <code>p</code>. Если же улица уже присутствует в массиве, то необходимо сравнить поле <code>max</code> для данной улицы
и значение <code>p</code>, изменив при необходимости поле <code>max</code> (здесь мы используем базовый алгоритм нахождения
максимального значения).
</p>
<p>Для ввода названий улиц и компаний в нашем случае удобно организовать <i>посимвольное</i> чтение строковых данных;
признаком завершения такого чтения будет обнаружение пробельного символа.
</p>
<p>После обработки набора исходных данных необходимо проверить, найдена ли хотя бы одна улица с АЗС,
предлагающей марку бензина <code>M</code> (для этого достаточно сравнить значение <code>ns</code> с нулем). Если ни одна улица
не найдена, то надо вывести строку &laquo;Нет&raquo;; в противном случае требуется выполнить сортировку
массива <code>s</code> по указанному набору ключей и вывести полученные данные в требуемом порядке. Поскольку размер
массива невелик, для его сортировки вполне допустимо использовать один из простых алгоритмов,
например, алгоритм <i>пузырьковой сортировки</i>.
</p>
<p>Приведем один из вариантов правильного решения задачи:
</p>
<blockquote><code><pre><b>uses</b> PT4Exam;
<b>type</b>
Street = <b>record</b>
name: string;
max: integer;
<b>end</b>;
<b>var</b>
m, n, ns, i, j, k, p: integer;
s: array[1..30] of Street;
s0: string;
x: Street;
c: char;
<b>begin</b>
Task('ExamTaskC53');
readln(m); <i>{ m - марка бензина }</i>
readln(n);
ns := 0;
<b>for</b> i := 1 <b>to</b> n <b>do</b>
<b>begin</b>
read(k);
<b>if</b> k &lt;&gt; m <b>then</b>
readln <i>{ пропускаем оставшуюся часть строки }</i>
<b>else</b>
<b>begin</b>
s0 := '';
read(c); <i>{ пропускаем пробел после первого числа }</i>
read(c); <i>{ читаем первый символ названия улицы }</i>
<b>while</b> c &lt;&gt; ' ' <b>do</b>
<b>begin</b>
s0 := s0 + c;
read(c);
<b>end</b>;
read(c); <i>{ читаем первый символ названия компании }</i>
<b>while</b> c &lt;&gt; ' ' <b>do</b>
read(c); <i>{ название компании не сохраняем }</i>
readln(p); <i>{ читаем цену бензина и переходим на новую строку }</i>
<i>{ Обработка прочитанной информации }</i>
k := 0;
<b>for</b> j := 1 <b>to</b> ns <b>do</b>
<b>if</b> s[j].name = s0 <b>then</b> <i>{ улица уже содержится в массиве s }</i>
<b>begin</b>
k := 1;
<b>if</b> s[j].max &lt; p <b>then</b>
s[j].max := p;
break;
<b>end</b>;
<b>if</b> k = 0 <b>then</b> <i>{ улица еще не содержится в массиве s }</i>
<b>begin</b>
Inc(ns);
s[ns].name := s0;
s[ns].max := p;
<b>end</b>;
<b>end</b>;
<b>end</b>;
<b>if</b> ns = 0 <b>then</b> <i>{ ни одной улицы не найдено }</i>
writeln('Нет')
<b>else</b>
<b>begin</b>
<i>{ Сортировка по возрастанию максимальной цены,</i>
<i> а для одинаковых цен - по названиям улиц }</i>
<b>for</b> k := 1 <b>to</b> ns - 1 <b>do</b>
<b>for</b> i := 1 <b>to</b> ns - k <b>do</b>
<b>if</b> (s[i].max &gt; s[i + 1].max) <b>or</b>
(s[i].max = s[i + 1].max) <b>and</b>
(s[i].name &gt; s[i + 1].name) <b>then</b>
<b>begin</b>
x := s[i];
s[i] := s[i + 1];
s[i + 1] := x;
<b>end</b>;
<i>{ Вывод результатов в требуемом порядке }</i>
<b>for</b> i := 1 <b>to</b> ns <b>do</b>
writeln(s[i].max,' ',s[i].name);
<b>end</b>;
<b>end</b>.
</pre></code></blockquote>

View file

@ -1,343 +0,0 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title></title>
<link rel="StyleSheet" href="../../default.css">
</head>
<body>
<H1>Задание на обработку файлов</H1>
<hr>
<h3>Создание программы-заготовки и знакомство с заданием</h3>
<p>В качестве примера задания на обработку файлов рассмотрим задание File48.</p>
<p>Напомним, что программу-заготовку для решения этого задания можно создать с помощью
команды
меню &laquo;Модули | Создать шаблон программы&raquo;, кнопки
<img border="0" src="gif/Load.gif" width="16" height="17"> или клавиатурной
комбинации <b>Shift+Ctrl+L</b>. Эта заготовка будет иметь следующий вид:
</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>begin</b>
Task('File48');
<b>end</b>.</pre></code>
</blockquote>
<p>После запуска данной программы на экране появится <a href="../PT4/window.html">окно задачника</a>:</p>
<blockquote>
<img border="0" src="gif/file48-1.png">
</blockquote>
<p>В первой строке раздела исходных данных указаны имена трех исходных файлов (S<sub>A</sub>, S<sub>B</sub>
и S<sub>C</sub>) и одного результирующего (S<sub>D</sub>). В последующих строках раздела исходных данных
показано содержимое исходных файлов.
Элементы файлов отображаются бирюзовым цветом, чтобы подчеркнуть их отличие от обычных исходных данных (желтого цвета)
и комментариев (светло-серого цвета).</p>
<p>Поскольку размер файлов, как правило, превышает количество элементов,
которое может уместиться на одной экранной строке,
для отображения содержимого файла может отводиться
более одной экранной строки. Слева от каждой строки с содержимым файла указывается порядковый номер
файлового элемента, значение которого указано первым в этой строке (элементы нумеруются от 1).</p>
<p>Запуск нашей программы признан <i>ознакомительным</i> (и поэтому правильность решения не анализировалась),
так как в ходе ее выполнения не было введено ни одного элемента исходных данных.
При ознакомительном запуске раздел результатов не отображается, однако приводится
<i>пример верного решения</i>, т.&nbsp;е. те числа,
которые должны содержаться в результирующем файле при правильной обработке исходных файлов.</p>
</p>
<hr>
<h3>Ввод части исходных данных</h3>
<p>Добавим в программу фрагмент, позволяющий ввести имена исходных файлов и связать с этими файлами соответствующие
файловые переменные. Поскольку мы собираемся работать с четырьмя файлами одного типа, удобно предусмотреть <i>массив</i>
для хранения всех файловых переменных:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
i: integer;
s: string;
f: <b>array </b>[1..4] <b>of</b> <b>file</b> <b>of</b> integer;
<b>begin</b>
Task('File48');
<b>for</b> i := 1 <b>to</b> 3 <b>do</b>
<b>begin</b>
read(s);
Assign(f[i], s);
<b>end</b>;
<b>end</b>. </pre></code>
</blockquote>
<p>Мы намеренно ограничились <i>тремя</i> итерациями цикла, оставив непрочитанным имя результирующего файла.
Считывание имен файлов производится в одну и ту же переменную <code>s</code>, поскольку после связывания файла,
имеющего имя <code>s</code>,
с соответствующей файловой переменной (процедурой <code>Assign</code>) все остальные действия
с данным файлом в нашей программе
будут осуществляться с использованием файловой переменной, без обращения к имени файла.</p>
<p>Запуск нового варианта программы уже не будет считаться ознакомительным, поскольку в программе
выполняется ввод исходных данных. Так как имя результирующего файла осталось непрочитанным, этот вариант
решения будет признан неверным и приведет к сообщению &laquo;<i>Введены не все требуемые исходные данные</i>&raquo;:</p>
<blockquote>
<img border="0" src="gif/file48-2.png">
</blockquote>
<p>При этом на экране появится раздел результатов (кроме комментария он пока ничего не содержит),
а также панель индикаторов. Первый из индикаторов
(индикатор ввода) показывает
количество введенных исходных данных. Обратите внимание на то, что второй индикатор
(индикатор вывода) является неактивным: он выделяется серым цветом более светлого
оттенка и не содержит текстового заголовка.
Это объясняется тем, что индикатор вывода показывает количество результирующих данных,
полученных задачником от программы, а в нашем случае программа
не должна передавать задачнику никакие данные; вместо этого ей необходимо создать файл и заполнить его
требуемыми значениями. Для заданий подобного типа (обычно это задания, связанные с обработкой файлов)
индикатор вывода не используется.
</p>
<hr>
<h3>Ввод всех исходных данных без создания требуемого файла</h3>
<p>Изменим программу, заменив в заголовке цикла число <code>3</code> на <code>4</code>, и вновь запустим программу. Теперь все данные,
необходимые для выполнения задания, введены в программу (это видно по индикатору ввода). Однако задание не выполнено, поскольку результирующий файл
не создан. Поэтому решение опять признано ошибочным с диагностикой &laquo;<i>Результирующий файл не найден</i>&raquo;:</p>
<blockquote>
<img border="0" src="gif/file48-3.png">
</blockquote>
<hr>
<h3>Пример программы, приводящей к ошибке времени выполнения</h3>
<p>Добавим в тело цикла после процедуры <code>Assign</code> вызов процедуры <code>Reset</code>, обеспечивающий открытие существующего файла:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
i: integer;
s: string;
f: <b>array </b>[1..4] <b>of</b> <b>file</b> <b>of</b> integer;
<b>begin</b>
Task('File48');
<b>for</b> i := 1 <b>to</b> 4 <b>do</b>
<b>begin</b>
read(s);
Assign(f[i], s);
Reset(f[i]);
<b>end</b>;
<b>end</b>. </pre></code>
</blockquote>
<p>Теперь запуск программы приведет к сообщению об ошибке &laquo;<i>Error System.IO.FileNotFoundException</i>&raquo;:</p>
<blockquote>
<img border="0" src="gif/file48-4.png">
</blockquote>
<p>Сообщение, начинающееся со слова <i>Error</i>, означает, что при работе программы произошла <i>ошибка времени выполнения</i>
(Runtime Error). После слова <i>Error</i> указывается имя этой ошибки (в данном случае <i>System.IO.FileNotFoundException</i>,
то есть ошибка ввода-вывода, связанная с тем, что файл не найден)
и краткое ее описание на английском языке.</p>
<p>Сообщение об ошибке времени выполнения появится и в разделе &laquo;Список ошибок&raquo;
окна <b>PascalABC.NET</b>:</p>
<blockquote>
<img border="0" src="gif/file48-5.png">
</blockquote>
<hr>
<h3>Создание пустого результирующего файла</h3>
<p>Для того чтобы избежать ошибки времени выполнения, отсутствующий файл результатов следует открыть
не процедурой <code>Reset</code>, а процедурой <code>Rewrite</code>, которая
и обеспечит создание этого файла. Далее, после завершения работы с файлами, открытыми в программе,
их необходимо закрыть процедурой <code>Close</code>. Добавим в программу соответствующие операторы:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
i: integer;
s: string;
f: <b>array </b>[1..4] <b>of file of</b> integer;
<b>begin</b>
Task('File48');
<b>for</b> i := 1 <b>to</b> 4 <b>do</b>
<b>begin</b>
read(s);
Assign(f[i], s);
<b>if</b> i &lt; 4 <b>then</b> Reset(f[i])
<b>else</b> Rewrite(f[i]);
<b>end</b>;
{ * }
<b>for</b> i := 1 <b>to</b> 4 <b>do</b>
Close(f[i]);
<b>end</b>. </pre></code>
</blockquote>
<p>Комментарий <code>{ * }</code> расположен в том месте программы, в котором можно выполнять операции ввода-вывода для всех
четырех файлов: они уже открыты процедурами <code>Reset</code> или <code>Rewrite</code> и еще не закрыты процедурой <code>Close</code>.</p>
<p>Запуск этого варианта программы не приведет к ошибке времени выполнения; более того, результирующий файл будет создан.
Однако созданный файл останется пустым, то есть не содержащим ни одного элемента. Поэтому при запуске программы
на информационной панели появится сообщение &laquo;<i>Ошибочное решение</i>&raquo;,
а в строке, которая должна содержать элементы результирующего файла, появится текст <code>EOF:</code> (особое значение EOF для
указателя текущей файловой позиции означает, что данный файл существует, но не содержит ни одного элемента):</p>
<blockquote>
<img border="0" src="gif/file48-6.png">
</blockquote>
<hr>
<h3>Пример программы, использующей неправильные типы для файловых данных</h3>
<p>Во всех ранее рассмотренных вариантах программы мы не использовали операции ввода-вывода для файлов.
Поэтому тип файлов не играл никакой роли: вместо типа <code><b>file of</b> integer</code> мы могли использовать любой другой файловый тип,
например, <code><b>file of</b> real</code>, и результат выполнения программы был бы тем же самым.</p>
<p>Тип файловых элементов становится принципиально важным, если в программе используются операции ввода-вывода
для данного файла. Чтобы продемонстрировать это на примере нашей программы, внесем в нее следующие изменения:
в описании массива <code>f</code> файловых переменных тип <code>integer</code> заменим на <code>real</code>, в раздел описаний добавим описание
переменной <code>a</code> типа <code>real</code>, в раздел операторов (в позицию, помеченную комментарием <code>{ * }</code>)
добавим следующий фрагмент:</p>
<blockquote>
<code><pre>
<b>for</b> i := 1 <b>to</b> 3 <b>do</b>
<b>begin</b>
read(f[i], a);
write(f[4], a);
<b>end</b>;
</pre></code>
</blockquote>
<p>Данный фрагмент обеспечивает считывание <i>одного</i> элемента для каждого из трех исходных файлов
и запись этих элементов в результирующий файл (в требуемом порядке). Подчеркнем, что мы <i>неправильно</i> указали
типы файлов; тем не менее, компиляция программы пройдет успешно, а после ее запуска не произойдет
ошибок времени выполнения.</p>
<p>Результат работы программы будет неожиданным:</p>
<blockquote>
<img border="0" src="gif/file48-7.png">
</blockquote>
<p>Судя по экранной строке с содержимым результирующего файла, в него будут записаны не три, а <i>шесть элементов</i>,
по два начальных элемента из каждого исходного файла. Объясняется это тем, что после связывания файлов
с файловыми переменными типа <code><b>file of</b> real</code> элементами файлов стали считаться <i>вещественные числа</i>
(занимающие в памяти по 8 байтов), тогда как &laquo;на самом деле&raquo;, то есть по условию задания, элементами файлов
являются целые числа (занимающие в памяти по 4 байта). Поэтому считывание из файла и последующая запись в файл
одного &laquo;вещественного элемента&raquo; фактически приводит к считыванию и записи блока данных размером 8 байтов,
содержащего <i>два</i> последовательных целочисленных элемента исходного файла.</p>
<p>Итак, мы выяснили, что ошибки, связанные с несоответствием типов файлов, не выявляются при компиляции
и не всегда приводят к ошибкам времени выполнения. Это следует иметь в виду, и при появлении &laquo;странных&raquo;
результирующих данных начинать поиск ошибки с проверки типов файловых переменных.</p>
<hr>
<h3>Исправление ошибки, связанной с неверными типами файловых данных</h3>
<p>Заменим в нашей программе все описания <code>real</code> на <code>integer</code>:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
i: integer;
s: string;
f: <b>array </b>[1..4] <b>of file of</b> integer;
a: integer;
<b>begin</b>
Task('File48');
<b>for</b> i := 1 <b>to</b> 4 <b>do</b>
<b>begin</b>
read(s);
Assign(f[i], s);
<b>if</b> i &lt; 4 <b>then</b> Reset(f[i])
<b>else</b> Rewrite(f[i]);
<b>end</b>;
<b>for</b> i := 1 <b>to</b> 3 <b>do</b>
<b>begin</b>
read(f[i], a);
write(f[4], a);
<b>end</b>;
<b>for</b> i := 1 <b>to</b> 4 <b>do</b>
Close(f[i]);
<b>end</b>. </pre></code>
</blockquote>
<p>Мы получим все еще неверное, но вполне &laquo;понятное&raquo; решение: первые три элемента результирующего файла
совпадают с контрольными (то есть &laquo;правильными&raquo;), а прочие элементы отсутствуют:</p>
<blockquote>
<img border="0" src="gif/file48-8.png">
</blockquote>
<hr>
<h3>Верное решение</h3>
<p>Приведем, наконец, верное решение задания
<code>File48</code>:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
i, a: integer;
s: string;
f: <b>array </b>[1..4] <b>of file of</b> integer;
<b>begin</b>
Task('File48');
<b>for</b> i := 1 <b>to</b> 4 <b>do</b>
<b>begin</b>
read(s);
Assign(f[i], s);
<b>if</b> i &lt; 4 <b>then</b> Reset(f[i])
<b>else</b> Rewrite(f[i]);
<b>end</b>;
<b>while</b> <b>not</b> Eof(f[1]) <b>do</b>
<b>for</b> i := 1 <b>to</b> 3 <b>do</b>
<b>begin</b>
read(f[i], a);
write(f[4], a);
<b>end</b>;
<b>for</b> i := 1 <b>to</b> 4 <b>do</b>
Close(f[i]);
<b>end</b>. </pre></code>
</blockquote>
<p>От предыдущего варианта данное решение отличается добавлением заголовка цикла <code><b>while not</b> Eof(f[1]) <b>do</b></code>,
который обеспечивает считывание всех элементов из исходных файлов (напомним, что по условию задания все исходные
файлы имеют <i>одинаковый размер</i>) и запись их в результирующий файл в нужном порядке.
После запуска этого варианта мы получим сообщение &laquo;<i>Верное решение. Тест номер 1 (из 5)</i>&raquo;,
а после пяти подобных запусков &#151; сообщение &laquo;<i>Задание выполнено!</i>&raquo;:</p>
<blockquote>
<img border="0" src="gif/file48-9.png">
</blockquote>
<hr>
<h3>Просмотр результатов выполнения задания</h3>
<p>Щелкнув мышью на метке &laquo;Результаты (F2)&raquo;, расположенной в правом верхнем
углу окна задачника, или нажав клавишу <b>F2</b>, мы можем вывести на экран <i>окно результатов</i>,
в котором будет перечислены все наши попытки решения задачи:</p>
<blockquote>
<code><pre>
File48 a08/09 12:43 Ознакомительный запуск.
File48 a08/09 12:50 Введены не все требуемые исходные данные.
File48 a08/09 12:52 Результирующий файл не найден.
File48 a08/09 12:53 Error System.IO.FileNotFoundException.
File48 a08/09 12:57 Ошибочное решение.--3
File48 a08/09 13:06 Задание выполнено!
</pre></code>
</blockquote>
<p>Для закрытия окна результатов достаточно нажать клавишу <b>Esc</b>.
Окно результатов можно отобразить на экране и после закрытия окна задачника и возврата в среду PascalABC.NET.
Для этого надо использовать команду меню &laquo;Модули | Просмотреть результаты&raquo;, кнопку
<img border="0" src="gif/Results.gif" width="16" height="17"> или клавиатурную
комбинацию <b>Shift+Ctrl+R</b>.
</p>
</body>
</html>

View file

@ -1,535 +0,0 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title></title>
<link rel="StyleSheet" href="../../default.css">
</head>
<body>
<H1>Задания на указатели и динамические структуры данных</H1>
<hr>
<H2>Пример 1. Анализ существующей динамической структуры</H2>
<hr>
<p>В заданиях группы Dynamic мы встречаемся с двумя новыми видами данных: это <i>динамические структуры</i>,
реализованные в виде цепочек связанных друг
с другом записей типа <code>TNode</code>, и <i>указатели</i> типа <code>PNode</code> на записи <code>TNode</code>:
<code>PNode&nbsp;=&nbsp;^TNode</code>. Типы <code>TNode</code> и <code>PNode</code> не являются стандартными типами языка Паскаль;
они определены в задачнике Programming Taskbook следующим образом (приводятся только те поля записи <code>TNode</code>, которые
используются при выполнении заданий группы Dynamic):</p>
<blockquote>
<code><pre>
<b>type</b>
PNode = ^TNode;
TNode = <b>record</b>
Data: integer;
Next: PNode;
Prev: PNode;
. . .
<b>end</b>;
</pre></code>
</blockquote>
<p>На примере задания Dynamic2 рассмотрим особенности, связанные с
использованием этих новых типов данных.</p>
<hr>
<h3>Создание программы-заготовки и знакомство с заданием</h3>
<p>Программа-заготовка для задания Dynamic2, созданная с помощью команды
меню &laquo;Модули | Создать шаблон программы&raquo;, кнопки
<img border="0" src="gif/Load.gif" width="16" height="17"> или клавиатурной
комбинации <b>Shift+Ctrl+L</b>, имеет следующий вид:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>begin</b>
Task('Dynamic2');
<b>end</b>.
</pre></code>
</blockquote>
<p>После запуска данной программы на экране появится <a href="../PT4/window.html">окно задачника</a>:</p>
<blockquote>
<img border="0" src="gif/Dynamic2-1.png">
</blockquote>
<p>Это окно содержит в качестве исходных и результирующих данных новые элементы: динамические структуры и указатели.</p>
<p>Начнем с описания того, как отображается на экране <i>динамическая структура</i>. Для ее вывода используются две
экранные строки; в первой строке отображаются имена указателей, связанных с данной структурой,
а во второй &#151; содержимое элементов этой структуры, то есть значения их полей <code>Data</code> и способ связи
между ними. Вся информация о динамической структуре отображается бирюзовым цветом (подобно информации об элементах файлов).</p>
<p>Рассмотрим в качестве примера динамическую структуру, указанную на рисунке:</p>
<blockquote>
<code><pre>
P<sub>1</sub>
75 - 65 - 22 - 26 - 10 >nil
</pre></code>
</blockquote>
<p>Этот текст означает, что структура состоит из 5 элементов, причем ее первый элемент имеет поле <code>Data</code>,
равное 75, и связан с помощью своего поля <code>Next</code> со вторым элементом, поле <code>Data</code>
которого равно 65, и так далее до последнего, пятого элемента, поле <code>Data</code> которого равно 10,
а поле <code>Next</code> равно <code>nil</code>, что является признаком завершения структуры.
Таким образом, текст, описывающий данную динамическую структуру, является максимально упрощенным вариантом
следующей схемы:</p>
<blockquote>
<img border="0" src="gif/Dynamic2-2.png">
</blockquote>
<p>Поскольку эта структура указана в разделе исходных данных, следовательно, после инициализации задания
она <i>уже существует</i> и размещается в некоторой области динамической памяти (подобно тому, как исходные файлы
после инициализации задания размещаются в каталоге учащегося).</p>
<p>Как получить доступ к этой существующей динамической структуре? Здесь также уместна аналогия с файлами.
Для доступа к внешнему файлу необходимо знать его <i>имя</i>, и в любом задании на обработку файлов
имена исходных файлов входят в набор исходных данных. Для доступа к данным,
размещенным в динамической памяти, необходимо знать их <i>адрес</i>, и поэтому в любом задании на
обработку динамических структур в набор исходных данных входят <i>указатели</i>, содержащие адреса этих структур.</p>
<p>Из текста, описывающего динамическую структуру, видно, что на ее первый элемент указывает указатель
с именем <code>P<sub>1</sub></code>, который также содержится в наборе исходных данных. Описание этого указателя имеет вид</p>
<blockquote>
<code><pre>
P<sub>1</sub> = ptr
</pre></code>
</blockquote>
<p>Здесь текст <code>P<sub>1</sub> =</code> является <i>комментарием</i> и выделяется, как обычный комментарий,
светло-серым цветом, а текст <code>ptr</code> означает, что этот элемент исходных данных является <i>указателем</i>,
который надо ввести в программу с помощью процедуры ввода <code>read</code>.</p>
<p><b>Замечание.</b> Может возникнуть вопрос: почему вместо условного текста &laquo;ptr&raquo; не отображается &laquo;настоящее&raquo; значение указателя
(то есть некоторый четырехбайтный адрес)? Это связано с тем, что, даже выведя это значение на экран,
мы не сможем определить, с какими данными связан этот адрес, поэтому подобная информация на экране будет излишней.</p>
<p>Итак, слово <code>ptr</code> в разделе исходных или результирующих данных означает, что соответствующий элемент данных
является <i>указателем</i>, причем непустым (для пустого указателя используется слово <code><b>nil</b></code>).
Определить, с каким элементом динамической структуры данных связан непустой указатель, можно по экранной
информации об этой динамической структуре.
Разумеется, при чтении указателя программа учащегося получит &laquo;настоящий&raquo; адрес, с помощью которого она сможет
обратиться к исходной динамической структуре.</p>
<p>Аналогично, создав (или преобразовав) некоторую динамическую
структуру, программа учащегося должна передать задачнику некоторый адрес,
связанный в этой структурой (используя процедуру вывода <code>write</code>).
Зная этот адрес, задачник сможет проверить правильность созданной структуры.</p>
<hr>
<h3>Приступаем к решению</h3>
<p>Вернемся к заданию
Dynamic2. В нем не требуется ни создавать, ни преобразовывать исходную структуру данных;
ее необходимо лишь проанализировать,
а именно, определить значения всех ее элементов, подсчитать количество элементов и, кроме того,
вывести указатель на последний элемент этой структуры.</p>
<p>Приведем вначале неполное решение задачи, выводящее все необходимые данные, кроме указателя на последний элемент:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
p1: PNode;
n: integer;
<b>begin</b>
Task('Dynamic2');
read(p1);
n := 0;
<b>while</b> p1 &lt;&gt; nil <b>do</b>
<b>begin</b>
write(p1^.Data);
n := n + 1;
p1 := p1^.Next;
<b>end</b>;
write(n);
<b>end</b>.
</pre></code>
</blockquote>
<p>После запуска программы можно убедиться, что все числовые результирующие данные найдены правильно,
однако из-за того, что не выведен указатель
на последний элемент, решение признано ошибочным с диагностикой &laquo;<i>Выведены не все результирующие данные</i>&raquo;:</p>
<blockquote>
<img border="0" src="gif/Dynamic2-3.png">
</blockquote>
<p>Добавим в конец программы оператор</p>
<blockquote>
<code><pre>
write(p1);
</pre></code>
</blockquote>
<p>После запуска нового варианта программы все требуемые данные будут выведены, однако результирующее значение
указателя будет равно <code>nil</code>. Это связано
с тем, что после завершения цикла <code><b>while</b></code> в переменной <code>p1</code> содержится <i>нулевой указатель</i>,
а не указатель на последний элемент динамической структуры:</p>
<blockquote>
<img border="0" src="gif/Dynamic2-4.png">
</blockquote>
<hr>
<h3>Правильное решение</h3>
<p>Для того чтобы получить правильное решение, опишем вспомогательную переменную <code>p2</code>,
в которой будем сохранять адрес элемента, <i>предшествующего</i> элементу с адресом <code>p1</code>.
После завершения цикла <code><b>while</b></code> в этой переменной будет храниться адрес последнего элемента
динамической структуры:</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>var</b>
p1,p2: PNode;
n: integer;
<b>begin</b>
Task('Dynamic2');
read(p1);
n := 0;
<b>while</b> p1 &lt;&gt; nil <b>do</b>
<b>begin</b>
write(p1^.Data);
n := n + 1;
p2 := p1; { сохраняем адрес текущего элемента }
p1 := p1^.Next; { и переходим к следующему элементу }
<b>end</b>;
write(n, p2);
<b>end</b>.
</pre></code></blockquote>
<p>Запустив эту программу три раза, мы получим сообщение &laquo;<i>Задание выполнено!</i>&raquo;:</p>
<blockquote>
<img border="0" src="gif/Dynamic2-5.png">
</blockquote>
<hr>
<H2>Пример 2. Добавление элемента к динамической структуре</H2>
<hr>
<h3>Знакомство с заданием</h3>
<p>Рассмотрим простейшее задание, связанное с добавлением элемента к динамической структуре-стеку:
Dynamic3.</p>
<p>При ознакомительном запуске этого задания мы обнаружим новое обозначение в тексте, описывающем результирующий стек,
а именно, точки, обрамляющие первый элемент стека:</p>
<blockquote>
<img border="0" src="gif/Dynamic3-1.png">
</blockquote>
<p>Точки обозначают элементы динамической структуры, память для которых <i>должна быть выделена программой учащегося</i>
(в отличие от тех элементов, которые размещаются в памяти самим задачником).</p>
<hr>
<h3>Приступаем к решению</h3>
<p>Что произойдет, если динамическая структура будет создана с ошибками? Для того чтобы это выяснить, вернем в программе, решающей задание
Dynamic3, указатель на прежнюю вершину стека, не добавляя к ней новый элемент:</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>var</b>
d: integer;
p1: PNode;
<b>begin</b>
Task('Dynamic3');
read(d, p1);
write(p1);
<b>end</b>.
</pre></code></blockquote>
<p>После запуска данной программы окно задачника примет вид:</p>
<blockquote>
<img border="0" src="gif/Dynamic3-2.png">
</blockquote>
<p>Скобки вокруг каждого элемента результирующего стека означают, что эти элементы созданы самим задачником, но <i>располагаются
не на тех позициях, на которых они должны находиться при правильном решении</i>. Действительно, тот элемент,
который в решении является первым, должен (после добавления нового элемента) оказаться вторым и т.&nbsp;д. Итак,
наличие скобок в тексте результирующей динамической структуры означает, что ее элементы располагаются не в том порядке,
который требуется.</p>
<hr>
<h3>Правильное решение</h3>
<p>Для получения правильного решения задания Dynamic3 необходимо явно выделить память для нового элемента,
используя процедуру <code>New</code>, и заполнить поля этого элемента, связав его с текущей вершиной стека
(в результате сам этот элемент станет новой вершиной, адрес которой и следует вывести):</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>var</b>
d: integer;
p1, p2: PNode;
<b>begin</b>
Task('Dynamic3');
read(d, p1);
New(p2);
p2^.Data := D;
p2^.Next := p1;
write(p2);
<b>end</b>.
</pre></code></blockquote>
<p>Приведем вид окна задачника при первом запуске этой программы:</p>
<blockquote>
<img border="0" src="gif/Dynamic3-3.png">
</blockquote>
<hr>
<H2>Пример 3. Удаление элемента из динамической структуры</H2>
<hr>
<h3>Знакомство с заданием</h3>
<p>Рассмотрим простейшее задание на удаление элемента из динамической структуры &#151;
Dynamic5.
В нем требуется удалить из стека вершину и вернуть указатель на новую вершину, то есть на элемент,
расположенный непосредственно за удаленным.</p>
<p>Особенность заданий на удаление элементов из динамических структур заключается в том, что удаляемый элемент
необходимо не только &laquo;отсоединить&raquo;
от исходной динамической структуры, но и полностью &laquo;уничтожить&raquo;, то есть <i>освободить память, занимаемую этим элементом</i>.</p>
<p>Для того чтобы напомнить учащемуся о необходимости уничтожения некоторых элементов исходной динамической
структуры, эти элементы выделяются на экране синим цветом меньшей яркости, чем обычные элементы (на рисунке
таким способом выделен элемент 15):</p>
<blockquote>
<img border="0" src="gif/Dynamic5-1.png">
</blockquote>
<hr>
<h3>Приступаем к решению</h3>
<p>Вначале приведем неправильный вариант решения, в котором
не освобождается память, занимаемая удаленным из стека элементом:</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>var</b> p1: PNode;
<b>begin</b>
Task('Dynamic5');
read(p1);
write(p1^.Data, p1^.Next);
<b>end</b>.
</pre></code></blockquote>
<p>Несмотря на то что все результирующие данные будут совпадать с контрольными
(то есть текст в разделах &laquo;Полученные результаты&raquo; и &laquo;Пример верного решения&raquo; будет одинаковым), на
информационной панели появится сообщение об ошибке &laquo;<i>Не освобождена динамическая память</i>&raquo;,
а в разделе исходных данных будет выделен красным цветом тот элемент, который требовалось удалить:</p>
<blockquote>
<img border="0" src="gif/Dynamic5-2.png">
</blockquote>
<hr>
<h3>Правильное решение</h3>
<p>Для получения правильного решения достаточно добавить в конец программы оператор вызова
процедуры <code>Dispose</code>, освобождающий память, на которую указывает указатель <code>p1</code>:</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>var</b> p1: PNode;
<b>begin</b>
Task('Dynamic5');
read(p1);
write(p1^.Data, p1^.Next);
Dispose(p1);
<b>end</b>.
</pre></code></blockquote>
<p>Приведем вид окна задачника при первом запуске этой программы:</p>
<blockquote>
<img border="0" src="gif/Dynamic5-3.png">
</blockquote>
<hr>
<H2>Пример 4. Двусвязные динамические структуры</H2>
<hr>
<h3>Знакомство с заданием</h3>
<p>Особенности работы с двусвязными динамическими структурами рассмотрим на примере задания
Dynamic30,
в котором требуется преобразовать исходную односвязную структуру в двусвязную. Запустив программу-заготовку,
созданную для этого задания, мы увидим в области исходных данных информацию об &laquo;обычной&raquo; односвязной структуре,
подобной рассмотренным в предыдущих примерах:</p>
<blockquote>
<img border="0" src="gif/Dynamic30-1.png">
</blockquote>
<p>Динамическая структура, приведенная в разделе результатов, имеет две особенности:
во-первых, ее элементы связаны символом <code>=</code>, а во-вторых, перед первым элементом присутствует текст
<code>nil&lt;</code>.</p>
<p>Это означает, что результирующая структура является <i>двусвязной</i>, то есть каждый ее элемент связан не
только с последующим элементом (с помощью поля <code>Next</code>, как в односвязной структуре), но и с
предыдущим элементом (с помощью нового поля <code>Prev</code>), а поле <code>Prev</code> первого элемента имеет
значение <code>nil</code>:</p>
<blockquote>
<img border="0" src="gif/Dynamic30-2.png">
</blockquote>
<hr>
<h3>Приступаем к решению</h3>
<p>Для преобразования исходной односвязной структуры в двусвязную необходимо задать правильные значения для
полей <code>Prev</code> всех элементов структуры, перебирая в цикле пары соседних элементов:</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>var</b>
p1, p: PNode;
<b>begin</b>
Task('Dynamic30');
read(p1);
p := p1^.Next;
<b>while</b> p &lt;&gt; nil <b>do</b>
<b>begin</b>
p^.Prev := p1;
p1 := p1^.Next;
p := p^.Next;
<b>end</b>;
write(p1); <i>{ вывод указателя на последний элемент }</i>
<b>end</b>.
</pre></code></blockquote>
<p>В этой программе мы определили поля <code>Prev</code> для всех элементов, кроме первого. Поэтому решение будет считаться ошибочным
(обратите внимание на то, что перед первым элементом полученного списка отсутствует текст <code>nil&lt;</code>):</p>
<blockquote>
<img border="0" src="gif/Dynamic30-3.png">
</blockquote>
<p><b>Замечание.</b> При анализе ошибочного решения часто оказывается полезным и специальное обозначение &laquo;<code>=</code>&raquo; для двойной связи.
Предположим, например, что информация
о результирующей двусвязной структуре, созданной программой, имеет вид:</p>
<blockquote><code><pre>
P<sub>2</sub>
nil&lt; 33 = 64 - 78 = 12 = 51 >nil
</pre></code></blockquote>
<p>Это означает, что между вторым и третьим элементом структуры имеется не двойная, а <i>одинарная</i> связь
(поле <code>Next</code> второго элемента содержит адрес третьего элемента, а поле <code>Prev</code> третьего элемента
<i>не содержит</i> адрес второго).</p>
<hr>
<h3>Правильное решение</h3>
<p>Для получения правильного решения достаточно добавить в программу перед циклом <code>while</code>
следующий оператор:</p>
<blockquote><code><pre>
p1^.Prev := nil;
</pre></code></blockquote>
<p>Приведем вид окна задачника при первом запуске исправленной программы:</p>
<blockquote>
<img border="0" src="gif/Dynamic30-4.png">
</blockquote>
<p><b>Замечание.</b> Для задания Dynamic30 возможен более короткий вариант решения,
в котором не требуется особо обрабатывать первый элемент списка:
</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>var</b>
p1, p: PNode;
<b>begin</b>
Task('Dynamic30');
p := nil;
read(p1);
<b>while</b> p1 &lt;&gt; nil <b>do</b>
<b>begin</b>
p1^.Prev := p;
p := p1;
p1 := p1^.Next;
<b>end</b>;
write(p);
<b>end</b>.
</pre></code></blockquote>
<hr>
<H2>Пример 5. Циклические динамические структуры</H2>
<hr>
<h3>Знакомство с заданием</h3>
<p>Динамическая структура называется <i>циклической</i>, если она замкнута в &laquo;кольцо&raquo;, то есть ее последний элемент
связан полем <code>Next</code> с первым (в случае двусвязной структуры требуется также, чтобы ее первый элемент
был связан полем <code>Prev</code>
с последним элементом). Простейшим заданием на циклические структуры является
Dynamic55, в котором требуется
преобразовать обычный двусвязный список
в циклический.</p>
<p>Запустив программу-заготовку для этого задания, мы увидим
на экране изображение двух динамических структур, причем исходная структура является &laquo;обычным&raquo; двусвязным списком,
а результирующая структура &#151; циклическим двусвязным списком:</p>
<blockquote>
<img border="0" src="gif/Dynamic55-1.png">
</blockquote>
<p>Обозначения <code>&lt;&lt; =</code> и <code>= &gt;&gt;</code> позволяют отличить циклический список от обычного
(напомним, что у обычного двусвязного списка поле <code>Prev</code> первого элемента и поле <code>Next</code> последнего
элемента равны <code><b>nil</b></code>).</p>
<p>Таким образом, экранный текст, описывающий циклический двусвязный список, является упрощенным вариантом
следующей схемы:</p>
<blockquote>
<img border="0" src="gif/Dynamic55-2.png">
</blockquote>
<hr>
<h3>Приступаем к решению</h3>
<p>Для решения задания Dynamic55 достаточно найти последний элемент исходного списка и связать его с
первым элементом:</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>var</b>
p1, p2: PNode;
<b>begin</b>
Task('Dynamic55');
read(p1);
p2 := p1;
<b>while</b> p2^.Next &lt;&gt; <b>nil</b> <b>do</b>
p2 := p2^.Next;
p2^.Next := p1;
write(p2);
<b>end</b>.
</pre></code></blockquote>
<p>В данном варианте решения мы &laquo;забыли&raquo; о том, что надо связать не только последний элемент с первым,
но и первый с последним (поскольку наш список &#151; двусвязный). Поэтому решение оказалось ошибочным
(обратите внимание на то, что после последнего элемента полученного списка изображена одинарная, а не двойная черта):</p>
<blockquote>
<img border="0" src="gif/Dynamic55-3.png">
</blockquote>
<hr>
<h3>Правильное решение</h3>
<p>Для получения правильного решения достаточно добавить в программу перед процедурой вывода <code>write</code>
следующий оператор:</p>
<blockquote><code><pre>
p1^.Prev := p2;
</pre></code></blockquote>
<p>Приведем вид окна задачника при первом запуске исправленной программы:</p>
<blockquote>
<img border="0" src="gif/Dynamic55-4.png">
</blockquote>
<hr>
<h3>Просмотр результатов выполнения заданий</h3>
<p>Щелкнув мышью на метке &laquo;Результаты (F2)&raquo;, расположенной в правом верхнем
углу окна задачника, или нажав клавишу <b>F2</b>, мы можем вывести на экран <i>окно результатов</i>,
в котором будет перечислены все наши попытки решения задачи:</p>
<blockquote><code><pre>
Dynamic2 a08/09 13:11 Ознакомительный запуск.
Dynamic2 a08/09 13:15 Выведены не все результирующие данные.
Dynamic2 a08/09 13:17 Ошибочное решение.
Dynamic2 a08/09 13:20 Задание выполнено!
Dynamic3 a08/09 13:21 Ознакомительный запуск.
Dynamic3 a08/09 13:24 Ошибочное решение.
Dynamic3 a08/09 13:28 Задание выполнено!
Dynamic5 a08/09 13:29 Ознакомительный запуск.
Dynamic5 a08/09 13:30 Не освобождена динамическая память.
Dynamic5 a08/09 13:31 Задание выполнено!
Dynamic30 a08/09 13:34 Ознакомительный запуск.
Dynamic30 a08/09 13:42 Ошибочное решение.
Dynamic30 a08/09 13:43 Задание выполнено!
Dynamic55 a08/09 13:54 Ознакомительный запуск.
Dynamic55 a08/09 13:57 Ошибочное решение.
Dynamic55 a08/09 13:58 Задание выполнено!
</pre></code></blockquote>
<p>Для закрытия окна результатов достаточно нажать клавишу <b>Esc</b>.
Окно результатов можно отобразить на экране и после закрытия окна задачника и возврата в среду PascalABC.NET.
Для этого надо использовать команду меню &laquo;Модули | Просмотреть результаты&raquo;, кнопку
<img border="0" src="gif/Results.gif" width="16" height="17"> или клавиатурную
комбинацию <b>Shift+Ctrl+R</b>.
</p>
</body>
</html>

View file

@ -1,215 +0,0 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title></title>
<link rel="StyleSheet" href="../../default.css">
</head>
<body>
<H1>Задание на обработку строк</H1>
<hr>
<h3>Создание программы-заготовки и знакомство с заданием</h3>
<p>В качестве примера задания на обработку строк рассмотрим задание String9.</p>
<p>Программу-заготовку для решения этого задания можно создать с помощью
команды
меню &laquo;Модули | Создать шаблон программы&raquo;, кнопки
<img border="0" src="gif/Load.gif" width="16" height="17"> или клавиатурной
комбинации <b>Shift+Ctrl+L</b>. Эта заготовка будет иметь следующий вид:</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>begin</b>
Task('String9');
<b>end</b>.
</pre></code></blockquote>
<p>После запуска данной программы на экране появится <a href="../PT4/window.html">окно задачника</a>:</p>
<blockquote>
<img border="0" src="gif/string9-1.png">
</blockquote>
<p>Данные типа <code>char</code> и <code>string</code> в окне задачника заключаются в <i>апострофы</i>; это позволяет, в частности,
отличить числовые данные (например, <code>9</code>) от символьных и строковых данных, содержащих цифры
(например, <code>'9'</code>).
Кроме того, апострофы дают возможность увидеть <i>пробелы</i>, находящиеся в начале или конце строк.
<hr>
<h3>Ввод исходных данных</h3>
<p>Добавим в программу фрагмент, обеспечивающий ввод исходных данных
(мы намеренно ввели данные не в том порядке, в котором они указаны в окне задачника):
</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
n: integer;
c1, c2: char;
<b>begin</b>
Task('String9');
read(c1, c2, n);
<b>end</b>.
</pre></code></blockquote>
<p>Запуск нового варианта программы уже не будет считаться ознакомительным, поскольку в программе
выполняется ввод исходных данных. Так как порядок ввода исходных данных является ошибочным,
этот вариант решения будет признан неверным и приведет к сообщению
&laquo;<i>Неверно указан тип при вводе исходных данных</i>&raquo;:</p>
<blockquote>
<img border="0" src="gif/string9-2.png">
</blockquote>
<p>Общее правило, определяющее порядок ввода и вывода данных для задачника <b>Programming Taskbook</b>
гласит:
<i>ввод и вывод данных производится по строкам (слева направо), а строки просматриваются сверху вниз</i>.
Иными словами,
данные, отображаемые в окне задачника, вводятся и выводятся в том порядке, в котором читается обычный
текст.</p>
<p>На панели индикаторов, расположенной под информационной панелью,
отмечено, что введен всего один элемент исходных данных (из трех), хотя в процедуре <code>read</code> нашей программы
были
указаны три переменные. Это объясняется тем, что при вводе первой из этих переменных (<code>с1</code>)
была обнаружена ошибка несоответствия типа, поэтому задачник не стал анализировать остальные данные,
которые программа пыталась ввести. Здесь проявляется еще одно правило задачника
<b>Programming Taskbook</b>: <i>при обнаружении первой ошибки ввода-вывода
анализ оставшихся исходных данных и результатов не проводится</i>.
</p>
<p>Исправим нашу программу, изменив порядкок параметров в процедуре ввода:</p>
<blockquote>
<code><pre>
read(n, c1, c2);
</pre></code></blockquote>
<p>Теперь с вводом данных проблем не возникает, однако из-за того что в программе отсутствует вывод результирующих
данных, решение по-прежнему считается ошибочным:</p>
<blockquote>
<img border="0" src="gif/string9-3.png">
</blockquote>
<p>Данная ошибка, в отличие от предыдущей, связана не с вводом исходных данных, а с выводом результатов.
Это отмечается в окне задачника двумя способами: цветной маркер ошибки располагается рядом с индикатором вывода,
и этим же цветом выделяется заголовок раздела результатов (который в данном случае не содержит никаких данных).
</p>
<hr>
<h3>Формирование и вывод требуемой строки</h3>
<p>Для формирования нужной строки воспользуемся операцией <code>+</code> (<i>сцепления</i> строк):</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
n, i: integer;
c1, c2: char;
s: string;
<b>begin</b>
Task('String9');
read(n, c1, c2);
s := '';
<b>for</b> i := 1 <b>to</b> n <b>do</b>
s := s + c1 + c2;
write(s);
<b>end</b>.
</pre></code></blockquote>
<p>Заметим, что в операторе <code>s := ''</code> нет необходимости, так как все глобальные переменные в Паскале автоматически
инициализируются &laquo;нулевыми&raquo; значениями (для числовых данных это обычные нули, для символов &#151; это
символ <code>#0</code>, для
строк &#151; пустая строка <code>''</code>).</p>
<p>Результат выполнения этой программы будет следующим:</p>
<blockquote>
<img border="0" src="gif/string9-4.png">
</blockquote>
<p>Красная звездочка, расположенная в конце выведенной строки, означает, что длина полученной строки превышает
длину верной строки. Для того чтобы увидеть на экране всю полученную строку, достаточно подвести курсор мыши
к строке со звездочкой; при этом полный текст строки появится во всплывающей подсказке:</p>
<blockquote>
<img border="0" src="gif/string9-5.png">
</blockquote>
<p><b>Замечание.</b> Красная звездочка может появиться и при выводе ошибочных числовых данных. Например, если ожидается
целое число в диапазоне от <code>1</code> до <code>99</code>, а получено число <code>10000</code>, то на экране изобразится первая цифра этого большого
числа, за которой будет указана красная звездочка: <code>1<font color=red>*</font></code>.</p>
<hr>
<h3>Верное решение</h3>
<p>Ошибка в предыдущей программе возникла из-за неверного указания количества итераций цикла. Действительно,
на каждой итерации к строке добавляется по <i>два</i> символа, поэтому после <code>n</code> итераций
строка будет содержать <code>2*n</code> символов (а не <code>n</code>, как требуется в задании).
<p>Для исправления ошибки достаточно вдвое
уменьшить число итераций:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
n, i: integer;
c1, c2: char;
s: string;
<b>begin</b>
Task('String9');
read(n, c1, c2);
s := '';
<b>for</b> i := 1 <b>to</b> n <b>div</b> 2 <b>do</b>
s := s + c1 + c2;
write(s);
<b>end</b>.
</pre></code></blockquote>
<p>После запуска этого варианта мы получим сообщение &laquo;<i>Верное решение. Тест номер 1 (из 5)</i>&raquo;,
а после пяти подобных запусков &#151; сообщение &laquo;<i>Задание выполнено!</i>&raquo;:</p>
<blockquote>
<img border="0" src="gif/string9-6.png">
</blockquote>
<p><b>Замечание.</b> Приведем другой, более быстрый, способ решения задания String9,
в котором результирующая строка заполняется <i>посимвольно</i>, как обычный массив:</p>
<blockquote>
<code><pre>
<b>uses</b> PT4;
<b>var</b>
n, i: integer;
c1, c2: char;
s: string;
<b>begin</b>
Task('String9');
read(n, c1, c2);
SetLength(s, n);
<b>for</b> i := 1 <b>to</b> n <b>div</b> 2 <b>do</b>
<b>begin</b>
s[2 * i - 1] := c1;
s[2 * i] := c2;
<b>end</b>;
write(s);
<b>end</b>.
</pre></code></blockquote>
<p>Обратите внимание на процедуру <code>SetLength(s, n)</code>, которая обеспечивает правильную настройку <i>длины</i> результирующей
строки <code>s</code>. Без вызова этой процедуры программа работала бы неверно, так как любая
глобальная строковая переменная инициализируется пустой строкой, а при работе с отдельными символами строки
корректировка ее длины не производится.</p>
<hr>
<h3>Просмотр результатов выполнения задания</h3>
<p>Щелкнув мышью на метке &laquo;Результаты (F2)&raquo;, расположенной в правом верхнем
углу окна задачника, или нажав клавишу <b>F2</b>, мы можем вывести на экран <i>окно результатов</i>,
в котором будет перечислены все наши попытки решения задачи:</p>
<blockquote>
<code><pre>
String9 a08/09 11:17 Ознакомительный запуск.
String9 a08/09 11:18 Неверно указан тип при вводе исходных данных.
String9 a08/09 11:21 Выведены не все результирующие данные.
String9 a08/09 11:26 Ошибочное решение.
String9 a08/09 11:29 Задание выполнено!
</pre></code>
</blockquote>
<p>Для закрытия окна результатов достаточно нажать клавишу <b>Esc</b>.
Окно результатов можно отобразить на экране и после закрытия окна задачника и возврата в среду <b>PascalABC.NET</b>.
Для этого надо использовать команду меню &laquo;Модули | Просмотреть результаты&raquo;, кнопку
<img border="0" src="gif/Results.gif" width="16" height="17"> или клавиатурную
комбинацию <b>Shift+Ctrl+R</b>.
</p>
</body>
</html>

View file

@ -1,314 +0,0 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251">
<title></title>
<link rel="StyleSheet" href="../../default.css">
</head>
<body>
<a name="main"></a>
<h1>Задания на обработку деревьев
</h1>
<hr>
<h2>Пример 1. Анализ бинарного дерева
</h2>
<hr>
<p>В заданиях группы Tree, как и в заданиях группы Dynamic, мы встречаемся с двумя новыми видами данных:
это <i>древовидные динамические структуры</i>, реализованные в виде наборов связанных друг с
другом записей типа <code>TNode</code>, и <i>указатели</i> типа <code>PNode</code> на записи <code>TNode</code>: <code>PNode =
^TNode</code>. <a href="..\PT4\IO.html#node">Типы <code>TNode</code> и <code>PNode</code></a> не являются стандартными типами языка Паскаль; они
определены в задачнике Programming Taskbook.
</p>
<p>Особенности, связанные с использованием новых типов данных, рассмотрим на
примере задания Tree2.
</p>
<p class="task">Tree2&#176;.
Дан адрес <i>P</i><sub>1</sub> записи типа TNode &#151; корня дерева.
Эта запись связана полями Left и Right с другими записями того же типа (дочерними вершинами),
они, в свою очередь, &#151; со своими дочерними вершинами, и так далее до записей,
поля Left и Right которых равны nil (у некоторых вершин может быть равно nil одно из полей Left или Right).
Вывести количество вершин дерева.
</p>
<hr>
<h3>Создание программы-заготовки и знакомство с заданием</h3>
<p>Напомним, что программу-заготовку для решения этого задания можно создать с помощью
команды
меню &laquo;Модули | Создать шаблон программы&raquo;, кнопки
<img border="0" src="gif/Load.gif" width="16" height="17"> или клавиатурной
комбинации <b>Shift+Ctrl+L</b>. Приведем текст созданной заготовки:
</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>begin</b>
Task('Tree2');
<b>end</b>.
</pre></code></blockquote>
<p>После запуска программы на экране появится <a href="../PT4/window.html">окно задачника</a>:
</p>
<blockquote>
<img border="0" src="gif/Tree2-1.png">
</blockquote>
<p>Это окно содержит в качестве исходных и результирующих данных новые
элементы: бинарные деревья и указатели.
</p>
<p>Начнем с описания того, как отображается на экране <i>дерево</i>. Для его вывода используется
несколько экранных строк. На каждой строке изображаются вершины дерева,
находящиеся на определенном уровне (номер уровня указывается слева от изображения дерева).
Для каждой вершины выводится ее значение, т.&nbsp;е. значение поля Data соответствующей записи типа TNode.
Любая вершина соединяется линиями со своими дочерними вершинами, расположенными на следующем уровне дерева;
левая дочерняя вершина изображается слева от родительской вершины, а правая &#151; справа.
Отсутствие у вершины одной или обеих дочерних вершин означает, что ее поля <code>Left</code> и/или <code>Right</code> равны <code>nil</code>.
</p>
<p>
Рассмотрим в качестве примера дерево, приведенное на рисунке.
Корень этого дерева имеет значение 15, левая дочерняя вершина корня равна 58,
правая дочерняя вершина равна 42, глубина дерева равна 4. Все листья дерева находятся на уровнях
3 и 4; листья на уровне 3 имеют значения 15 и 11, листья на уровне 4 &#151; значения 38 и 84.
Некоторые из внутренних вершин дерева имеют по две
дочерние вершины (это корень и вершины со значениями 55 и 20), некоторые по одной:
левой (вершины 42, 87 и 60) или правой (вершина 58).
</p>
<p>Поскольку это дерево указано в разделе исходных данных, следовательно, после инициализации
задания оно уже существует и размещается в некоторой области динамической памяти.
Для доступа к данным, размещенным в динамической памяти,
необходимо знать их адрес, поэтому в любом задании на обработку деревьев в набор исходных данных входят
<i>указатели</i>, содержащие адреса каких-либо вершин этих деревьев (как правило, указывается адрес корня дерева).
</p>
<p>Работа с исходными и результирующими
данными типа указателя подробно обсуждается в разделе, посвященном
<a href="ctpointer.html">линейным динамическим структурам</a>.
</p>
</p>
<hr>
<h3>Решение задачи</h3>
<p>В задании Tree2 не требуется ни создавать, ни
преобразовывать исходное дерево;
его необходимо лишь проанализировать, а именно определить количество его вершин.
</p>
<p>Для выполнения этого задания, как и для подавляющего большинства других заданий на обработку деревьев,
следует воспользоваться вспомогательной <i>рекурсивной</i> подпрограммой (функцией или процедурой).
Рекурсивная природа алгоритмов,
связанных с обработкой деревьев (в частности, бинарных деревьев), объясняется тем, что сами
определения деревьев общего вида и бинарных деревьев являются рекурсивными.
Так, дать словесное описание функции <code>NodeCount(P)</code>, подсчитывающей число вершин дерева с корнем,
с которым связан указатель <code>P</code>, можно следующим образом: если указатель <code>P</code> равен <code>nil</code>,
то следует вернуть значение <code>0</code>; в противном случае следует вернуть значение
<code>1 + NodeCount(P^.Left) + NodeCount(P^.Right)</code>
(в этом выражении первое слагаемое соответствует корню дерева, второе &#151;
его левому поддереву, а третье &#151; его правому поддереву; при этом не требуется проверять,
что указанные поддеревья существуют, так как при их отсутствии соответствующее слагаемое просто будет равно нулю).
</p>
<p>Таким образом, решение задачи будет иметь следующий вид:
</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>function</b> NodeCount(P: PNode): integer;
begin
<b>if</b> P = nil <b>then</b>
result := 0
<b>else</b>
result := 1 + NodeCount(P^.Left) + NodeCount(P^.Right);
<b>end</b>;
<b>var</b> P1: PNode;
<b>begin</b>
Task('Tree2');
read(P1);
write(NodeCount(P1));
<b>end</b>.
</pre></code></blockquote>
<p>Цепочка рекурсивных вызовов функции <code>NodeCount</code> завершается при достижении терминальной вершины (листа),
у которой поля <code>Left</code> и <code>Right</code> равны <code>nil</code>. Благодаря наличию функции <code>NodeCount</code>,
раздел операторов программы является очень кратким: в нем считывается адрес <code>P1</code> корня исходного дерева,
после чего вызывается функция <code>NodeCount(P1)</code>, возвращаемое значение которой сразу выводится процедурой write.
</p>
<p>Запустив эту программу пять раз, мы получим сообщение &laquo;<i>Задание
выполнено!</i>&raquo;.
</p>
<hr>
<a name="parent"></a>
<h2>Пример 2. Бинарные деревья с обратной связью
</h2>
<hr>
<p>Рассмотренная выше реализация бинарных деревьев позволяет легко переходить от родительских вершин
к их дочерним вершинам, но не допускает обратного перехода. В то же время, для некоторых задач,
связанных с обработкой деревьев, возможность обратного перехода от потомков к их предку позволяет
получить более простое решение. Ясно, что для обеспечения возможности обратного перехода
каждую вершину дерева надо снабдить еще одним полем связи, в котором должна храниться ссылка на
ее родительскую вершину. Это поле связи естественно назвать <code>Parent</code>. Поскольку корень дерева
предка не имеет, его поле <code>Parent</code> должно быть равно <code>nil</code>.
</p>
<p>Деревья, вершины которых содержат информацию о своих родителях, будем называть <i>деревьями с обратной связью</i>.
Особенности работы с подобными деревьями рассмотрим на примере задания Tree49.
</p>
<p class="task">Tree49&#176;.
Дан указатель <i>P</i><sub>1</sub> на корень дерева, вершинами которого являются записи типа TNode,
связанные между собой с помощью полей Left и Right. Используя поле Parent записи TNode,
преобразовать исходное дерево в <i>дерево с обратной связью</i>, в котором каждая вершина
связана не только со своими дочерними вершинами (полями Left и Right), но и с родительской
вершиной (полем Parent). Поле Parent корня дерева положить равным nil.
</p>
<p>Запустив программу-заготовку, созданную для задания Tree49,
мы увидим в области исходных данных изображение &laquo;обычного&raquo; бинарного дерева,
в то время как в области результатов будет изображено дерево с обратной связью,
вершины которого связаны не одинарными, а двойными линиями.
</p>
<blockquote>
<img border="0" src="gif/Tree49-1.png">
</blockquote>
<p>Обратите также внимание на то, что в области результатов отсутствуют какие-либо данные, кроме
измененного дерева. Это означает, что в программе, решающей задачу, не требуется использовать
процедуры вывода; достаточно лишь преобразовать исходное дерево требуемым образом.
Поскольку при таком преобразовании адрес корня дерева <i>P</i><sub>1</sub>
не изменится, задачник сможеть получить доступ к
этому дереву и проверить его правильность.
</p>
<p>Для преобразования исходного дерева в дерево с обратной связью необходимо задать правильные значения
для полей <code>Parent</code> всех вершин дерева, перебирая эти вершины с помощью подходящей рекурсивной процедуры.
В эту процедуру удобно передавать в качестве параметров не только указатель <code>P</code> на текущую вершину,
но и указатель <code>Par</code> на предка этой вершины:
</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>procedure</b> SetParent(P, Par: PNode);
<b>begin</b>
<b>if</b> P = nil <b>then</b>
exit;
P^.Parent := Par;
SetParent(P^.Left, P);
SetParent(P^.Right, P);
<b>end</b>;
<b>var</b> P1: PNode;
<b>begin</b>
Task('Tree49');
read(P1);
SetParent(P1, nil);
<b>end</b>.
</pre></code></blockquote>
<p>При стартовом запуске рекурсивной процедуры <code>SetParent</code> в качестве
второго параметра указывается <code>nil</code>.
</p>
<p>
<b>Примечание.</b> Обозначение для двойной связи может оказаться полезным при анализе ошибочного решения.
Так, если в изображении дерева с обратной связью имеется вершина, соединенная со своей родительским
вершиной не двойной, а одинарной линией, значит, у этой вершины поле <code>Parent</code> содержит ошибочное значение
(например, равно <code>nil</code>).
</p>
<hr>
<a name="general"></a>
<h2>Пример 3. Деревья общего вида
</h2>
<hr>
<p>С помощью связанных записей типа <code>TNode</code> можно моделировать не только бинарные деревья,
но и произвольные упорядоченные деревья, вершины которых имеют любое число непосредственных потомков
(будем называть такие деревья <i>деревьями общего вида</i>; для них также используется название
&laquo;деревья с множественным ветвлением&raquo;).
Рассмотрим задание Tree86 &#151; первое из заданий, связанных с деревьями общего вида, в котором
описываются особенности подобных деревьев.
</p>
<p class="task">Tree86&#176;.
<i>Дерево общего вида</i> (каждая вершина которого может иметь произвольное число дочерних вершин,
расположенных в фиксированном порядке в направлении слева направо) реализуется с помощью набора
связанных записей типа TNode следующим образом: для каждой внутренней вершины ее поле Left
содержит указатель на ее первую (т.&nbsp;е. левую) дочернюю вершину, а поле Right &#151; указатель на ее
правую <i>сестру</i>, т.&nbsp;е. вершину, имеющую в дереве общего вида того же родителя.
Поле Right корня дерева общего вида всегда равно nil, так как корень сестер не имеет.
Дан указатель <i>P</i><sub>1</sub> на корень непустого бинарного дерева. Создать дерево общего вида,
соответствующее исходному бинарному дереву, и вывести указатель <i>P</i><sub>2</sub> на его корень.
</p>
<p>Приведем пример дерева общего вида, которое реализовано с помощью связанных
записей типа <code>TNode</code> (аналогичным образом деревья общего вида изображаются в окне задачника):
</p>
<blockquote>
<img border="0" src="gif/Tree86-1.png">
</blockquote>
<p>Корень этого дерева (со значением 13) имеет три дочерние вершины (71, 73 и 29),
причем вершина 71 не имеет потомков, вершина 73 имеет три непосредственных потомка (18, 93 и 92),
а вершина 29 &#151; два (24 и 84). На последнем уровне располагается вершина 46, являющаяся
единственной дочерней вершиной вершины 93.
</p>
<p>При ознакомительном запуске задания Tree86 на экране появится окно, подобное следующему.
</p>
<blockquote>
<img border="0" src="gif/Tree86-2.png">
</blockquote>
<p>Обратите внимание на то, как выглядит одно и то же дерево в двух различных представлениях:
вариант, соответствующий обычному бинарному дереву, приводится в разделе исходных данных,
а вариант, соответствующий дереву общего вида, &#151; в разделе результатов.
При переходе от бинарного дерева к дереву общего вида часть информации о структуре бинарного дерева
теряется, поскольку в случае, если некоторая вершина дерева общего вида имеет только одного
непосредственного потомка, нельзя определить, каким был этот потомок в исходном бинарном дереве &#151; левым или правым.
</p>
<p>Напомним, что точки, обрамляющие значения вершин в разделе результатов, означают, что
все эти вершины должны быть созданы программой учащегося (в отличие от вершин исходного дерева, созданных
самим задачником при инициализации задания).
</p>
<p>При формировании нового дерева будем использовать рекурсивную функцию <code>CreateNode(P)</code>.
Параметр <code>P</code> содержит указатель на вершину исходного дерева, копия которой создается при вызове функции.
Возвращаемым значением функции является указатель на созданную вершину (как обычно, если <code>P = nil</code>,
то функция не выполняет никаких действий и возвращает <code>nil</code>). Для создания дочерних вершин выполняется
рекурсивный вызов этой функции. Заметим, что цепочка дочерних вершин может быть пустой
(если вершина <code>P</code> является листом), содержать один элемент (если вершина <code>P</code> имеет только одного
непосредственного потомка) или два элемента. Перед формированием цепочки дочерних вершин удобно
занести адреса дочерних вершин вершины <code>P</code> во вспомогательные переменные <code>P1</code> и <code>P2</code>. При этом в случае,
если вершина <code>P</code> имеет только одного потомка (неважно, левого или правого), адрес этого потомка
заносится в переменную <code>P1</code>, а переменная <code>P2</code> остается равной <code>nil</code>. Благодаря использованию
переменных <code>P1</code> и <code>P2</code>, фрагмент кода, отвечающий за формирование списка дочерних вершин,
удается сделать более кратким. Приведем текст программы, решающей задачу Tree86.
</p>
<blockquote><code><pre>
<b>uses</b> PT4;
<b>function</b> CreateNode(P: PNode): PNode;
<b>var</b> P1, P2: PNode;
<b>begin</b>
<b>if</b> P = nil <b>then</b>
<b>begin</b>
result := nil;
exit;
<b>end</b>;
New(result);
result^.Data := P^.Data;
result^.Right := nil;
P1 := P^.Left;
P2 := P^.Right;
<b>if</b> P1 = nil <b>then</b>
<b>begin</b>
P1 := P2;
P2 := nil;
<b>end</b>;
<i>{ формирование списка дочерних вершин }</i>
result^.Left := CreateNode(P1);
<b>if</b> P1 &lt;&gt; nil <b>then</b>
result^.Left^.Right := CreateNode(P2);
<b>end</b>;
<b>var</b> P1: PNode;
<b>begin</b>
Task('Tree86');
read(P1);
write(CreateNode(P1));
end.
</pre></code></blockquote>
<p>
<b>Примечание.</b> Фрагмент дерева общего вида, содержащий все дочерние вершины некоторой вершины,
можно рассматривать как <i>односвязный список</i>, элементы которого связаны между собой
с помощью поля <code>Right</code> (у последнего элемента списка поле <code>Right</code> равно <code>nil</code>).
Каждый элемент подобного списка может содержать &laquo;подсписок&raquo; своих дочерних элементов;
адрес начала этого подсписка хранится в поле <code>Left</code> данного элемента.
Поэтому в алгоритмах, связанных с обработкой вершин деревьев общего вида,
для перебора непосредственных потомков некоторой вершины удобно использовать <i>цикл</i>
(как при переборе элементов списка), в то время как для обработки каждой дочерней вершины следует,
как обычно, использовать <i>рекурсию</i>.
</p>

View file

@ -35,6 +35,7 @@
<param name="Keyword" value="Sort">
<param name="Keyword" value="SortDescending">
<param name="Keyword" value="Swap">
<param name="Keyword" value="ToString">
<param name="Keyword" value="Transform">
<param name="Keyword" value="расширение"></object>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1251"><link rel="StyleSheet" href="../../../default.css">
@ -187,6 +188,8 @@
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Сортирует массив по убыванию по ключу</td></tr>
<tr><td><code><b>function</b> Swap&lt;T&gt;(Self: <b>array</b> <b>of</b> T; i, j: integer): <b>array</b> <b>of</b> T;</code>
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Меняет местами элементы массива с индексами i и j</td></tr>
<tr><td><code><b>function</b> ToString(Self: <b>array</b> <b>of</b> real; digits: integer): string;</code>
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Возвращает строковое представление массива вещественных чисел с фиксированным количеством знаков после десятичной точки</td></tr>
<tr><td><code><b>function</b> Transform&lt;T&gt;(Self: <b>array</b> <b>of</b> T; f: T-&gt;T): <b>array</b> <b>of</b> T;</code>
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Преобразует элементы массива по заданному правилу</td></tr>
<tr><td><code><b>function</b> Transform&lt;T&gt;(Self: <b>array</b> <b>of</b> T; f: (T,integer)-&gt;T): <b>array</b> <b>of</b> T;</code>

View file

@ -191,6 +191,8 @@
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Возвращает произведение элементов последовательности</td></tr>
<tr><td><code><b>function</b> Product(Self: <b>sequence </b> <b>of</b> integer): int64;</code>
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Возвращает произведение элементов последовательности</td></tr>
<tr><td><code><b>function</b> Product(Self: <b>sequence </b> <b>of</b> int64): int64;</code>
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Возвращает произведение элементов последовательности</td></tr>
<tr><td><code><b>function</b> Product&lt;T&gt;(Self: <b>sequence </b> <b>of</b> T; f: T-&gt;real): real;</code>
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Возвращает произведение элементов последовательности, спроектированных на числовое значение</td></tr>
<tr><td><code><b>function</b> Product&lt;T&gt;(Self: <b>sequence </b> <b>of</b> T; f: T-&gt;integer): int64;</code>

View file

@ -1 +1 @@
3.11.0.3699
3.11.0.3700

View file

@ -1 +1 @@
!define VERSION '3.11.0.3699'
!define VERSION '3.11.0.3700'

View file

@ -11274,6 +11274,14 @@ begin
Result *= x;
end;
/// Возвращает произведение элементов последовательности
function Product(Self: sequence of int64): int64; extensionmethod;
begin
Result := 1;
foreach var x in Self do
Result *= x;
end;
/// Возвращает произведение элементов последовательности, спроектированных на числовое значение
function Product<T>(Self: sequence of T; f: T->real): real; extensionmethod;
begin
@ -13903,6 +13911,12 @@ begin
yield i;
end;
/// Возвращает строковое представление массива вещественных чисел с фиксированным количеством знаков после десятичной точки
function ToString(Self: array of real; digits: integer): string; extensionmethod;
begin
Result := Self.Select(x -> Round(x,digits)).JoinToString;
end;
function NextCombHelper(ind: array of integer; m,n: integer): boolean;
begin
for var i:=m-1 downto 0 do

View file

@ -11274,6 +11274,14 @@ begin
Result *= x;
end;
/// Возвращает произведение элементов последовательности
function Product(Self: sequence of int64): int64; extensionmethod;
begin
Result := 1;
foreach var x in Self do
Result *= x;
end;
/// Возвращает произведение элементов последовательности, спроектированных на числовое значение
function Product<T>(Self: sequence of T; f: T->real): real; extensionmethod;
begin

Binary file not shown.