Programming Taskbook


E-mail:

Пароль:

Регистрация пользователя   Восстановление пароля

English

ЮФУ SMBU

Электронный задачник по программированию

©  М. Э. Абрамян (Южный федеральный университет, Университет МГУ-ППИ в Шэньчжэне), 1998–2024

 

Решения | C#, VB.NET, F# | Методы задачника

PrevNext


Вспомогательные методы задачника

Приведенные в данном разделе методы реализованы в классах, входящих в сборку pt4net.dll. При создании проектов-заготовок с помощью модуля PT4Load данная сборка подключается к созданным проектам автоматически.

Хотя в программах для различных языков платформы .NET используются разные классы из сборки pt4net.dll (PT для C#, PTVB для VB.NET, pt для F#), их методы в основном совпадают. Поэтому далее для краткости будут приведены только варианты описаний методов для языка C#.

В программах, выполняющих задания на языках C# и VB.NET все описанные далее методы можно указывать непосредственно, не уточняя, к какому классу они относятся. В программах на языке F# при вызове всех методов указывать класс pt, например, pt.GetInt() или pt.Put(a, b, c).

Инициализация заданий, ввод-вывод данных

// Метод, инициализирующий задание:
static void Task(string name);
// Методы, обеспечивающие ввод исходных данных:
static bool GetBool();
static int GetInt();
static double GetDouble();
static char GetChar();
static string GetString();
static Node GetNode();
// Метод, обеспечивающий вывод результатов:
static void Put(params object[] a);

Метод Task инициализирует задание с указанным именем name. Он должен вызываться в начале процедуры Solve, выполняющей это задание (до вызова методов ввода-вывода Get–Put) или, для языка F#, входить в выражение вида let Solve = pt.Task <имя задания>. Если метод Task не вызван, то при запуске программы будет выведено окно с сообщением «Не вызвана процедура Task с именем задания».

Имя задания name должно включать имя группы заданий и порядковый номер в пределах группы (например, "Begin3"). Регистр букв в имени группы может быть произвольным. Если указана неверная группа, то программа выведет сообщение об ошибке «Указана неверная тема задания» (список всех доступных групп можно получить с помощью программных модулей PT4Demo и PT4Load). Если указан недопустимый номер задания, то программа выведет сообщение, в котором будет указан диапазон допустимых номеров для данной группы. Если после имени задания в параметре name указан суффикс «?» (например, "Begin3?"), то программа будет работать в демонстрационном режиме.

Начиная с версии 4.8, метод Task может также использоваться для генерации и вывода на экран html-страницы с текстом задания или группы заданий. Для этого необходимо указать в качестве параметра name имя конкретного задания или группы заданий и суффикс «#», например, "Begin3#" или "Begin#". Дополнительные сведения о генерации html-страниц с описаниями заданий приводятся в разделе, посвященном демонстрационному режиму задачника.

Начиная с версии 4.13, задачник обеспечивает автоматическое тестирование программы на нескольких наборах исходных данных при ее однократном запуске. Для отключения этой возможности достаточно указать в параметре Name после имени задания суффикс «!» (например, "Begin3!"); в этом случае при запуске программы она будет протестирована на единственном наборе исходных данных, и для проверки правильности решения программу потребуется запустить несколько раз (как в предыдущих версиях задачника).

Если метод Task вызывается в программе несколько раз, то все последующие его вызовы игнорируются. Исключением является ситуация, когда метод используется для генерации html-страницы с описанием нескольких заданий или групп заданий; в этом случае учитываются все вызовы данного метода.

Начиная с версии 4.12, параметр name может содержать суффикс «_ru» или «_en», позволяющий явным образом задать язык интерфейса (русский или английский соответственно) для окна задачника и выполняемого задания. В версиях 4.12–4.14 специальные суффиксы «?», «#» и «!» должны указываться перед данным суффиксом, например, "Begin3#_en"; начиная с версии 4.15, суффиксы могут указываться в любом порядке. В случае нескольких вызовов метода Task (для генерации html-страницы) учитывается только суффикс, указанный при первом вызове метода. При отсутствии суффикса используется язык интерфейса, определенный в качестве основного для данного рабочего каталога (в универсальном варианте задачника основной язык интерфейса можно настроить с помощью программного модуля PT4Load, используя его контекстное меню).

Методы группы Get обеспечивают ввод исходных данных в программу, выполняющую учебное задание. Они должны вызываться после вызова метода Task; в случае их вызова до вызова метода Task при запуске программы будет выведено сообщение об ошибке «В начале программы не вызвана процедура Task с именем задания».

Следует обратить внимание на то, что в варианте задачника для языков платформы .NET методы ввода оформлены как функции. Это соответствует общей идеологии стандартной библиотеки .NET Framework (и библиотек других языков, например, Python или Java), в которой ввод данных всегда выполняется с помощью функций.

Для языка F# предусмотрены функции ввода, позволяющие ввести кортеж из 2, 3 или 4 элементов данных одного типа. В конце имен этих функций указывается соответствующее число, например, pt.GetChar2(), pt.GetInt3(), pt.GetDouble4(). Кроме того, для ввода вещественных чисел предусмотрены функции-синонимы GetFloat, GetFloat2, GetFloat3, GetFloat4, поскольку вещественный тип System.Double в языке F# может описываться как с помощью ключевого слова double, та и с помощью ключевого слова float.

Начиная с версии 4.22, аналогичные функции для ввода кортежей типа ValueTuple (например, GetChar2(), GetInt3(), GetDouble4()) можно использовать и для языков C# и VB.NET (за исключением среды SharpDevelop, в которой используется версия 5.0 языка C#, не поддерживающая работу с кортежами данного типа).

Используемая функция ввода должна соответствовать типу очередного элемента исходных данных; в противном случае выводится сообщение об ошибке «Неверно указан тип при вводе исходных данных» (такое сообщение будет выведено, например, если очередной элемент данных является символом, а для его ввода используется функция GetInt).

При попытке ввести больше исходных данных, чем это предусмотрено в задании, выводится сообщение об ошибке «Попытка ввести лишние исходные данные». Если исходные данные, необходимые для решения задания, введены не полностью, то выводится сообщение «Введены не все требуемые исходные данные».

Метод Put обеспечивает вывод на экран результирующих данных, найденных программой, и их сравнение с контрольными данными (т. е. с правильным решением). Как и методы группы Get, метод Put должен вызываться после вызова метода Task; в противном случае при запуске программы будет выведено сообщение об ошибке «В начале программы не вызвана процедура Task с именем задания».

Благодаря использованию параметра-массива, снабженного атрибутом params, при вызове метода Put можно указывать произвольное число параметров. Параметры могут иметь тип bool, int, double, char, string, Node для C# и аналогичные типы для языков VB.NET и F#. В качестве параметров метода Put можно указывать не только переменные, но и выражения (в частности, константы соответствующего типа, а также нулевую ссылку — null для C# и F#, Nothing для VB.NET). Заметим, что нулевые ссылки, как и объекты типа Node, требуется выводить только в заданиях групп Dynamic и Tree (и их аналогов GCDyn и GCTree, появившихся в версии задачника 4.15). Если в списке параметров указываются параметры недопустимого типа, то при выполнении программы выводится сообщение об ошибке «В методе Put указан параметр недопустимого типа». В версии 4.19 метод Put изменен таким образом, чтобы с его помощью можно было выводить данные составных типов (см. далее раздел «Дополнительные средства для ввода и вывода структур данных (версия 4.19)»);

Тип параметра должен не только быть допустимым, но и соответствовать типу очередного элемента результирующих данных; в противном случае выводится сообщение об ошибке «Неверно указан тип при выводе результатов».

Как и в случае методов группы Get, при вызовах метода Put программа осуществляет контроль за соответствием количества требуемых и выведенных результирующих данных. Если программа выведет недостаточное или избыточное количество результирующих данных, то после проверки этих данных появится сообщение «Выведены не все результирующие данные» или, соответственно, «Попытка вывести лишние результирующие данные».

Класс Node

// Конструкторы:
Node();
Node(int aData);
Node(int aData, Node aNext);
Node(int aData, Node aNext, Node aPrev);
Node(Node aLeft, Node aRight, int aData);
Node(Node aLeft, Node aRight, int aData, Node aParent);
// Свойства:
int Data { get; set; }
Node Next { get; set; }
Node Prev { get; set; }
Node Left { get; set; }
Node Right { get; set; }
Node Parent { get; set; }
// Метод, освобождающий ресурсы, используемые объектом Node:
void Dispose();
}

Класс Node используется в заданиях групп Dynamic и Tree и их аналогов GCDyn и GCTree. В заданиях на стеки и очереди (Dynamic1–Dynamic28) при работе с объектами типа Node используются только свойства Data и Next; в заданиях на двусвязные списки (Dynamic29–Dynamic80) используются свойства Data, Next и Prev. В большинстве заданий на бинарные деревья (группа Tree) используются свойства Data, Left и Right; в заданиях на обработку бинарных деревьев с обратной связью (Tree48–Tree56 и Tree70–Tree71) дополнительно используется свойство Parent. Варианты конструктора класса Node позволяют задавать значения требуемых свойств при создании объекта; прочие свойства инициализируются нулевыми значениями (числом 0 для свойства Data, нулевой ссылкой null/Nothing для остальных свойств).

Следует обратить внимание на то, что данный класс реализует интерфейс IDisposable, поэтому при завершении работы с объектом типа Node требуется вызвать его метод Dispose, освобождающий неуправляемые ресурсы, выделенные для этого объекта (исключение делается только для тех объектов, которые передаются обратно задачнику в качестве результирующих данных). Если в задании требуется вызвать метод Dispose для некоторых объектов, но этот вызов не выполняется, то при запуске программы выводится сообщение об ошибке «Не вызван метод Dispose для объекта типа Node». При выполнении заданий групп GCDyn и GCTree, появившихся в версии 4.15, использовать метод Dispose не требуется.

Все исходные и результирующие данные-ссылки в заданиях группы Dynamic имеют тип Node; их ввод и вывод должен осуществляться с помощью методов GetNode и Put соответственно.

Дополнительные средства для ввода и вывода последовательностей

Описываемые в данном пункте возможности доступны для языков C# и VB.NET, начиная с версии 4.15, а для языка F# — с версии 4.20.

Для ввода последовательностей с элементами базовых типов int, double, char и string, предусмотрены следующие вспомогательные методы (ранее они были реализованы в расширении задачника Programming Taskbook for LINQ):

// Ввод последовательностей
static IEnumerable<int> GetEnumerableInt([int count]);
static IEnumerable<double> GetEnumerableDouble([int count]);
static IEnumerable<char> GetEnumerableChar([int count]);
static IEnumerable<string> GetEnumerableString([int count]);

В версии 4.20 были добавлены методы-синонимы с более краткими именами: GetSeqInt, GetSeqDouble, GetSeqChar, GetSeqString. Кроме того, для языка F# предусмотрены методы-синонимы для ввода последовательностей вещественных чисел: GetEnumerableFloat и GetSeqFloat.

Методы GetEnumerableInt, GetEnumerableDouble, GetEnumerableChar, GetEnumerableString (и их синонимы) возвращают исходную последовательность соответствующего типа. Если параметр count отсутствует, то предварительно считывается размер последовательности (целое число), после чего вводятся сами ее элементы; если параметр count явно указан, то он определяет размер последовательности, и выполняется чтение только указанного количества элементов.

Для вывода последовательности в разделе результатов предусмотрен метод расширения Put для класса IEnumerable<T>:

static void Put<T>([int count]);

Вызов метода расширения должен иметь вид a.Put(), где a — некоторая последовательность (на месте a может быть также расположена цепочка запросов LINQ, преобразующих исходную последовательность). Если параметр count отсутствует, то вначале выводится размер последовательности (целое число), а затем — все ее элементы. Если параметр count явно указан, то выводятся первые count элементов последовательности или все ее элементы, если параметр count не принадлежит диапазону от 1 до L, где L — размер последовательности (сам размер последовательности не выводится).

Имеется также метод расширения Show для вывода последовательности в раздел отладки (о других способах отладочного вывода см. далее раздел Вывод отладочной информации):

static IEnumerable<TSource> Show<TSource, TResult>([string cmt],
  [Func<TSource, TResult> selector])

Метод расширения Show может применяться к любым последовательностям и обеспечивает их вывод в раздел отладки. Возвращаемым значением этого метода является сама последовательность, поэтому он может использоваться в цепочке запросов LINQ. Вначале в раздел отладки выводится строковый комментарий cmt (если он указан), затем размер последовательности, а затем — все ее элементы. После вывода последовательности автоматически выполняется переход на новую строку раздела отладки. Если указано лямбда-выражение selector, то выводятся не сами элементы e последовательности, а их преобразованные значения, возвращаемые функцией selector(e).

Дополнительные средства для ввода и вывода структур данных (версия 4.19)

static int[] GetArrInt([int count]);
static double[] GetArrDouble([int count]);
static char[] GetArrChar([int count]);
static string[] GetArrString([int count]);

static List<int> GetListInt([int count]);
static List<double> GetListDouble([int count]);
static List<char> GetListChar([int count]);
static List<string> GetListString([int count]);

static int[,] GetMatrInt([int m [, int n]]);
static double[,] GetMatrDouble([int m [, int n]]);
static char[,] GetMatrChar([int m [, int n]]);
static string[,] GetMatrString([int m [, int n]]);

static int[][] GetArrArrInt([int m [, int n]]);
static double[][] GetArrArrDouble([int m [, int n]]);
static char[][] GetArrArrChar([int m [, int n]]);
static string[][] GetArrArrString([int m [, int n]]);

static List<List<int>> GetListListInt([int m [, int n]]);
static List<List<double>> GetListListDouble([int m [, int n]]);
static List<List<char>> GetListListChar([int m [, int n]]);
static List<List<string>> GetListListString([int m [, int n]]);

В версии 4.19 набор методов ввода для языков C# и VB.NET был дополнен методами, упрощающими ввод линейных и двумерных структур данных. Новые методы реализованы аналогично описанным выше методам GetEnumerable... . Каждая группа методов обеспечивает чтение структур с элементами одного из четырех основных типов T: int, double, char и string. Реализованы пять групп методов, описанных ниже.

Для языка F# этот набор методов доступен, начиная с версии 4.20, причем предусмотрены методы-синонимы для ввода вещественных структур; в именах этих методов вместо слова Double указывается слово Float: GetArrFloat, GetListFloat и т. д.

Методы ввода линейных структур представлены группами GetArr... и GetList... . Они могут вызываться без параметров (и в этом случае вначале выполняют чтение целого числа — размера структуры, после чего считывают все ее элементы) или с одним параметром целого типа count, определяющим размер структуры (и в этом случае считывают только сами элементы). Методы группы GetArr... возвращают одномерный массив T[], методы группы GetList... — список на базе массива List<T>.

Методы ввода двумерных структур представлены группами GetMatr..., GetArrArr... и GetListList... . Они могут вызываться без параметров и в этом случае вначале выполняют чтение двух целых чисел —, определяющих размеры структуры по первому (число строк) и по второму индексу (число столбцов), после чего считывают все элементы по строкам. При указании одного целочисленного параметра m он определяет и число строк, и число столбцов (т. е. структура соответствует квадратной матрице порядка m); метод считывает только элементы структуры. При указании двух целочисленных параметров m и n первый из них определяет число строк, второй — число столбцов, в этом случае также метод считывает только элементы структуры. Методы группы GetMatr... возвращают двумерный массив T[,], методы группы GetArrArr... — массив массивов T[][], методы группы GetListList... — список списков List<List<T>>.

Универсальный метод Put изменен таким образом, что в качестве его параметров можно указывать не только базовые типы данных int, double, char, string, bool и Node, но и любые последовательности (т. е. объекты, реализующие интерфейс IEnumerable), в том числе вложенные, а также кортежи (объекты типа Tuple и ValueTuple). В случае кортежей последовательно выводятся все их компоненты, в случае последовательностей выводятся все их элементы.

Вывод отладочной информации

Возможность вывода отладочной информации непосредственно в окно задачника (в специальный раздел отладки) появилась в версии 4.9 задачника Programming Taskbook. Для этого были предназначены методы Show, ShowLine и HideTask. В версии 4.19 средства отладочного вывода были усовершенствованы. Методы Show и ShowLine теперь могут принимать произвольное число параметров (ранее можно было указывать только один параметр), причем можно настраивать ширину вывода данных, а также число дробных знаков для вещественных чисел. Кроме того, появилась возможность наглядного вывода составных данных с применением вспомогательных форматирующих элементов.

Начиная с версии 4.22, в раздел отладки можно выводить текстовые данные, содержащие любые символы Юникода.

// Методы, предназначенные для отладки программ:
static void Show(params object[] args);
static void ShowLine(params object[] args);
static void ShowLine();
static void HideTask();
static void SetWidth(int w);
static void SetPrecision(int p);

Метод Show отображает произвольное количество элементов данных в разделе отладки окна задачника. В качестве параметров можно указывать элементы данных любого типа; эти элементы будут автоматически преобразованы к своему строковому представлению.

Выводимые данные разделяются пробелом; если текущая экранная строка в разделе отладки уже содержит некоторый текст, то выводимая строка снабжается начальным пробелом и приписывается к предшествующему тексту, за исключением случая, когда при таком приписывании размер полученного текста превысит ширину области данных (равную 80 символам). В последнем случае вывод строки осуществляется с начала следующей экранной строки; если же и в этой ситуации строка превысит ширину области данных, то строка будет выведена на нескольких экранных строках, причем разрывы текста будут выполняться по пробельным символам строки, а при отсутствии пробелов — при достижении очередного фрагмента строки длины, равной 80.

Строковые параметры функции Show могут содержать явные команды перехода на новую экранную строку. В качестве таких команд можно использовать или символ с кодом 13 («возврат каретки» — '\r' для C# и F#, ChrW(13) для VB.NET), или символ с кодом 10 («переход на новую строку» — '\n' для C# и F#, ChrW(10) для VB.NET), или их комбинацию в указанном порядке ("\r\n" для C# и F#, ChrW(13) & ChrW(10) для VB.NET).

Метод ShowLine является модификацией метода Show; после вывода данных в раздел отладки он дополнительно осуществляет автоматический переход на следующую экранную строку. Если метод ShowLine вызывается без параметров, то он просто обеспечивает переход на новую экранную строку в разделе отладки.

Метод HideTask обеспечивает автоматическое скрытие всех разделов окна задачника, кроме раздела отладки. Если раздел отладки в окне задачника не отображается (в частности, если программа запущена в демонстрационном режиме), то вызов метода HideTask игнорируется. Игнорируются также все повторные вызовы данного метода.

Скрыть/восстановить основные разделы окна задачника после его отображения на экране можно также с помощью клавиши пробела или соответствующей команды контекстного меню раздела отладки.

Метод SetWidth(w) появился в версии задачника 4.19. Он позволяет задать минимальную ширину w (w >= 0) поля вывода для элементов данных, выводимых методами Show и ShowLine; по умолчанию минимальная ширина полагается равной 0. Действие указанной настройки распространяется на все последующие вызовы методов Show и ShowLine. Числовые данные выравниваются по правой границе, прочие данные — по левой.

Метод SetPrecision(d) также появился в версии задачника 4.19. Он устанавливает количество d дробных знаков при выводе вещественных чисел (при d <= 0 используется формат с плавающей точкой; число дробных знаков в этом случае равно –d или 6, если d равно 0); по умолчанию d = 2. В качестве десятичного разделителя используется точка. Действие указанной настройки распространяется на все последующие вызовы методов Show и ShowLine.

В версии 4.19 реализация методов Show и ShowLine была изменена таким образом, чтобы обеспечивать форматированный вывод наборов данных, в том числе вложенных. При оформлении выводимых наборов данных используются следующие обозначения: кортежи (Tuple и ValueTuple) обрамляются круглыми скобками; для большинства типов, реализующих интерфейс Enumerable (в частности, массивов и списков List), используются квадратные скобки; исключение составляют множества (HashSet и SortedSet) и словари (в частности, Dictionary и SortedDictionary), для которых применяются фигурные скобки. Элементы словарей, как и кортежи, обрамляются круглыми скобками, кроме того, ключи (key) и значения (value) этих элементов разделяются двоеточием.

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

Элементы любых наборов данных разделяются запятыми (за исключением элементов, которые реализуют интерфейс Enumerable, после которых запятая не указывается, поскольку, как было отмечено выше, такие элементы разделяются разрывами строк).

Начиная с версии 4.19, метод ShowLine выполняет дополнительный переход на следующую строку только в случае, если последним выводимым элементом данных не является Enumerable-объект, поскольку после вывода подобных объектов всегда выполняется переход на новую строку.

Ниже на языке C# приводятся примеры использования метода Show для вывода различных наборов данных. Фрагмент программы, обеспечивающий отладочный вывод, имеет следующий вид (было использовано задание Matrix80, в котором дается вещественная квадратная матрица):

     Task("Matrix80");
     HideTask();
     ShowLine("Матрица вещественных чисел (width = 5):");
     var a = GetMatrDouble(GetInt());
     SetWidth(5);
     ShowLine(a);
     ShowLine("Словарь строк (width = 0):");
     var b1 = new Dictionary<int, string>();
     b1[1] = "ABCD";
     b1[3] = "123";
     b1[10] = "!*****!";
     SetWidth(0);
     ShowLine(b1);
     ShowLine("Словарь строковых массивов (width = 3):");
     var b2 = new Dictionary<int, string[]>();
     b2[1] = new string[] { "ab", "d" };
     b2[2] = new string[] { "123", "45", "7" };
     b2[22] = new string[] { "**", "#", "@@@" };
     SetWidth(3);
     ShowLine(b2);
     ShowLine("Список списков, содержащих числовые строки (width = 6):");
     var c1 = new List<List<string>>();
     c1.Add(new List<string>());
     c1[0].Add("ABCD"); c1[0].Add("123"); c1[0].Add("!****!");
     c1.Add(new List<string>());
     c1[1].Add("abcd"); c1[1].Add("56789");
     SetWidth(6);
      ShowLine(c1);
     ShowLine("Массив массивов, содержащих числовые массивы (width = 2):");
     var c2 = new int[3][][];
     c2[0] = new int[2][];
     c2[0][0] = new int[] { 1, 2, 3 };
     c2[0][1] = new int[] { 4, 5, 6 };
     c2[1] = new int[3][];
     c2[1][0] = new int[] { 7, 8, 9 };
     c2[1][1] = new int[] { 10, 11, 12 };
     c2[1][2] = new int[] { 60, 61, 62 };
     c2[2] = new int[2][];
     c2[2][0] = new int[] { 13, 14, 15, 50 };
     c2[2][1] = new int[] { 16, 17, 18, 19, 20 };
     SetWidth(2);
     ShowLine(c2);

В приведенном далее образце содержимого раздела отладки следует обратить внимание на то, что при выводе скалярных элементов или элементов-кортежей они разделяются запятыми, тогда как после каждого элемента, реализующего интерфейс Enumerable (массива или списка List), выполняется переход на новую строку. Кроме того, следует обратить внимание на использование отступов при выводе многомерных списков, а также на настройку ширины области вывода с помощью метода SetWidth.

  1>  Матрица вещественных чисел (width = 5):
  2>  [ [  2.07 ,  4.75 ,  4.15 ,  6.20 ,  0.59 ]
  3>    [  4.47 ,  3.12 ,  7.51 ,  7.98 ,  9.45 ]
  4>    [  5.50 ,  8.67 ,  2.51 ,  2.64 ,  5.90 ]
  5>    [  2.45 ,  6.45 ,  4.24 ,  4.35 ,  4.51 ]
  6>    [  2.87 ,  1.46 ,  6.67 ,  5.57 ,  5.00 ]
  7>  ]
  8>  Словарь строк (width = 0):
  9>  { ( 1 : ABCD ) , ( 3 : 123 ) , ( 10 : !*****! ) }
 10>  Словарь строковых массивов (width = 3):
 11>  { (   1 : [ ab  , d   ]
 12>  ) (   2 : [ 123 , 45  , 7   ]
 13>  ) (  22 : [ **  , #   , @@@ ]
 14>  ) }
 15>  Список списков, содержащих числовые строки (width = 6):
 16>  [ [ ABCD   , 123    , !****! ]
 17>    [ abcd   , 56789  ]
 18>  ]
 19>  Массив массивов, содержащих числовые массивы (width = 2):
 20>  [ [ [  1 ,  2 ,  3 ]
 21>      [  4 ,  5 ,  6 ]
 22>    ]
 23>    [ [  7 ,  8 ,  9 ]
 24>      [ 10 , 11 , 12 ]
 25>      [ 60 , 61 , 62 ]
 26>    ]
 27>    [ [ 13 , 14 , 15 , 50 ]
 28>      [ 16 , 17 , 18 , 19 , 20 ]
 29>    ]
 30>  ]

PrevNext

 

Рейтинг@Mail.ru

Разработка сайта:
М. Э. Абрамян, В. Н. Брагилевский

Последнее обновление:
01.01.2024