Министерство образования и науки Российской Федерации

Федеральное государственное автономное образовательное учреждение
высшего профессионального образования
"ЮЖНЫЙ ФЕДЕРАЛЬНЫЙ УНИВЕРСИТЕТ"

А. В. Абрамян

Работа с массивами, строками и файлами на языке C#

Задачи, решения, указания, справочный материал

Электронное учебное пособие

Ростов-на-Дону 2011

Публикуется по решению учебно-методической комиссии
факультета математики, механики и компьютерных наук ЮФУ
от 25 апреля 2011 г. (протокол № 8)

Рецензенты:
к. ф.-м. н., доцент С. С. Михалкович,
старший преподаватель В. Н. Брагилевский

Аннотация

Электронное учебное пособие содержит материал, посвященный разработке сложных структур данных: массивов (одномерных и двумерных), символьных строк и файлов (двоичных и текстовых). В качестве языка программирования используется язык С#, получивший в последнее время очень широкое распространение благодаря тесной интеграции с платформой .NET, активно разрабатываемой компанией Microsoft.

Пособие разбито на 5 модулей. В модуле 1 рассматриваются алгоритмы обработки числовых последовательностей. Каждый из оставшихся четырех модулей посвящен определенной структуре данных: массивам (модуль 2), символьным строкам (модуль 3), двоичным файлам (модуль 4) и текстовым файлам (модуль 5). В состав каждого модуля, помимо основного содержания — формулировок учебных задач, решений и указаний — модули включают раздел со справочным материалом, а также контрольные разделы (набор контрольных вопросов и итоговое задание), позволяющие определить уровень усвоения изученной темы.

Для преподавателей программирования и студентов.

Автор: А. В. Абрамян.


Введение

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

В качестве языка программирования используется язык С#, получивший в последнее время очень широкое распространение благодаря тесной интеграции с платформой .NET, активно разрабатываемой компанией Microsoft. Наличие C-подобных базовых конструкций, богатая система базовых типов, возможность прямого использования обширноой стандартной библиотеки классов .NET, полная поддержка объектно-ориентированной модели программирования, наконец, наличие удобной бесплатной среды разработки (Visual Studio .NET Express Edition 2003/2005/2008) делают данный язык очень привлекательным для использования при обучении программированию [6, 8].

Следует отметить, что при переходе к языку C# от «процедурных» языков типа Паскаля или Visual Basic учащийся обычно испытывает некоторые трудности, связанные с другим способом организации программного кода и другим подходом в реализации ввода-вывода. Для того чтобы облегчить подобный переход, целесообразно использовать дополнительные обучающие средства, например, электронный задачник по программированию Programming Taskbook, разработанный на факультете математики, механики и компьютерных наук (автор — доц. М. Э. Абрамян).

При описание процесса решения типовых учебных заданий, включенных в пособие, предполагается, что учащийся использует задачник Programming Taskbook. Однако это не является обязательным, так как все этапы решения заданий можно реализовать и без него (соответствующий пример приведен в модуле 1).

Все основные сведения в настоящем пособии «передаются» учащемуся в ходе подробного описания решения типовых задач на обработку сложных структур данных. Кроме того, в данном пособии приводятся формулировки 450 учебных заданий, выполнение которых позволит закрепить изученный материал. С целью облегчения самостоятельного выполнения заданий к большей их части даются указания. Система гипертекстовых ссылок позволяет быстро переходить от формулировок заданий к связанным с ними указаниям и к решениям типовых задач, относящихся к той же теме.

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

В приложении дается описание методов вспомогательного класса PT, реализованного в задачнике Programming Taskbook.

При подготовке пособия были использованы материалы книг [1–7].

Ниже приводится перечень модулей и их разделов.

Модуль 1. Обработка последовательностей

  • Комплексная цель
  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание

Модуль 2. Одномерные и двумерные массивы

  • Комплексная цель
  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание

Модуль 3. Символы и строки

  • Комплексная цель
  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание

Модуль 4. Двоичные файлы

  • Комплексная цель
  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание

Модуль 5. Текстовые файлы

  • Комплексная цель
  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание


Модуль 1. Обработка последовательностей

Комплексная цель

Ознакомиться с базовыми алгоритмами обработки числовых последовательностей, в том числе с алгоритмами, связанными с нахождением минимумов и максимумов. Научиться выполнять учебные задания, используя электронный задачник Programming Taskbook. Научиться создавать консольные приложения и организовывать в них ввод и вывод данных. Изучить особенности базовых числовых типов, а также основные методы классов Math, Random и Console стандартной библиотеки .NET.

Структура модуля

  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание


Модуль 1. Обработка последовательностей

Решения типовых задач

Минимумы и максимумы: Minmax1

В данном пункте подробно описывается процесс выполнения учебных заданий в среде Microsoft Visual Studio .NET 2003, 2005 или 2008 с применением электронного задачника Programming Taskbook. При описании всех примеров предполагается, что средой программирования является система Microsoft Visual Studio 2003 (при использовании системы Microsoft Visual Studio 2005 и 2008 действия будут аналогичными).

Порядок выполнения учебного задания опишем на примере задания Minmax1 — первого задания из группы задания, посвященных нахождению минимумов и максимумов в числовых последовательностях.

Minmax1. Дано целое число N и набор из N чисел. Найти минимальный и максимальный из элементов данного набора и вывести их в указанном порядке.

Чтобы создать проект, содержащий заготовку для требуемого задания, воспользуемся программным модулем PT4Load; для этого перейдем в рабочий каталог задачника и запустить ярлык Load.lnk. На экране появится окно модуля PT4Load, в котором надо ввести имя нужного задания и нажать кнопку «Загрузка».

Созданный проект-заготовка состоит из нескольких файлов, однако для решения задания нам потребуется только один файл Minmax1.cs, содержимое которого будет загружено в редактор среды Visual Studio:

using System;
using PT4;

namespace PT4Tasks
{
    public class MyTask: PT
    {
        public static void Solve()
        {
            Task("Minmax1");

        }
    }
}

Файл Minmax1.cs содержит описание класса MyTask (этот класс является потомком класса PT, описанного в пространстве имен PT4). Функция Solve — метод класса MyTask — содержит вызов функции Task, инициализирующей задание Minmax1. Решение задания необходимо запрограммировать в функции Solve (хотя, разумеется, в решении могут использоваться другие функции, описанные в данном проекте).

Заметим, что функция Task (наряду с функциями группы Get и функцией Put) является классовым методом класса PT. Однако ее имя не требуется дополнять именем класса, поскольку Task вызывается из метода Solve класса MyTask — потомка класса PT.

Заметим также, что в файле Minmax1 отсутствует описание функции Main, которая является стартовой при выполнении программы на языке C#. Эта функция описана в файле pt4main.cs, также созданном при генерации проекта-заготовки для данного задания. Именно в функции Main происходит вызов функции Solve, решающей задание. Поскольку содержимое файла pt4main.cs не требует редактирования, данный файл не загружается в редактор среды Microsoft Visual Studio. Однако он входит в проект, в чем можно убедиться, посмотрев на окно «Solution Explorer», расположенное обычно в правой части окна среды Visual Studio:

Окно «Solution Explorer» также показывает, что к проекту подключен файл pt4net.dll. В этом файле описано пространство имен PT4, содержащее классы, используемые задачником, в частности, класс PT.

Запустим программу, нажав клавишу [F5]. После компиляции и запуска программы мы сможем увидеть на экране окно задачника с формулировкой задания и примером исходных данных:

В верхней части этого окна выводится информация о теме, к которой относится выполняемое задание, название задания, сведения об учащемся, дата и время запуска программы. Затем следует раздел с формулировкой задания.

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

Последним разделом является раздел контрольных данных и результатов, содержащий две вкладки: «Пример верного решения» и «Полученные результаты». Если в программе не указан ни один из операторов ввода-вывода, то при запуске программы активной становится вкладка «Пример верного решения» с контрольными (то есть «правильными») результирующими данными. В нашем случае в ней отображаются правильные значения минимального и максимального элемента для текущего набора исходных данных, снабженные комментариями. Если перейти на вкладку с результатами, щелкнув на ярлычке «Полученные результаты» или нажав комбинацию клавиш [Ctrl]+[Tab], то в ней отобразятся только комментарии, поскольку наша программа пока не выводит никаких результирующих данных.

Чтобы отличить комментарии от данных (как исходных, так и результирующих), используется цветовое выделение: комментарии выводятся светло-серым цветом, данные — желтым. Контрольные данные отображаются тем же цветом, что и комментарии (для того чтобы подчеркнуть, что эти данные не найдены программой учащегося).

В нижней части окна располагается информационная панель и кнопка «Выход». Так как наша программа не выполняет действий по вводу-выводу данных, ее текущий запуск рассматривается как ознакомительный, и проверка правильности решения не производится. Для выхода из программы надо нажать кнопку «Выход» или клавишу [Esc]. Можно также нажать клавишу [F5], то есть ту же клавишу, которая использовалась для запуска программы из среды Visual Studio.

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

До сих пор мы не пытались решать задание, а только знакомились с ним. Теперь перейдем к решению задания. Для того чтобы найти минимальный и максимальный из элементов исходного набора данных, требуется получить доступ к самим данным. Стандартные методы ввода, имеющиеся в библиотеке .NET Framework, обеспечивают ввод исходных данных с клавиатуры (или их считывание из какого-либо файла). У нас — другая ситуация: исходные данные уже сформированы функцией Task при инициализации задания; нам требуется лишь получить к ним доступ. Для этого надо использовать специальные функции ввода, имеющиеся в электронном задачнике Programming Taskbook. Перечислим те функции ввода, которые будут использоваться в настоящем пособии:

  • GetInt возвращает элемент данных целого типа int;
  • GetDouble возвращает элемент данных вещественного типа double;
  • GetChar возвращает элемент данных символьного типа char;
  • GetString возвращает элемент данных строкового типа string;
  • GetBool возвращает элемент данных логического типа bool.

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

Используя функцию GetInt для ввода количества исходных элементов и функцию GetDouble для ввода самих вещественных элементов и реализуя алгоритм нахождения минимального/максимального значения, получим следующий вариант функции Solve:

public static void Solve()
{
    Task("Minmax1");
    int n = GetInt();
    double a = GetDouble(), min, max;
    min = max = a;
    for (int i = 2; i <= n; i++)
    {
        a = GetDouble();
        if (a < min)
            min = a;
        else
            if (a > max)
                max = a;
    }
}

В данном фрагменте программы демонстрируются два варианта описания переменных: с инициализацией и без нее. Переменные n и a, предназначенные для хранения исходных данных, инициализируются в момент описания; при этом используются функции GetInt и GetDouble соответственно. Переменные min и max, предназначенные для хранения результатов, не инициализируются при описании; они получают требуемые значения при последующем выполнении операторов присваивания. Заметим, что вполне возможно было бы инициализировать при описании все переменные (поскольку в момент описания переменных min и max значение a уже известно) или, наоборот, описать все четыре переменные без инициализации, а затем задать их значения, используя операторы присваивания. Заметим, что в языке C# неинициализированные локальные переменные не получают значения по умолчанию, а попытка обратиться к содержимому неинициализированных переменных приводит к ошибке компиляции.

Запустив полученную программу, мы получим сообщение: «Выведены не все результирующие данные», причем фон информационной панели станет красным. Ситуация понятна: приступая к вводу исходных данных, мы тем самым продемонстрировали намерение выполнить задание. Однако, найдя требуемые данные, мы не вывели их в разделе результатов, и поэтому электронный задачник не смог их проанализировать и сравнить с верным решением.

Итак, для верного решения задания нам осталось вывести полученные результаты. Здесь, как и в случае с функциями ввода, нельзя использовать стандартные методы вывода из библиотеки .NET Framework, поскольку нам требуется не только отобразить результаты на экране, но и передать их электронному задачнику для сравнения с контрольными данными. Нужные действия обеспечивает специальная функция вывода Put, имеющаяся в электронном задачнике Programming Taskbook. Эта функция позволяет выводить данные целого, вещественного, символьного, строкового, логического типа, причем в ней можно указывать любое количество параметров любого допустимого типа. Параметрами функции Put могут быть не только переменные, но и выражения. Тип параметра, указанного в функции Put, должен соответствовать типу ожидаемого результата; если это условие нарушается, то при выполнении программы выводится сообщение об ошибке «Неверно указан тип при выводе результатов». Подробнее функция Put описана в приложении.

Еще раз подчеркнем, что рассмотренные выше методы ввода-вывода не входят в стандартную библиотеку .NET Framework. Они описаны в задачнике Programming Taskbook (точнее, являются классовыми методами класса PT, определенного в пространстве имен PT4) и могут использоваться только при выполнении учебных заданий.

Добавим в конец функции Solve вывод результатов:

Put(max, min);

После запуска программы мы можем убедиться, что найденные значения выведены в разделе результирующих данных. Но из-за того что вначале мы вывели значение max, а затем — значение min, решение опять признано неверным. Информационная панель теперь содержит сообщение «Ошибочное решение», означающее, что все исходные данные были введены, все результирующие данные выведены, но значения полученных результатов не совпадают с контрольными. Перейдя на вкладку «Пример верного решения», мы можем убедиться, что минимальное и максимальное значения были определены верно, и что нарушен лишь порядок их вывода.

Таким образом, важно не только найти правильные значения результирующих данных, но и вывести их в нужном порядке. Для того чтобы указать этот порядок, и используются комментарии (см. рисунок). Заметим, что исходные данные тоже важно вводить именно в том порядке, в котором они указаны в соответствующем разделе. Общее правило здесь следующее: ввод и вывод данных производится по строкам (слева направо), а строки просматриваются сверху вниз. Иными словами, данные вводятся и выводятся в том порядке, в котором они читались бы в обычном тексте (на русском языке).

Учитывая последнее правило и изменяя порядок вывода результирующих данных, мы, наконец, получим правильный вариант функции Solve:

public static void Solve()
{
    Task("Minmax1");
    int n = GetInt();
    double a = GetDouble(), min, max;
    min = max = a;
    for (int i = 2; i <= n; i++)
    {
        a = GetDouble();
        if (a < min)
            min = a;
        else
            if (a > max)
                max = a;
    }
    Put(min, max);
}

После запуска правильного варианта информационная панель (фон которой теперь станет зеленым) будет содержать сообщение «Верное решение. Тест номер 1 (из 5)». Мы провели первое успешное испытание нашей программы. Однако однократное успешное испытание алгоритма еще не означает, что он является правильным. Для того чтобы получить уверенность в правильности алгоритма, его надо протестировать несколько раз на различных наборах исходных данных. Количество тестовых испытаний, требующихся для каждого учебного задания, различно и может меняться в пределах от 3 до 10 (для простого задания Minmax1 число испытаний равно пяти). Если нужное количество испытаний, проведенных подряд, прошло успешно, то на информационной панели появится сообщение «Задание выполнено!». После этого можно переходить к следующему заданию. Если же в ходе очередного испытания было получено неверное решение, то счетчик количества успешных испытаний для данного задания будет сброшен в 0, и после исправления алгоритма программу придется тестировать заново.

Примечание. После выполнения требуемого количества успешных тестовых испытаний на информационной панели вместо сообщения «Задание выполнено!» может появиться сообщение «Верное решение. Покажите преподавателю текст программы»>. Подобная ситуация обычно возникает, если преподаватель желает убедиться в том, что программа оформлена надлежащим образом (например, содержит описание требуемой функции). Просмотрев текст программы и убедившись, что все требования учебного задания выполнены, преподаватель еще раз запустит программу и введет специальный код, означающий, что задание им проверено. При этом на информационной панели появится сообщение «Задание выполнено!#», которое будет занесено в файл результатов (символ # означает, что программа не только прошла тестовые испытания, но и была проверена преподавателем).

Напомним, что закрыть окно программы с учебным заданием можно не только нажатием клавиши [Esc], но и нажатием клавиши [F5], то есть той же клавиши, которая в среде Visual Studio запускает программу на выполнение. Таким образом, для проверки программы на нескольких наборах исходных данных достаточно несколько раз подряд нажать [F5].

Информация о ходе выполнения задания записывается в файл результатов. Для просмотра этого файла следует использовать ярлык Results.lnk, находящийся в рабочем каталоге учащегося.

Последовательности: Series15, Series17, Series19, Series21

В данном разделе приводятся решения нескольких заданий из группы Series. В этой группе, как и в группе Minmax, собраны задачи на обработку последовательностей чисел, при решении которых не требуется использовать массивы.

Series15. Дано целое число K и набор ненулевых целых чисел; признак его завершения — число 0. Вывести номер первого числа в наборе, большего K. Если таких чисел нет, то вывести 0.

В этом и последующих примерах решений будем приводить только операторы, входящие в функцию Solve.

Task("Series15");
int k = GetInt(), a = GetInt(),
    i = 0, n = 0;
while (a != 0)
{
    i++;
    if (a > k)
    {
        n = i;
        break;
    }
    a = GetInt();
}
Put(n);

Переменная i используется в качестве счетчика прочитанных элементов последовательности. Следует обратить внимание на то, что первый элемент последовательности считывается до цикла, а остальные — в цикле, причем сразу после считывания очередного элемента он сравнивается с 0, и при обнаружении равенства цикл завершает работу (тем самым нулевое значение в цикле не обрабатывается, что является правильным, поскольку 0 считается не очередным элементом последовательности, а лишь признаком ее завершения).

При обнаружении первого элемента, большего k, в переменной n запоминается его номер, после чего происходит немедленный выход из цикла с помощью оператора break. Если элемент, больший k, в последовательности отсутствует, то переменная n сохраняет свое начальное значение, равное 0.

Заметим, что наряду с оператором break, обеспечивающим немедленный выход из цикла, в C# предусмотрен оператор continue, позволяющий немедленно завершить текущую итерацию цикла.

Series17. Дано вещественное число B, целое число N и набор из N вещественных чисел, упорядоченных по возрастанию. Вывести элементы набора вместе с числом B, сохраняя упорядоченность выводимых чисел.

Task("Series17");
double b = GetDouble(), a;
int n = GetInt();
bool f = true;
for (int i = 1; i <= n; i++)
{
    a = GetDouble();
    if (a > b && f)
    {
        Put(b);
        f = false;
    }
    Put(a);
}
if (f)
    Put(b);

При решении данного задания удобно использовать переменную-флаг f логического типа. Если значение флага равно true, значит, число B еще не было выведено. При обнаружении элемента последовательности, большего числа B, выполняется вывод числа B и «сброс» флага f в состояние false. Важно также проверять состояние флага после завершения цикла; это позволит правильно обработать ситуацию, когда число B превосходит все числа из исходного набора и, следовательно, не было выведено ни на одной из итераций цикла.

Series19. Дано целое число N (> 1) и набор из N целых чисел. Вывести те элементы в наборе, которые меньше своего левого соседа, и количество K таких элементов.

Task("Series19");
int n = GetInt(), a = GetInt(),
    k = 0, b;
for (int i = 2; i <= n; i++)
{
    b = GetInt();
    if (a > b)
    {
        Put(b);
        k++;
    }
    a = b;
}
Put(k);

На каждой итерации цикла сравниваются две переменные: b — текущий элемент и a — предыдущий элемент последовательности (то есть левый сосед текущего элемента). В конце каждой итерации текущий элемент сохраняется в переменной a и в дальнейшем используется при анализе следующего элемента.

Series21. Дано целое число N (> 1) и набор из N вещественных чисел. Проверить, образует ли данный набор возрастающую последовательность. Если образует, то вывести true, если нет — вывести false.

Task("Series21");
int n = GetInt();
bool f = true;
double a = GetDouble(), b;
for (int i = 2; i <= n; i++)
{
    b = GetDouble();
    if (a >= b)
    {
        f = false;
        break;
    }
    a = b;
}
Put(f);

В данном задании, как и в ранее рассмотренном задании Series19, на каждой итерации анализируются соседние элементы последовательности a и b. При обнаружении пары элементов, нарушающих упорядоченность по возрастанию, происходит немедленный выход из цикла.

О выполнении заданий без применения электронного задачника

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

В данном разделе описывается, как выполнять задания без использования электронного задачника в системе Microsoft Visual Studio .NET. При этом приводится информация об имеющихся в .NET методах консольного ввода-вывода.

Перед началом выполнения задания необходимо создать заготовку для новой программы. Для выполнения учебных заданий проще всего использовать так называемые консольные приложения, которые осуществляют ввод исходных данных и вывод результатов в специальное консольное окно.

Заготовку для консольного приложения можно создать, выполняя следующие действия: в меню Visual Studio выберите команду «File | New | Project:», в появившемся окне «New Project» выберите в разделе «Project types» (расположенном слева) группу «Visual C#» и в этой группе вариант «Windows»; при этом в правой части окна появится список имеющихся видов проектов. В этом списке надо выбрать вариант «Console Application». Далее, в нижней части окна надо указать сведения, необходимые для генерации заготовки выбранного проекта: имя проекта (строка «Name»; в качестве имени целесообразно указывать имя выполняемого задания, например, Minmax1) и каталог, в котором этот проект будет размещен (строка «Location»; в этой строке следует указывать свой рабочий каталог). Для того чтобы не создавать сложной структуры подкаталогов, связанных с создаваемым проектом, флажок «Create directory for solution» надо снять.

После выполнения всех описанных действий надо нажать в окне «New Project» кнопку [OK]; в результате будет создана заготовка проекта с указанным именем, и этот проект немедленно загрузится в среду Visual Studio. Заметим, что для данного проекта будет создан подкаталог каталога, указанного в строке «Location»; имя этого подкаталога будет совпадать с именем проекта. Таким образом, каждый проект размещается в отдельном каталоге, что предотвращает опасность «перемешивания» файлов, относящихся к различным проектам.

Проект типа «Консольное приложение» содержит единственный файл Program.cs с программным кодом, который сразу отображается в редакторе среды Visual Studio:

using System;
using System.Collections.Generic;
using System.Text;

namespace Minmax1
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

Обратите внимание на то, что в строке namespace указывается имя проекта, заданное в строке «Name» окна «New Project». Созданный файл уже содержит несколько операторов using, с помощью которых упрощается доступ к классам, определенным в часто используемых пространствах имен: System, System.Collections.Generic и System.Text. Заметим, что при выполнении заданий на файлы в программе надо добавить еще один оператор using, подключающий пространство имен System.IO.

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

Если при использовании задачника Programming Taskbook решение задания следовало вводить в функцию Solve, то в консольном приложении решение надо запрограммировать непосредственно в функции Main. При этом отличия проявятся лишь в организации ввода-вывода.

Откорректируем решение задания Minmax1, приведенное в первом разделе настоящего модуля, организовав ввод и вывод с помощью стандартных методов класса Console, отвечающего за ввод-вывод в консольных приложениях (подробная информация о классе Console и его методах приводится в модуле № 5):

static void Main(string[] args)
{
    Console.Write("N = ");
    int n = int.Parse(Console.ReadLine());
    Console.WriteLine("Elements:");
    double a = double.Parse(Console.ReadLine()), min, max;
    min = max = a;
    for (int i = 2; i <= n; i++)
    {
        a = double.Parse(Console.ReadLine());
        if (a < min)
            min = a;
        else
            if (a > max)
                max = a;
    }
    Console.WriteLine("Min = " + min);
    Console.WriteLine("Max = " + max);
    Console.ReadLine();

}

Как видно из приведенного текста программы, для вывода данных в консольное окно используется метод Write класса Console (а также его вариант WriteLine, который после вывода указанных в нем данных обеспечивает в консольном окне переход на новую строку). В качестве параметра методов Write и WriteLine можно указывать данные любого базового типа, в частности строки (напомним, что строковые константы должны заключаться в двойные кавычки). Подобным образом в начале программы выводятся приглашения к вводу исходных данных: "N = " и "Элементы:". Обратите внимание также на то, что для вывода результата с комментарием достаточно использовать обычную операцию +, например, "Min = " + min.

Ввод числовых данных выполняется сложнее, чем вывод, поскольку в классе Console не предусмотрено метода для ввода числовых данных. Имеется лишь метод ReadLine для ввода строки текста. Поэтому после чтения строки с очередным числом необходимо преобразовать ее к требуемому числовому типу. Для этого используется метод Parse, имеющийся у всех числовых типов. Например, для получения числа n надо после чтения «изображения» этого числа, введенного в консольное окно, вызвать метод Parse класса int: int.Parse(Console.ReadLine()). При вводе вещественных чисел следует учитывать, что в русской версии Windows в качестве десятичного разделителя надо использовать не точку (как в программах на C#), а запятую. Дополнительная информация о методе Parse содержится в справочных сведениях к текущему модулю.

Осталось объяснить смысл последнего оператора: Console.ReadLine(). Этот оператор приостанавливает работу программы до тех пор, пока не будет нажата клавиша [Enter]. Таким образом, после вывода полученных результатов, программа будет ожидать нажатия [Enter], консольное окно останется на экране, и мы сможем ознакомиться с его содержимым. После нажатия [Enter] программа завершит работу, что приведет к немедленному закрытию консольного окна. Если бы оператор Console.ReadLine() в конце программы отсутствовал, то после вывода результатов консольное окно было бы немедленно закрыто, и мы не успели бы ознакомиться с его содержимым.

Приведем образец исходных данных и выведенных для них результатов:

N = 5
Элементы:
3
5
7,3
4,5
9,2
Min = 3
Max = 9,2

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

Для генерации случайных чисел в стандартной библиотеке классов .NET предусмотрен класс Random (его подробное описание приводится в справочных сведениях). После создания объекта типа Random его можно использовать для получения очередного случайного вещественного числа, лежащего на полуинтервале [0, 1) (для этого предусмотрен метод NextDouble без параметров) и для получения очередного случайного целого числа, лежащего в требуемом диапазоне (для этого предусмотрено несколько вариантов метода Next; например, метод с одним параметром Next(n) возвращает целое случайное число, лежащее в диапазоне от 0 до n – 1). Заметим, что для получения вещественного числа, лежащего на полуинтервале [A, B), достаточно применить к результату x функции NextDouble следующее преобразование: A + x * (B – A). Если требуется получить одно из целых чисел в диапазоне от a до b – 1, можно воспользоваться вариантов функции Next с двумя параметрами: Next(a, b).

Приведем измененный вариант программы, решающей задание Minmax1, в котором элементы исходного вещественного набора данных генерируются с помощью датчика случайных чисел (предполагается, что эти элементы могут принимать значения из полуинтервала [0, 10); количество элементов по-прежнему вводится с клавиатуры):

static void Main(string[] args)
{
    Console.Write("N = ");
    int n = int.Parse(Console.ReadLine());
    Console.WriteLine("Элементы:");
    Random r = new Random();
    double a = 10 * r.NextDouble(), min, max;
    Console.WriteLine(a);
    min = max = a;
    for (int i = 2; i <= n; i++)
    {
        a = 10 * r.NextDouble();
        Console.WriteLine(a);
        if (a < min)
            min = a;
        else
            if (a > max)
                max = a;
    }
    Console.WriteLine("Min = " + min);
    Console.WriteLine("Max = " + max);
    Console.ReadLine();

}

В начале программы создается объект r типа Random, который и используется для генерации случайных чисел. Поскольку эти числа не вводятся с клавиатуры, необходимо выводить их на экран с помощью функции WriteLine.

При выполнении нового варианта программы, если в качестве N ввести число 7, на экране появится результат, подобный следующему (разумеется, числа будут другими):

N = 7
Элементы:
8,8232039468471
5,06240666148365
1,35175694308791
2,29198868958838
5,98469718638095
9,56033890115113
2,02468000912325
Min = 1,35175694308791
Max = 9,56033890115113

Как правило, необходимости в выводе всех значащих цифр вещественных чисел, не возникает. Для того чтобы уменьшить количество выводимых цифр, можно использовать форматированный вывод данных. Так, для вывода вещественного числа a с 4 знаками после запятой, достаточно использовать следующий вариант функции WriteLine:

Console.WriteLine("{0:f4}", a);

Если выводимое число надо предварить комментарием, то этот комментарий следует указать в той же строке, что и форматные настройки, например:

Console.WriteLine("Min = {0:f4}", min);

Подробная информация о форматировании данных приводится в справочных сведениях к модулю № 3.

При соответствующем изменении операторов вывода в последнем варианте программы, текст в консольном окне примет вид, аналогичный приведенному ниже:

N = 7
Элементы:
6,8642
7,5267
4,5558
5,1632
7,0990
3,2503
4,6169
Min = 3,2503
Max = 7,5267


Модуль 1. Обработка последовательностей

Учебные задания и указания к ним

Минимумы и максимумы

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

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

Minmax1. Дано целое число N и набор из N чисел. Найти минимальный и максимальный из элементов данного набора и вывести их в указанном порядке.

Minmax2. Дано целое число N и набор из N прямоугольников, заданных своими сторонами — парами чисел (a, b). Найти минимальную площадь прямоугольника из данного набора.

Minmax3. Дано целое число N и набор из N прямоугольников, заданных своими сторонами — парами чисел (a, b). Найти максимальный периметр прямоугольника из данного набора.

Minmax4. Дано целое число N и набор из N чисел. Найти номер минимального элемента из данного набора.

Minmax5. Дано целое число N и набор из N пар чисел (m, v) — данные о массе m и объеме v деталей, изготовленных из различных материалов. Вывести номер детали, изготовленной из материала максимальной плотности, а также величину этой максимальной плотности. Плотность P вычисляется по формуле

P = m/v.

Minmax6. Дано целое число N и набор из N целых чисел. Найти номера первого минимального и последнего максимального элемента из данного набора и вывести их в указанном порядке.

Minmax7. Дано целое число N и набор из N целых чисел. Найти номера первого максимального и последнего минимального элемента из данного набора и вывести их в указанном порядке.

Minmax8. Дано целое число N и набор из N целых чисел. Найти номера первого и последнего минимального элемента из данного набора и вывести их в указанном порядке.

Minmax9. Дано целое число N и набор из N целых чисел. Найти номера первого и последнего максимального элемента из данного набора и вывести их в указанном порядке.

Minmax10. Дано целое число N и набор из N целых чисел. Найти номер первого экстремального (то есть минимального или максимального) элемента из данного набора.

Minmax11. Дано целое число N и набор из N целых чисел. Найти номер последнего экстремального (то есть минимального или максимального) элемента из данного набора.

Minmax12. Дано целое число N и набор из N чисел. Найти минимальное положительное число из данного набора. Если положительные числа в наборе отсутствуют, то вывести 0.

Minmax13. Дано целое число N и набор из N целых чисел. Найти номер первого максимального нечетного числа из данного набора. Если нечетные числа в наборе отсутствуют, то вывести 0.

Minmax14. Дано число B (> 0) и набор из десяти чисел. Вывести минимальный из тех элементов набора, которые больше B, а также его номер. Если чисел, больших B, в наборе нет, то дважды вывести 0.

Minmax15. Даны числа B, C (0 < B < C) и набор из десяти чисел. Вывести максимальный из элементов набора, содержащихся в интервале (B, C), и его номер. Если требуемые числа в наборе отсутствуют, то дважды вывести 0.

Minmax16. Дано целое число N и набор из N целых чисел. Найти количество элементов, расположенных перед первым минимальным элементом.

Minmax17. Дано целое число N и набор из N целых чисел. Найти количество элементов, расположенных после последнего максимального элемента.

Minmax18. Дано целое число N и набор из N целых чисел. Найти количество элементов, содержащихся между первым и последним максимальным элементом. Если в наборе имеется единственный максимальный элемент, то вывести 0.

Minmax19. Дано целое число N и набор из N целых чисел. Найти количество минимальных элементов из данного набора.

Minmax20. Дано целое число N и набор из N целых чисел. Найти общее количество экстремальных (то есть минимальных и максимальных) элементов из данного набора.

Minmax21. Дано целое число N (> 2) и набор из N чисел — значений некоторой величины, полученных в N опытах. Найти среднее значение этой величины. При вычислении среднего значения не учитывать минимальное и максимальное из имеющихся в наборе значений.

Minmax22. Дано целое число N (> 2) и набор из N чисел. Найти два наименьших элемента из данного набора и вывести эти элементы в порядке возрастания их значений.

Minmax23. Дано целое число N (> 3) и набор из N чисел. Найти три наибольших элемента из данного набора и вывести эти элементы в порядке убывания их значений.

Minmax24. Дано целое число N (> 1) и набор из N чисел. Найти максимальную сумму двух соседних чисел из данного набора.

Minmax25. Дано целое число N (> 1) и набор из N чисел. Найти номера двух соседних чисел из данного набора, произведение которых является минимальным, и вывести вначале меньший, а затем больший номер.

Minmax26. Дано целое число N и набор из N целых чисел. Найти максимальное количество четных чисел в наборе, идущих подряд. Если четные числа в наборе отсутствуют, то вывести 0.

Minmax27. Дано целое число N и набор из N целых чисел, содержащий только нули и единицы. Найти номер элемента, с которого начинается самая длинная последовательность одинаковых чисел, и количество элементов в этой последовательности. Если таких последовательностей несколько, то вывести номер первой из них.

Minmax28. Дано целое число N и набор из N целых чисел, содержащий только нули и единицы. Найти номер элемента, с которого начинается самая длинная последовательность единиц, и количество элементов в этой последовательности. Если таких последовательностей несколько, то вывести номер последней из них. Если единицы в исходном наборе отсутствуют, то дважды вывести 0.

Minmax29. Дано целое число N и набор из N целых чисел. Найти максимальное количество подряд идущих минимальных элементов из данного набора.

Minmax30. Дано целое число N и набор из N целых чисел. Найти минимальное количество подряд идущих максимальных элементов из данного набора.

Последовательности

Во всех заданиях данной группы предполагается, что исходный набор содержит ненулевое число элементов (в частности, число N всегда больше нуля). В заданиях на обработку нескольких наборов чисел (Series29–Series40) количество наборов K также всегда является ненулевым.

Series1. Даны десять вещественных чисел. Найти их сумму.

Series2. Даны десять вещественных чисел. Найти их произведение.

Series3. Даны десять вещественных чисел. Найти их среднее арифметическое.

Series4. Дано целое число N и набор из N вещественных чисел. Вывести сумму и произведение чисел из данного набора.

Series5. Дано целое число N и набор из N положительных вещественных чисел. Вывести в том же порядке целые части всех чисел из данного набора (как вещественные числа с нулевой дробной частью), а также сумму всех целых частей.

Series6. Дано целое число N и набор из N положительных вещественных чисел. Вывести в том же порядке дробные части всех чисел из данного набора (как вещественные числа с нулевой целой частью), а также произведение всех дробных частей.

Series7. Дано целое число N и набор из N вещественных чисел. Вывести в том же порядке округленные значения всех чисел из данного набора (как целые числа), а также сумму всех округленных значений.

Series8. Дано целое число N и набор из N целых чисел. Вывести в том же порядке все четные числа из данного набора и количество K таких чисел.

Series9. Дано целое число N и набор из N целых чисел. Вывести в том же порядке номера всех нечетных чисел из данного набора и количество K таких чисел.

Series10. Дано целое число N и набор из N целых чисел. Если в наборе имеются положительные числа, то вывести true; в противном случае вывести false.

Series11. Даны целые числа K, N и набор из N целых чисел. Если в наборе имеются числа, меньшие K, то вывести true; в противном случае вывести false.

Series12. Дан набор ненулевых целых чисел; признак его завершения — число 0. Вывести количество чисел в наборе.

Series13. Дан набор ненулевых целых чисел; признак его завершения — число 0. Вывести сумму всех положительных четных чисел из данного набора. Если требуемые числа в наборе отсутствуют, то вывести 0.

Series14. Дано целое число K и набор ненулевых целых чисел; признак его завершения — число 0. Вывести количество чисел в наборе, меньших K.

Series15. Дано целое число K и набор ненулевых целых чисел; признак его завершения — число 0. Вывести номер первого числа в наборе, большего K. Если таких чисел нет, то вывести 0.

Series16. Дано целое число K и набор ненулевых целых чисел; признак его завершения — число 0. Вывести номер последнего числа в наборе, большего K. Если таких чисел нет, то вывести 0.

Series17. Дано вещественное число B, целое число N и набор из N вещественных чисел, упорядоченных по возрастанию. Вывести элементы набора вместе с числом B, сохраняя упорядоченность выводимых чисел.

Series18. Дано целое число N и набор из N целых чисел, упорядоченный по возрастанию. Данный набор может содержать одинаковые элементы. Вывести в том же порядке все различные элементы данного набора.

Series19. Дано целое число N (> 1) и набор из N целых чисел. Вывести те элементы в наборе, которые меньше своего левого соседа, и количество K таких элементов.

Series20. Дано целое число N (> 1) и набор из N целых чисел. Вывести те элементы в наборе, которые меньше своего правого соседа, и количество K таких элементов.

Series21. Дано целое число N (> 1) и набор из N вещественных чисел. Проверить, образует ли данный набор возрастающую последовательность. Если образует, то вывести true, если нет — вывести false.

Series22. Дано целое число N (> 1) и набор из N вещественных чисел. Если данный набор образует убывающую последовательность, то вывести 0; в противном случае вывести номер первого числа, нарушающего закономерность.

Series23. Дано целое число N (> 2) и набор из N вещественных чисел. Набор называется пилообразным, если каждый его внутренний элемент либо больше, либо меньше обоих своих соседей (то есть является «зубцом»). Если данный набор является пилообразным, то вывести 0; в противном случае вывести номер первого элемента, не являющегося зубцом.

Series24. Дано целое число N и набор из N целых чисел, содержащий по крайней мере два нуля. Вывести сумму чисел из данного набора, расположенных между последними двумя нулями (если последние нули идут подряд, то вывести 0).

Series25. Дано целое число N и набор из N целых чисел, содержащий по крайней мере два нуля. Вывести сумму чисел из данного набора, расположенных между первым и последним нулем (если первый и последний нули идут подряд, то вывести 0).

Series26. Даны целые числа K, N и набор из N вещественных чисел: A1, A2, …, AN. Вывести K-e степени чисел из данного набора:

(A1)K, (A2)K, …, (AN)K.

Series27. Дано целое число N и набор из N вещественных чисел: A1, A2, …, AN. Вывести следующие числа:

A1, (A2)2, …, (AN–1)N–1, (AN)N.

Series28. Дано целое число N и набор из N вещественных чисел: A1, A2, …, AN. Вывести следующие числа:

(A1)N, (A2)N–1, …, (AN–1)2, AN.

Series29. Даны целые числа K, N, а также K наборов целых чисел по N элементов в каждом наборе. Вывести общую сумму всех элементов, входящих в данные наборы.

Series30. Даны целые числа K, N, а также K наборов целых чисел по N элементов в каждом наборе. Для каждого набора вывести сумму его элементов.

Series31. Даны целые числа K, N, а также K наборов целых чисел по N элементов в каждом наборе. Найти количество наборов, содержащих число 2. Если таких наборов нет, то вывести 0.

Series32. Даны целые числа K, N, а также K наборов целых чисел по N элементов в каждом наборе. Для каждого набора вывести номер его первого элемента, равного 2, или число 0, если в данном наборе нет двоек.

Series33. Даны целые числа K, N, а также K наборов целых чисел по N элементов в каждом наборе. Для каждого набора вывести номер его последнего элемента, равного 2, или число 0, если в данном наборе нет двоек.

Series34. Даны целые числа K, N, а также K наборов целых чисел по N элементов в каждом наборе. Для каждого набора выполнить следующее действие: если в наборе содержится число 2, то вывести сумму его элементов; если в наборе нет двоек, то вывести 0.

Series35. Дано целое число K, а также K наборов ненулевых целых чисел. Признаком завершения каждого набора является число 0. Для каждого набора вывести количество его элементов. Вывести также общее количество элементов во всех наборах.

Series36. Дано целое число K, а также K наборов ненулевых целых чисел. Каждый набор содержит не менее двух элементов, признаком его завершения является число 0. Найти количество наборов, элементы которых возрастают.

Series37. Дано целое число K, а также K наборов ненулевых целых чисел. Каждый набор содержит не менее двух элементов, признаком его завершения является число 0. Найти количество наборов, элементы которых возрастают или убывают.

Series38. Дано целое число K, а также K наборов ненулевых целых чисел. Каждый набор содержит не менее двух элементов, признаком его завершения является число 0. Для каждого набора выполнить следующее действие: если элементы набора возрастают, то вывести 1; если элементы набора убывают, то вывести –1; если элементы набора не возрастают и не убывают, то вывести 0.

Series39. Дано целое число K, а также K наборов ненулевых целых чисел. Каждый набор содержит не менее трех элементов, признаком его завершения является число 0. Найти количество пилообразных наборов (определение пилообразного набора дано в задании Series23).

Series40. Дано целое число K, а также K наборов ненулевых целых чисел. Каждый набор содержит не менее трех элементов, признаком его завершения является число 0. Для каждого набора выполнить следующее действие: если набор является пилообразным (см. задание Series23), то вывести количество его элементов; в противном случае вывести номер первого элемента, который не является зубцом.

Указания

В тексте указаний используется сокращенное обозначение для диапазона заданий, включающее имя темы, номер начального и номер конечного задания (например, диапазон заданий от Minmax1 до Minmax3 обозначается следующим образом: Minmax1–3).

Minmax1–3. См. решение Minmax1. В Minmax2–3 элементы набора, в котором ищется минимум/максимум, необходимо вычислять по имеющимся исходным данным.

Minmax4–11. В программе необходимо использовать не только переменные min/max для хранения самих минимальных/максимальных элементов (см. решение Minmax1), но и дополнительные переменные для хранения их номеров (например, nMin и nMax). В случае, когда имеется несколько минимальных/максимальных элементов и требуется найти номер первого или последнего из них, важно выбрать правильную операцию отношения (<, > или <=, >=) при сравнении исходных данных с переменной min/max.

Minmax12–15. Задания на нахождение условного минимума/максимума. В данном случае переменные min/max (предназначенные для хранения результата) нельзя инициализировать значением первого элемента исходного набора (как в решении Minmax1), так как этот элемент может не удовлетворять дополнительным условиям. Проще всего инициализировать переменную min значением, которое заведомо больше всех элементов исходного набора данных, а переменную max — значением, которое заведомо меньше этих элементов (в качестве таких значений удобно использовать поля MaxValue и MinValue соответствующего числового типа). Еще одна особенность условных минимумов/максимумов заключается в том, что для некоторых наборов данных они могут отсутствовать. Выявить такую ситуацию несложно: в этом случае начальное значение переменной min/max не изменится после просмотра исходного набора данных.

Minmax16–18. Решение сводится к нахождению номера требуемого минимального/максимального элемента (см. также указание к Minmax4–11).

Minmax19–20. Используйте переменную-счетчик для хранения количества найденных минимумов/максимумов. Этот счетчик необходимо корректировать в двух ситуациях: при изменении значения переменной min/max (содержащей значение наименьшего/наибольшего элемента из уже просмотренных — см. решение Minmax1) и при появлении элемента, значение которого совпадает с текущим значением min/max.

Minmax21. Поскольку заранее неизвестно, какой элемент набора окажется минимальным (максимальным), следует просуммировать все исходные элементы, одновременно определяя минимальный и максимальный из них. После обработки всех исходных данных достаточно вычесть из полученной суммы минимальный и максимальный элементы и разделить результат на N – 2.

Minmax22–23. Для решения Minmax22 каждый элемент исходного набора надо сравнивать не только с переменной min1, в которой хранится значение наименьшего из уже просмотренных элементов, но и с переменной min2, содержащей значение, следующее за наименьшим (из уже просмотренных). Более точно: следует проверять, в каком из промежутков (–¥, min1), (min1, min2), (min2, +¥) содержится очередной элемент исходных данных, и в зависимости от результата корректировать значения min1 и min2. Важно также позаботиться о правильной инициализации этих переменных (например, можно инициализировать обе переменные одним и тем же значением, которое заведомо больше всех элементов исходного набора). В Minmax23 следует использовать три вспомогательные переменные: max1, max2, max3.

Minmax24–28. В этих заданиях основная трудность состоит не в реализации собственно алгоритма нахождения минимума/максимума, а в нахождении величин, из которых надо выбрать минимальное/максимальное значение. Если при решении задания не использовать массив, то текущий (анализируемый) элемент исходных данных необходимо сохранять во вспомогательной переменной, которую в дальнейшем использовать при анализе следующего элемента.

Minmax29–30. В этих заданиях алгоритм нахождения минимума/максимума используется дважды: для отбора минимальных/максимальных элементов из исходного набора данных и для определения самой длинной/короткой последовательности таких элементов. Напомним, что требуемый результат надо получить после однократного просмотра исходного набора данных.

Series1–7. Простейшие задачи на обработку последовательностей, использующие стандартные алгоритмы нахождения суммы и произведения. В Series5 для выделения целой части вещественного числа используйте метод Floor класса Math. В Series6 для выделения дробной части числа достаточно вычесть из данного числа его целую часть. Для округления числа в Series7 используйте метод Round класса Math, преобразовав возвращаемое значение этого метода к целому типу.

Series8–9. Задания на обработку элементов, удовлетворяющих некоторому условию. В Series9 для определения номера текущего элемента набора воспользуйтесь параметром цикла.

Series10–11. Задания на проверку наличия в наборе требуемых элементов. При обнаружении требуемого элемента цикл, в котором обрабатываются элементы, следует прервать (используя оператор break).

Series12–16. Особенность этих заданий состоит в том, что перед началом ввода элементов набора не известно их количество: признаком завершения набора является число 0, расположенное после последнего элемента набора (подчеркнем, что число 0 в набор не входит). Для решения этих заданий следует использовать цикл с условием. См. решение Series15. Для получения решения Series16 достаточно в решении Series15 убрать оператор break из цикла while.

Series17. См. решение этого задания.

Series18–22. Для выполнения этих заданий необходимо на каждом этапе обработки исходного набора иметь доступ не только к текущему (только что введенному) элементу, но и к предыдущему элементу, поскольку их необходимо сравнивать между собой. См. решение Series19 и решение Series21, прочие задания решаются аналогично.

Series23. Ср. с Series18–22. Удобно на каждом этапе обработки исходного набора иметь доступ к трем последовательным элементам a, b, c; в этом случае легко проверить, является ли средний элемент зубцом (достаточно сравнить знаки выражений b – a и b – c). После обработки текущей тройки элементов значение b необходимо переместить в переменную a, значение c — в переменную b, а очередной элемент набора ввести в переменную c.

Series24–25. Сложность этих заданий состоит в том, что при обработке элементов, расположенных в наборе после первого нуля, заранее не известно, надо ли включать их в требуемую сумму (поскольку мы еще не знаем, сколько нулей будет обнаружено в наборе). Опишем алгоритм решения Series24. Используем две переменные s и t, инициализируя их нулевыми значениями. Считываем начальные элементы исходного набора, пока не обнаружим первый нуль. После этого в цикле перебираем остальные элементы набора, причем если очередной элемент отличен от нуля, то он добавляется к сумме t, а если очередной элемент равен нулю, то текущее значение t записывается в s, а переменная t обнуляется. В результате после перебора всех элементов исходного набора в переменной s будет содержаться требуемая сумма. Алгоритм для Series25 имеет единственное отличие: при обнаружении очередного нуля текущее значение t должно добавляться к прежнему содержимому s. Для считывания начальных элементов набора (до первого нуля) можно использовать вспомогательный цикл с условием. Можно также использовать единственный цикл для считывания всех элементов набора, но в этом случае необходимо по-разному обрабатывать элементы, прочитанные до и после обнаружения первого нуля.

Series26–28. Используйте вложенные циклы: во внутреннем цикле вычисляются степени слагаемых, а во внешнем найденные степени добавляются к сумме. В Series27–28 дополнительно следует учитывать, что число итераций внутреннего цикла зависит от номера итерации внешнего цикла (например, в Series27 параметр j внутреннего цикла следует менять от 1 до i, где i — параметр внешнего цикла).

Series29–34. Задания на обработку нескольких числовых наборов одинакового размера. Используйте внешний цикл (с параметром, меняющимся от 1 до K) для перебора различных исходных наборов. Для обработки элементов очередного набора потребуется вложенный цикл, параметр которого будет меняться от 1 до N. В этих заданиях важно правильно определить место инициализации используемых переменных. Например, в Series29 переменная для хранения суммы элементов всех наборов должна инициализироваться нулем один раз перед началом выполнения внешнего цикла, а в Series30 переменная для хранения суммы элементов текущего набора должна принимать нулевое значение в начале каждой итерации внешнего цикла (перед выполнением вложенного цикла).

Series35–40. Задания на обработку нескольких числовых наборов различного размера. Перед началом ввода элементов очередного набора не известно их количество, поэтому в данном случае, в отличие от Series29–34, в качестве вложенного цикла следует использовать цикл с условием (см. указание к Series12–16). По поводу инициализации вспомогательных переменных см. указание к Series29–34. Ср. Series36 с Series21, Series39–40 с Series23. Алгоритм, описанный в указании к Series23, можно использовать и в Series37–38, если заметить, что ни один из внутренних элементов возрастающего или убывающего набора не может быть зубцом (элемент называется внутренним, если он имеет двух соседей: слева и справа). Таким образом, для решения Series37 достаточно подсчитать количество наборов, не имеющих внутренних зубцов. При решении Series38 для упорядоченных наборов требуется дополнительно выяснить «способ упорядоченности»: по возрастанию или убыванию. Для этого достаточно сравнить два последних элемента набора (эти элементы сохранятся во вспомогательных переменных после выхода из вложенного цикла).


Модуль 1. Обработка последовательностей

Справочные сведения

Предварительные замечания

Приведенные сведения соответствуют .NET Framework 1.1–3.0. Дополнения, внесенные в более поздние версии (начиная с версии 2.0), особо оговариваются. При описании классов не рассматриваются свойства и методы, связанные с многопоточностью и синхронизацией.

Заголовки перегруженных вариантов методов обычно объединяются; при этом используются вспомогательные группирующие символы [ и ], обрамляющие параметры, которые могут отсутствовать. Например, если для некоторого класса определен метод M1, имеющий перегруженные варианты void M1(int a); void M1(int a, int b); void M1(int a, int b, double c), то заголовок этого метода будет указан следующим образом:

void M1(int a[, int b[, double c]]);

Для метода M2 с вариантами int M2(string s); int M2(string s, double d); int M2(string s, char c); int M2(string s, double d, char c) заголовок будет иметь вид

int M2(string s[, double d][, char c]);

При описании экземплярных методов класса используется ключевое слово языка C# this; оно обозначает тот объект, для которого был вызван описываемый метод. Для обозначения нулевой ссылки используется ключевое слово языка C# null. Методы, называемые в языке C# статическими (описываются с помощью модификатора static), называются классовыми методами (как и в основном содержании каждого модуля), поскольку они должны вызываться не для отдельных экземпляров класса, а для класса в целом.

Элементарные типы языка C#

Общие сведения

К элементарным типам относятся типы данных, для которых предусмотрена специальная поддержка языка, упрощающая их описание и использование. Элементарным типам языков платформы .NET соответствуют типы библиотеки FCL. В приведенной ниже таблице перечисляются те элементарные типы языка C#, которые упоминаются в настоящем пособии.

C# | Описание | Тип FCL
object | базовый предок всех типов FCL | System.Object
bool | логическое значение: true или false | System.Boolean
byte | 8-разрядное беззнаковое целое | System.Byte
int | 32-разрядное знаковое целое | System.Int32
long | 64-разрядное знаковое целое | System.Int64
double | 64-разрядное вещественное число | System.Double
char | 16-разрядный символ Unicode | System.Char
string | Unicode-строка | System.String

Ключевые слова C#, указанные в первом столбце таблицы, являются синонимами полного имени соответствующего типа FCL (это имя указано в третьем столбце). Таким образом, следующие описания эквивалентны:

//Вариант 1
int a;
double b;

//Вариант 2
using System;
. . .
Int32 a;
Double b;

//Вариант 3
System.Int32 a;
System.Double b;

В настоящем пособии всегда применяются описания, использующие ключевые слова языка; аргументация за и против такого подхода приводится в [2], п. 6.14.

Все элементарные типы, кроме object и string, являются структурами, то есть размерными типами (value types). В C# структуры описываются с помощью ключевого слова struct. Объекты размерных типов размещаются в программном стеке. Кроме структур размерными типами являются перечисления (enum в C#).

Большинство типов, описанных в библиотеке FCL, являются ссылочными типами (reference types). Объект ссылочного типа размещается в динамической памяти, а переменная ссылочного типа содержит ссылку на ту область памяти, в которой размещен связанный с нею объект. Все классы (class в C#) являются ссылочными типами.

Заметим, что объекты размерного типа могут быть преобразованы к ссылочному типу и размещены в динамической памяти. Такое действие называется упаковкой (boxing) — см. [6], гл. 5, с. 117.

Тип object

Класс object является общим предком всех классов, структур и перечислений, определенных в библиотеке FCL. Объекты данного класса практически никогда не создаются, хотя в классе предусмотрен конструктор без параметров. Кратко опишем все методы этого класса.

virtual bool Equals(object obj);
static bool Equals(object objA, object objB);

Возвращает true, если сравниваемые объекты являются равными, и false в противном случае. В экземплярном методе сравниваются объекты this и obj, в классовом методе — objA и objB. Экземплярный метод обычно переопределяется в типах-потомках; в нем, как правило, проводится попарное сравнение полей сравниваемых объектов. Выполнение классового метода сводится в вызову переопределенного экземплярного метода.

static bool ReferenceEquals(object objA, object objB);

Возвращает true, если объекты objA и objB являются тождественными (то есть двумя ссылками на один и тот же объект), и false в противном случае.

Type GetType();

Возвращает объект типа Type, содержащий информацию о фактическом типе объекта this.

virtual int GetHashCode();

Возвращает целочисленный хеш-код для данного объекта, используемый при хранении объектов в хеш-таблицах. Хеш-код должен удовлетворять двум условиям: (1) равные объекты должны иметь одинаковый хеш-код, (2) значения хеш-кодов для различных объектов данного типа должны быть по возможности равномерно распределены в некотором диапазоне (последнее условие повышает производительность хеш-таблиц). Напрямую в программах данный метод обычно не используется. Рекомендуется переопределять его во всех типах- потомках, для которых переопределяется метод Equals.

virtual string ToString();

Возвращает строковое представление объекта this. Его реализация в классе object возвращает полное имя типа (то есть имя типа, дополненное именем пространства имен, например, System.Object). Переопределяется для многих потомков класса object, в частности, для всех элементарных типов.

Числовые типы: byte, int, long, double

Преобразование числовых типов

Преобразование числовых типов может выполняться неявно (то есть автоматически) или явно. Для явного преобразования типов в языке C# предназначен оператор преобразования типа: (тип)выражение. Здесь тип обозначает имя типа, к которому должно быть преобразовано указанное выражение.

При выполнении явного преобразования вещественных данных к целому типу в C# возвращается ближайшее целое «по направлению к нулю». Например, любое из приведенных ниже выражений вернет целое число 0:

(int)0.4
(int)0.9
(int)-0.9

Если требуемое преобразование не может привести к потере информации, то оно может быть выполнено неявно (например, тип byte может быть неявно преобразован в int, тип int — в long, любой целочисленный тип — в double). Неявные преобразования, приводящие к потере информации, в языке C# запрещены.

Поля и методы числовых типов

Текст числовой_тип, используемый в заголовках полей и методов, обозначает любой из рассматриваемых в книге числовых типов.

static const числовой_тип MaxValue;
static const числовой_тип MinValue;

Возвращает соответственно максимальное и минимальное значение для данного числового типа (например, int.MaxValue равно 2147483647, а double.MaxValue равно 1.79769313486232E+308).

static числовой_тип Parse(string value[, IFormatProvider p]);

Преобразует строку value, содержащую изображение числа, в число соответствующего типа. В качестве параметра p можно использовать объект типа CultureInfo, определенный в пространстве имен System.Globalization; это позволяет учитывать в методе Parse различные региональные настройки. Например, если в качестве параметра p указать new CultureInfo("en-US"), то будут использоваться форматные настройки для США (строка "ru-RU" соответствует настройкам для России). Если параметр p не указан, то используются текущие региональные настройки; по умолчанию они определяются настройками системы Windows.

Имеется возможность изменить текущие региональные настройки, используемые в программе. Для этого достаточно присвоить новое значение типа CultureInfo свойству CurrentCulture объекта Thread.CurrentThread, определенного в пространстве имен System.Threading. Например, чтобы задать в качестве текущих настроек программы настройки для США, достаточно выполнить следующий оператор присваивания:

System.Threading.Thread.CurrentThread.CurrentCulture =
    new System.Globalization.CultureInfo("en-US");

string ToString([string fmt][, IFormatProvider p]);

Возвращает строковое представление объекта this. Позволяет указывать форматную строку fmt и объект p, определяющий региональные настройки (см. описание метода Parse). Различные спецификаторы формата, используемые в форматной строке, приводятся в разделе, посвященном форматированию. Если параметр fmt не указан или равен null, то выполняется форматирование по умолчанию. Если параметр p не указан или равен null, то используются текущие региональные настройки.

Следующие классовые неизменяемые поля и методы определены только для вещественных типов. Приведем их заголовки для типа double.

static const double Epsilon;
static const double NaN;
static const double NegativeInfinity;
static const double PositiveInfinity;

Определяют числовые константы, соответствующие особым значениям: Epsilon определяет минимальное положительное вещественное число, NegativeInfinity и PositiveInfinity определяют значение «минус бесконечность» (–¥) и «плюс бесконечность» (+¥) соответственно (эти значения могут быть получены, например, при делении отрицательного или положительного числа на 0), NaN определяет значение «не число» (Not a Number; это значение, например, является результатом деления 0 на 0). Подчеркнем, что при выполнении арифметических действий над вещественными числами никогда не возбуждается исключений, однако результатом может быть одно из особых значений, приведенных выше.

static bool IsInfinity(double value);
static bool IsNaN(double value);
static bool IsNegativeInfinity(double value);
static bool IsPositiveInfinity(double value);
Позволяют проверить, содержит ли параметр value особое значение требуемого вида (см. описание особых значений для вещественных чисел).

Стандартные математические функции: класс Math

Стандартные математические функции для любых числовых типов реализованы в библиотеке FCL в виде классовых методов класса Math, определенного в пространстве имен System. Кроме классовых методов данный класс имеет два классовых неизменяемых поля типа double:

  • E — константа e, равная 2.71828182845905;
  • PI — константа pi, равная 3.14159265358979.

Все методы класса Math можно разбить на три группы. К первой группе относятся четыре метода, которые реализованы для большинства числовых типов.

static числовой_тип Min(числовой_тип val1, числовой_тип val2);
static числовой_тип Max(числовой_тип val1, числовой_тип val2);
static числовой_тип Abs(числовой_тип value);
static int Sign(числовой_тип value);

Методы Min и Max возвращают соответственно меньший и больший из своих параметров, метод Abs возвращает модуль (абсолютное значение) параметра value, а метод Sign возвращает –1 для отрицательных параметров value, 0 для нулевых и 1 для положительных.

Для каждого варианта перегруженных методов Min, Max, Abs возвращаемый тип совпадает с типом параметров, а для методов Min и Max, кроме того, совпадают типы параметров. Поскольку тип byte является беззнаковым, для него не определены варианты методов Abs и Sign.

Вторая группа методов связана с обработкой целых чисел.

static long BigMul(int a, int b);
static int DivRem(int a, int b, out int rem);
static long DivRem(long a, long b, out long rem);

Метод BigMul обеспечивает правильное перемножение двух чисел типа int в ситуации, когда результат этого перемножения выходит за пределы допустимого для int диапазона значений.

Метод DivRem выполняет деление нацело параметра a на параметр b. Возвращаемым значением является частное, а в выходном параметре rem возвращается остаток. Если параметр b равен 0, то возбуждается исключение DivideByZeroException. Интересно отметить, что метод DivRem является единственным методом библиотеки FCL (из рассмотренных в настоящем пособии), в котором для параметра используется модификатор out, означающий передачу данного параметра по ссылке.

Наконец, третья, самая многочисленная, группа методов класса Math связана с обработкой вещественных чисел.

static double Round(double value[, int d][, MidpointRounding mode]);

Возвращает значение числа value, округленное до ближайшего числа с d дробными знаками. Если параметр d не указан, то он считается равным 0, то есть число value округляется до целого. В случае значения value, равного NaN или ±¥, возвращается само значение value.

Параметр mode перечислимого типа MidpointRounding определяет способ округления в ситуации, когда число value находится в точности посредине между возможными округленными значениями. Если mode равен MidpointRounding.ToEven, то подобные числа округляются так, чтобы их последняя цифра была четной («банковское округление»); если mode равен MidpointRounding.AwayFromZero, то числа округляются «по направлению от нуля», то есть из двух возможных округленных значений выбирается более удаленное от 0. Если параметр mode не указан, то выполняется банковское округление. Заметим, что тип MidpointRounding и варианты с параметром mode доступны в .NET Framework, начиная с версии 2.0.

Для остальных методов, связанных с обработкой вещественных чисел, достаточно привести краткие описания (все параметры и возвращаемые значения этих методов имеют тип double).

  • IEEERemainder(x, y) — остаток от деления числа x на число y, понимаемый как число, равное x - y * Round(x/y) (название метода связано с аббревиатурой организации, которая в 1985 г. разработала стандарт выполнения операций над вещественными числами, используемый в .NET Framework: IEEE — Institute of Electrical and Electronics Engineers).
  • Ceiling(x), Floor(x) — округление числа x соответственно с избытком и с недостатком (Ceiling возвращает ближайшее к x целое число, большее или равное x, а Floor — меньшее или равное x). Для x, равного NaN или ±¥, возвращается x. Подчеркнем, что возвращаемое значение имеет тип double.
  • Sqrt(x) — квадратный корень из x. Если x отрицателен или равен NaN, то возвращается NaN, если х равен +¥, то возвращается +¥.
  • Pow(x, y) — степенная функция xy. Если x или y равен NaN, то возвращается NaN; если x равен 1, а y не равен NaN, то возвращается 1; для конечных отрицательных x функция правильно обрабатывает случай целых показателей y. Особые ситуации, связанные с бесконечными значениями, рассматривать не будем.
  • Exp(x) — показательная функция ex. Если x равен NaN или +¥, то возвращается x, если x равен –¥, то возвращается 0.
  • Log(x), Log10(x), Log(x, a) — логарифмические функции по основанию e, 10 и a соответственно: ln x, lg x, logax. Если x < 0, то возвращается NaN, если x равен 0 или +¥, то при a > 1 возвращается соответственно -¥ и +¥, а при a, принадлежащем интервалу (0, 1), возвращается +¥ и -¥. Если x равен NaN, то возвращается NaN. Если a < 0, a = 1 или a = NaN, то возвращается NaN. Другие особые ситуации рассматривать не будем.
  • Sin(x), Cos(x), Tan(x) — тригонометрические функции синус, косинус и тангенс; x измеряется в радианах. Если x равен NaN или ±¥, то возвращается NaN. Функция Tan для конечных x всегда принимает конечные значения (например, для x = ±PI/2 функция Tan возвращает ±1,63317787283838E+16).
  • Asin(x), Acos(x), Atan(x) — обратные тригонометрические функции арксинус, арккосинус и арктангенс; возвращаемые значения измеряются в радианах и лежат в следующих промежутках: [–PI/2, PI/2] для Asin и Atan, [0, PI] для Acos. Особые ситуации для Asin и Acos: если x не принадлежит промежутку [–1, 1], то возвращается NaN. Особые ситуации для Atan: если x = NaN, то возвращается NaN, если x = ±¥, то возвращается ±PI/2.
  • Atan2(y, x) — угол наклона радиус-вектора с координатами {x, y} к положительной полуоси Ox, измеряемый в радианах и лежащий в промежутке (–PI, PI]. Допускаются как конечные, так и бесконечные координаты. Если один или оба параметра равны NaN, то возвращается NaN.
  • Sinh(x), Cosh(x), Tanh(x) — гиперболические функции. Если x равен NaN, то возвращается NaN; если x равен ±¥, то Sinh возвращает ±¥, Cosh возвращает +¥, а Tanh возвращает ±1.

Генерация случайных чисел: класс Random

Для генерации случайных чисел (как целых, так и вещественных) предназначен класс Random, определенный в пространстве имен System.

Random([int seed]);

Данный конструктор создает объект типа Random — датчик равномерно распределенных псевдослучайных чисел — и инициализирует его с помощью целочисленного параметра seed. Если параметр seed не указан, то датчик инициализируется значением, полученным на основе текущего времени (по часам компьютера). Объекты типа Random, инициализированные с помощью одинаковых значений seed, будут генерировать одинаковые последовательности случайных чисел.

int Next([[int min,] int max]);

Возвращает очередное случайное число типа int. Если параметры отсутствуют, то число выбирается из диапазона [0, int.MaxValue) (int.MaxValue — максимальное значение типа int); если указан только параметр max, то число выбирается из диапазона [0, max); если указаны два параметра, то число выбирается из диапазона [min, max). Левая граница диапазона включается в него, а правая — нет. Если границы диапазона равны, то всегда возвращается значение левой границы диапазона. Если значение левой границы больше значения правой, то возбуждается исключение.

double NextDouble();
Возвращает очередное случайное число типа double, лежащее в полуинтервале [0, 1).
void NextBytes(byte[] buffer);

Заполняет все элементы массива байтов buffer случайными целыми числами из диапазона 0–255 (включая границы диапазона). Если параметр buffer равен null, то возбуждается исключение.


Модуль 1. Обработка последовательностей

Контрольные вопросы

Для каждого вопроса щелкните мышью на радиокнопке с номером варианта ответа, который, по вашему мнению, является правильным. Во всех фрагментах кода предполагается, что в программе указаны операторы using со всеми требуемыми пространствами имен. После того как вы ответите на все вопросы, проверьте правильность ответов, нажав на кнопку «Проверка».

1. Какой оператор языка C# предназначен для досрочного завершения текущей итерации цикла?

(1) | continue | (2) | break
(3) | return | (4) | finally

2. Укажите ошибочное утверждение.

(1) | В языке C# переменные могут быть описаны внутри блока | (2) | В языке C# локальные переменные по умолчанию инициализируются нулевыми значениями
(3) | В языке C# допускается явно инициализировать локальные переменные в момент их описания | (4) | В языке C# переменная цикла for может быть описана в его заголовке

3. Укажите выражение, позволяющее получить из вещественного числа x его целочисленное округленное значение (к ближайшему целому)

(1) | Math.Round(x) | (2) | (int)Math.Round(x)
(3) | Math.Round((int)x) | (4) | (int)x

4. Что произойдет при обработке следующего оператора:

Console.WriteLine(Math.Sqrt(-1));
(1) | При компиляции будет выведено сообщение об ошибке | (2) | При выполнении будет возбуждена исключительная ситуация
(3) | При выполнении будет выведен текст NaN | (4) | При выполнении будет выведен текст I

5. Укажите оператор, позволяющий в консольном приложении ввести с клавиатуры вещественное число и записать его в вещественную переменную x.

(1) | Console.Read(x); | (2) | x = Console.ReadDouble();
(3) | x = double.Parse(Console.ReadLine()); | (4) | x = (double)Console.ReadLine();

6. Укажите оператор, обеспечивающий вывод вещественного числа x с двумя дробными знаками после запятой.

(1) | Console.WriteLine("{f2}", x); | (2) | Console.WriteLine("{0,f2}", x);
(3) | Console.WriteLine("{0:f2}", x); | (4) | Console.WriteLine("{0,2}", x);


Модуль 1. Обработка последовательностей

Итоговое задание

Ввести с клавиатуры положительное целое число N (при вводе отрицательного или нулевого числа положить N равным 10). Сгенерировать с помощью датчика случайных чисел набор из N вещественных чисел, лежащих в диапазоне от –100 до 100. Вывести каждое из чисел на отдельной экранной строке, перед каждым числом вывести его порядковый номер (нумеровать числа от 1), а после числа — его квадрат и его округленное значение (как целое). Для вывода каждого из целых чисел использовать 5 экранных позиций; для вывода каждого из вещественных чисел использовать 10 экранных позиций, из которых 2 позиции отвести под вывод дробной части.

После вывода всех чисел вывести на экран следующие данные:

  • сумму всех чисел из исходного набора;
  • сумму всех положительных чисел из исходного набора;
  • количество отрицательных чисел из исходного набора;
  • номер, с которого начинается самая длинная последовательность положительных чисел, и количество чисел в этой последовательности (если набор исходных чисел содержит несколько последовательностей положительных чисел максимальной длины, то вывести номер начального элемента первой из таких последовательностей; если набор не содержит положительных чисел, то дважды вывести 0).

При выполнении задания не использовать массивы.

Приведем образец текста, который должен выводиться программой, выполняющей проектное задание:

N = 16
    1     88,25   7788,02   88
    2    -71,33   5087,51  -71
    3     -0,15      0,02    0
    4    -84,26   7100,58  -84
    5      3,62     13,09    4
    6    -23,15    535,93  -23
    7     52,57   2763,29   53
    8      5,05     25,53    5
    9    -59,89   3586,42  -60
   10    -45,05   2029,24  -45
   11     54,85   3008,20   55
   12     44,99   2023,93   45
   13    -68,60   4706,36  -69
   14     68,08   4635,33   68
   15     74,14   5496,01   74
   16    -80,74   6518,40  -81
Сумма чисел: -41,63
Сумма положит. чисел: 391,54
Кол-во отрицат. чисел: 8
Макс. посл. положит. чисел: начало 7, длина 2


Модуль 2. Одномерные и двумерные массивы

Комплексная цель

Ознакомиться с правилами языка C#, связанными с описанием и использованием одномерных и двумерных массивов. Изучить базовые алгоритмы, связанные с формированием, анализом и преобразованием одномерных и двумерных массивов, включая их сортировку. Освоить основные методы классов Array и ArrayList стандартной библиотеки .NET. Ознакомиться с возможностями цикла foreach.

Структура модуля

  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание


Модуль 2. Одномерные и двумерные массивы

Решения типовых задач

Ввод и вывод элементов массива: Array7

Array7. Дан массив размера N. Вывести его элементы в обратном порядке.

Данное задание является очень простым. Его решение приводится лишь для того, чтобы показать, как описываются массивы и организуется ввод и вывод их элементов. Как и в предыдущих примерах решений, будем приводить только операторы, входящие в функцию Solve.

Task("Array7");
double[] a = new double[GetInt()];
for (int i = 0; i < a.Length; i++)
    a[i] = GetDouble();
for (int i = 0; i < a.Length; i++)
    Put(a[a.Length - i - 1]);

Обратите внимание на то, что прочитанный функцией GetInt размер массива N не сохраняется в переменной, а сразу используется в операторе описания массива a; в дальнейшем размер массива определяется с помощью его свойства Length. Обратите также внимание на то, что в C# при создании массива указывается размер массива, то есть число его элементов.

Элементы массивов в C# всегда индексируются от 0.

Подробности, связанные с описанием и инициализацией массивов, см. в справочных сведениях.

При выводе элементов массива в обратном порядке можно было бы также воспользоваться циклом с убывающим параметром:

for (int i = a.Length - 1; i >= 0; i--)
    Put(a[i]);

Следует, однако, иметь в виду, что в программах нередко приходится организовывать перебор элементов в обратном порядке в цикле с возрастающим параметром (см., например, второй вариант решения задания String10 в модуле № 3).

Анализ элементов массива: Array47

Array47. Дан целочисленный массив размера N. Найти количество различных элементов в данном массиве.

Task("Array47");
int[] a = new int[GetInt()];
int k = 1;
for (int i = 0; i < a.Length; i++)
    a[i] = GetInt();
for (int i = 1; i < a.Length; i++)
{
    int k0 = 1;
    for (int j = 0; j < i; j++)
        if (a[j] == a[i])
        {
            k0 = 0;
            break;
        }
    k += k0;
}
Put(k);

Количество различных элементов массива a подсчитывается в переменной k. При анализе элемента a[i] организуется просмотр всех предыдущих элементов (с индексами от 0 до i – 1), и если обнаруживается элемент, совпадающий с a[i], то просмотр немедленно прекращается, и значение вспомогательной переменной k0 сбрасывается в 0. Если элементы, предшествующие элементу a[i], не совпадают с ним, то переменная k0 сохраняет начальное значение, равное 1, которое добавляется к переменной k. Поскольку переменная k0 используется только внутри цикла по i, она описана и инициализирована именно в этом цикле (подобные переменные, описанные внутри блока операторов и, следовательно, имеющие область действия только в пределах этого блока, называются блочными).

Приведенное выше «традиционное» решение можно существенно сократить, если воспользоваться методом IndexOf класса Array:

Task("Array47");
int[] a = new int[GetInt()];
int k = 1;
for (int i = 0; i < a.Length; i++)
    a[i] = GetInt();
for (int i = 1; i < a.Length; i++)
    if (Array.IndexOf(a, a[i], 0, i) == -1)
        k++;
Put(k);

В программе использован вариант метода IndexOf, в котором поиск элемента, равного a[i], проводится только среди предшествующих ему элементов.

Преобразование массива: Array79

Array79. Дан массив размера N. Осуществить сдвиг элементов массива вправо на одну позицию (при этом A1 перейдет в A2, A2 — в A3, …, AN–1 — в AN, a исходное значение последнего элемента будет потеряно). Первый элемент полученного массива положить равным 0.

Task("Array79");
double[] a = new double[GetInt()];
for (int i = 0; i < a.Length; i++)
    a[i] = GetDouble();
for (int i = a.Length - 2; i >= 0; i--)
    a[i + 1] = a[i];
a[0] = 0;
for (int i = 0; i < a.Length; i++)
    Put(a[i]);

Обработка элементов при правом сдвиге выполняется с конца массива, чтобы преждевременно не стереть исходные значения изменяемых элементов.

При преобразовании массива часто оказываются полезными методы, предусмотренные в классе ArrayList (данный класс реализует возможности динамического массива; подробное описание класса ArrayList см. в справочных сведениях). Класс ArrayList определен в пространстве имен System.Collection, поэтому в начале текста программы укажем следующий оператор (этот оператор необходим для того, чтобы можно было при указании имени класса ArrayList опускать имя его пространства имен):

using System.Collections;

С помощью методов Insert и RemoveAt класса ArrayList правый сдвиг элементов можно выполнить двумя операторами, не используя цикл:

Task("Array79");
ArrayList a = new
    ArrayList(GetInt());
for (int i = 0; i < a.Capacity; i++)
    a.Add(GetDouble());
a.RemoveAt(a.Count - 1);
a.Insert(0, 0.0);
for (int i = 0; i < a.Count; i++)
    Put(a[i]);

В конструкторе ArrayList указывается начальная емкость Capacity массива; число фактически содержащихся в нем элементов Count при этом остается равным нулю. Метод Add, выполняемый в первом цикле, заполняет динамический массив a исходными элементами. Обратите внимание на указание вещественного нуля (0.0) в методе Insert: если бы был указан целочисленный нуль, то в динамический массив a был бы добавлен элемент целого типа, что привело бы к сообщению задачника о неправильном типе одного из элементов полученного массива.

Серии целых чисел: Array116

Array116. Дан целочисленный массив A размера N. Назовем серией группу подряд идущих одинаковых элементов, а длиной серии — количество этих элементов (длина серии может быть равна 1). Сформировать два новых целочисленных массива B и C одинакового размера, записав в массив B длины всех серий исходного массива, а в массив C — значения элементов, образующих эти серии.

Задание Array116 является первым из заданий группы Array, посвященных обработке серий, то есть последовательностей одинаковых элементов. Для выделения серий в массиве может быть использовано простое наблюдение: элемент массива является началом серии, если его значение не равно значению предыдущего элемента (кроме того, началом серии является первый элемент массива).

Task("Array116");
int[] a = new int[GetInt()],
    b = new int[a.Length],
    c = new int[a.Length];
int k = 1;
for (int i = 0; i < a.Length; i++)
    a[i] = GetInt();
b[0] = 1;
c[0] = a[0];
for (int i = 1; i < a.Length; i++)
    if (a[i - 1] == a[i])
        b[k - 1]++;
    else
    {
        b[k] = 1;
        c[k] = a[i];
        k++;
    }
for (int i = 0; i < k; i++)
    Put(b[i]);
for (int i = 0; i < k; i++)
    Put(c[i]);

В переменной k хранится информация о количестве заполненных элементов массивов b и c. Поскольку число серий массива a может находиться в диапазоне от 1 до a.Length, размер массивов b и c задается «по максимуму», равному a.Length.

В ситуации, когда размер результирующих массивов неизвестен, удобно использовать класс ArrayList, уже упоминавшийся в предыдущем пункте. Напомним, что данный класс реализует возможности динамических массивов и определен в пространстве имен System.Collection, которое надо указать в начале программы, используя оператор using. Приведем вариант решения задания Array116, использующий для хранения результирующих данных динамические массивы типа ArrayList.

Task("Array116");
int[] a = new int[GetInt()];
ArrayList
    b = new ArrayList(a.Length),
    c = new ArrayList(a.Length);
for (int i = 0; i < a.Length; i++)
    a[i] = GetInt();
b.Add(1);
c.Add(a[0]);
for (int i = 1; i < a.Length; i++)
    if (a[i - 1] == a[i])
        b[b.Count - 1] = (int)b[b.Count - 1] + 1;
    else
    {
        b.Add(1);
        c.Add(a[i]);
    }
for (int i = 0; i < b.Count; i++)
    Put(b[i]);
for (int i = 0; i < c.Count; i++)
    Put(c[i]);

Следует обратить внимание на оператор, увеличивающий значение последнего элемента динамического массива b на 1: для выполнения указанного действия необходимо явно преобразовать элемент к целому типу (если этого не сделать, то произойдет ошибка компиляции, так как типом элемента динамического массива на этапе компиляции будет считаться базовый тип object, для которого операция сложения не определена).

Обратите также внимание на использованный вариант конструктора класса ArrayList: хотя данный класс имеет конструктор без параметров, рекомендуется использовать вариант, в котором явно указывается требуемая емкость, поскольку это позволит уменьшить количество операций выделения памяти при работе с динамическим массивом. В нашем случае была указана емкость, равная размеру исходного массива; такая начальная емкость будет достаточна для любого возможного числа серий, имеющихся в исходном массиве, следовательно, при выполнении программы для массивов b и c не потребуется дополнительного выделения памяти.

В заключение отметим, что перебор всех элементов динамического массива более эффективно выполнять с помощью специального цикла foreach. Приведем вариант вывода элементов динамических массивов b и c, использующий вместо цикла for цикл foreach:

foreach (int i in b)
    Put(i);
foreach (int i in c)
    Put(i);
Параметр цикла i в данном случае последовательно принимает значения всех элементов массива, указанного после служебного слова in. Как и для цикла for, параметр цикла foreach можно описать либо в самом заголовке цикла (как это сделано в приведенном фрагменте программы), либо до цикла. Тип параметра цикла foreach должен быть совместим с типом элементов массива (при нарушении этого условия произойдет ошибка времени выполнения). Отметим, что цикл foreach не только записывается более кратко, но и выполняется для динамических массивов быстрее, чем цикл for. В то же время для «обычных» массивов более эффективным является перебор элементов в цикле for, хотя для них также допустимо использовать цикл foreach (см. [2], п. 22.9, а также раздел о цикле foreach в справочных сведениях).

Ввод и вывод элементов матрицы: Matrix7

Matrix7. Дана матрица размера M ´ N и целое число K (1 £ K £ M). Вывести элементы K-й строки данной матрицы.

Данное задание (подобно ранее рассмотренному заданию Array7) демонстрирует, как описать двумерный массив (матрицу) и организовать ввод и вывод его элементов.

Task("Matrix7");
double[,] a = new double[GetInt(), GetInt()];
for (int i = 0; i < a.GetLength(0); i++)
    for (int j = 0; j < a.GetLength(1); j++)
        a[i, j] = GetDouble();
int k = GetInt() - 1;
for (int j = 0; j < a.GetLength(1); j++)
    Put(a[k, j]);

Заметим, что из всей исходной матрицы нас интересует только одна строка. Однако мы вынуждены прочесть в программе все элементы матрицы, так как только после ввода элементов матрицы можно получить доступ к элементу исходных данных, содержащему номер K требуемой строки.

Поскольку в учебных заданиях предполагается, что строки и столбцы нумеруются от 1, а индексы в C# начинаются от 0, для получения индекса строки с номером K необходимо из данного номера вычесть 1.

В данном решении, как и в решении Array7, размеры массива-матрицы по каждой размерности не сохраняются в переменных, а сразу передаются в конструктор. В дальнейшем для получения числа строк матрицы используется метод GetLength с параметром 0, а для получения числа столбцов — этот же метод с параметром 1 (свойство Length в решении не используется, так как оно возвращает общее число элементов матрицы). К сожалению, имя метода GetLength является достаточно длинным, и это делает данный вариант решения излишне «многословным». Чтобы получить более краткий текст программы, можно воспользоваться вспомогательными переменными m и n для хранения числа строк и столбцов:

Task("Matrix7");
int m = GetInt(), n = GetInt();
double[,] a = new double[m, n];
for (int i = 0; i < m; i++)
    for (int j = 0; j < n; j++)
        a[i, j] = GetDouble();
int k = GetInt() - 1;
for (int j = 0; j < n; j++)
    Put(a[k, j]);

Для решения заданий на матрицы можно также воспользоваться невыровненными массивами, то есть массивами массивов. Приведем соответствующий вариант решения задания Matrix7.

Task("Matrix7");
double[][] a = new double[GetInt()][];
int n = GetInt();
for (int i = 0; i < a.Length; i++)
{
    a[i] = new double[n];
    for (int j = 0; j < n; j++)
        a[i][j] = GetDouble();
}
int k = GetInt() - 1;
for (int j = 0; j < n; j++)
    Put(a[k][j]);

Создание двумерного невыровненного массива выполняется в два этапа: вначале создается одномерный массив, элементами которого являются одномерные массивы (строки матрицы), а затем в цикле создаются сами строки, после чего они заполняются исходными данными. Обратите внимание на то, что для элементов невыровненных массивов каждый индекс надо указывать в отдельных скобках.

Несмотря на более сложное создание, невыровненные массивы обладают рядом преимуществ по сравнению с «обычными» двумерными массивами (см. [2], п. 22.6). Прежде всего, они быстрее обрабатываются, так как среда выполнения .NET оптимизирована для доступа к элементам одномерных массивов (из которых и состоят невыровненные массивы). Далее, невыровненные массивы удобны для хранения непрямоугольных наборов данных (пример создания непрямоугольного набора данных приводится в справочных сведениях). Наконец, невыровненные массивы обеспечивают доступ не только к отдельным элементам матрицы, но и к ее строкам, что дает возможность выполнять строковые преобразования (например, перемену местами строк) за одну операцию, без использования цикла.

Диагонали квадратной матрицы: Matrix82

Matrix82. Дана квадратная матрица A порядка M. Найти сумму элементов каждой ее диагонали, параллельной главной (начиная с одноэлементной диагонали A1,M).

Task("Matrix82");
int m = GetInt();
double[,] a = new double[m, m];
for (int i = 0; i < m; i++)
    for (int j = 0; j < m; j++)
        a[i, j] = GetDouble();
for (int k = 1; k <= m; k++)
{
    double s = 0;
    for (int i = 0; i < k; i++)
        s += a[i, m - k + i];
    Put(s);
}
for (int k = m - 1; k >= 1; k--)
{
    double s = 0;
    for (int j = 0; j < k; j++)
        s += a[m - k + j, j];
    Put(s);
}

В данном решении в первом цикле по k обрабатываются те диагонали, которые начинаются в первой строке (их количество равно M, а длины увеличиваются от 1 до M). Во втором цикле обрабатываются оставшиеся диагонали, которые начинаются в первом столбце (количество этих диагоналей равно M – 1, так как главная диагональ уже обработана в первом цикле); длины этих диагоналей уменьшаются от M – 1 до 1.

Приведем второй вариант решения этого задания, использующий следующее наблюдение: если последовательно перенумеровать все диагонали, параллельные главной, так, чтобы первая диагональ (содержащая единственный элемент A1,M) получила номер 0, а последняя (содержащая единственный элемент AM,1) — номер 2M – 2, то определить номер K диагонали, на которой расположен элемент AI,J, можно с помощью простой формулы: K = M + I – J – 1.

Task("Matrix82");
int m = GetInt();
double[,] a = new double[m, m];
double[] b = new double[2 * m - 1];
for (int i = 0; i < m; i++)
    for (int j = 0; j < m; j++)
        a[i, j] = GetDouble();
for (int i = 0; i < m; i++)
    for (int j = 0; j < m; j++)
    {
        int k = m + i - j - 1;
        b[k] += a[i, j];
    }
for (int k = 0; k < 2 * m - 1; k++)
    Put(b[k]);

Поскольку в C# элементы массивов автоматически инициализируются нулевыми значениями, явного обнуления элементов массива b в программе не требуется. Отметим, что в данном варианте решения можно было бы обойтись без использования двумерного массива, поскольку элементы исходной матрицы обрабатываются в том же порядке, в котором считываются.


Модуль 2. Одномерные и двумерные массивы

Учебные задания и указания к ним

Одномерные массивы

Условие вида «дан массив размера N» означает, что вначале дается фактический размер массива (целое число N), а затем приводятся все его элементы. Если в задании явно не указывается, какие значения может принимать размер исходного массива, то предполагается, что размер может изменяться в пределах от 2 до 10. Индекс начального элемента массива считается равным 1.

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

При выполнении заданий, связанных с преобразованием массивов, не следует использовать вспомогательные массивы.

Формирование массива и вывод его элементов

В заданиях на формирование массива предполагается, что размер результирующего массива не превосходит 10.

Array1. Дано целое число N (> 0). Сформировать и вывести целочисленный массив размера N, содержащий N первых положительных нечетных чисел: 1, 3, 5, … .

Array2. Дано целое число N (> 0). Сформировать и вывести целочисленный массив размера N, содержащий степени двойки от первой до N-й: 2, 4, 8, 16, … .

Array3. Дано целое число N (> 1), а также первый член A и разность D арифметической прогрессии. Сформировать и вывести массив размера N, содержащий N первых членов данной прогрессии:

A, A + D, A + 2·D, A + 3·D, … .

Array4. Дано целое число N (> 1), а также первый член A и знаменатель D геометрической прогрессии. Сформировать и вывести массив размера N, содержащий N первых членов данной прогрессии:

A, A·D, A·D2, A·D3, … .

Array5. Дано целое число N (> 2). Сформировать и вывести целочисленный массив размера N, содержащий N первых элементов последовательности чисел Фибоначчи FK:

F1 = 1, F2 = 1, FK = FK–2 + FK–1, K = 3, 4, … .

Array6. Даны целые числа N (> 2), A и B. Сформировать и вывести целочисленный массив размера N, первый элемент которого равен A, второй равен B, а каждый последующий элемент равен сумме всех предыдущих.

Array7. Дан массив размера N. Вывести его элементы в обратном порядке.

Array8. Дан целочисленный массив размера N. Вывести все содержащиеся в данном массиве нечетные числа в порядке возрастания их индексов, а также их количество K.

Array9. Дан целочисленный массив размера N. Вывести все содержащиеся в данном массиве четные числа в порядке убывания их индексов, а также их количество K.

Array10. Дан целочисленный массив размера N. Вывести вначале все содержащиеся в данном массиве четные числа в порядке возрастания их индексов, а затем — все нечетные числа в порядке убывания их индексов.

Array11. Дан массив A размера N и целое число K (1 £ K £ N). Вывести элементы массива с порядковыми номерами, кратными K: AK, A2·K, A3·K, … . Условный оператор не использовать.

Array12. Дан массив A размера N (N — четное число). Вывести его элементы с четными номерами в порядке возрастания номеров: A2, A4, A6, …, AN. Условный оператор не использовать.

Array13. Дан массив A размера N (N — нечетное число). Вывести его элементы с нечетными номерами в порядке убывания номеров: AN, AN–2, AN–4, …, A1. Условный оператор не использовать.

Array14. Дан массив A размера N. Вывести вначале его элементы с четными номерами (в порядке возрастания номеров), а затем — элементы с нечетными номерами (также в порядке возрастания номеров):

A2, A4, A6, …, A1, A3, A5, … .

Условный оператор не использовать.

Array15. Дан массив A размера N. Вывести вначале его элементы с нечетными номерами в порядке возрастания номеров, а затем — элементы с четными номерами в порядке убывания номеров:

A1, A3, A5, …, A6, A4, A2.

Условный оператор не использовать.

Array16. Дан массив A размера N. Вывести его элементы в следующем порядке:

A1, AN, A2, AN–1, A3, AN–2, … .

Array17. Дан массив A размера N. Вывести его элементы в следующем порядке:

A1, A2, AN, AN–1, A3, A4, AN–2, AN–3, … .

Анализ элементов массива

Для выполнения некоторых заданий из данного пункта не требуется одновременно хранить в памяти все исходные данные, поэтому использовать при их выполнении массивы, строго говоря, не нужно. Однако применение массивов позволяет сделать алгоритмы решения этих заданий более простыми и наглядными. Задания из данного пункта можно дополнить заданиями из групп Minmax и Series, рассматривая их как задания на обработку массивов. С другой стороны, для тех заданий данного пункта, которые можно выполнить, не используя массивы, полезно реализовать и такие алгоритмы решения.

Array18. Дан массив A ненулевых целых чисел размера 10. Вывести значение первого из тех его элементов AK, которые удовлетворяют неравенству AK < A10. Если таких элементов нет, то вывести 0.

Array19. Дан целочисленный массив A размера 10. Вывести порядковый номер последнего из тех его элементов AK, которые удовлетворяют двойному неравенству A1 < AK < A10. Если таких элементов нет, то вывести 0.

Array20. Дан массив размера N и целые числа K и L (1 £ K £ L £ N). Найти сумму элементов массива с номерами от K до L включительно.

Array21. Дан массив размера N и целые числа K и L (1 £ K £ L £ N). Найти среднее арифметическое элементов массива с номерами от K до L включительно.

Array22. Дан массив размера N и целые числа K и L (1 < K £ L £ N). Найти сумму всех элементов массива, кроме элементов с номерами от K до L включительно.

Array23. Дан массив размера N и целые числа K и L (1 < K £ L £ N). Найти среднее арифметическое всех элементов массива, кроме элементов с номерами от K до L включительно.

Array24. Дан целочисленный массив размера N, не содержащий одинаковых чисел. Проверить, образуют ли его элементы арифметическую прогрессию (см. задание Array3). Если образуют, то вывести разность прогрессии, если нет — вывести 0.

Array25. Дан массив ненулевых целых чисел размера N. Проверить, образуют ли его элементы геометрическую прогрессию (см. задание Array4). Если образуют, то вывести знаменатель прогрессии, если нет — вывести 0.

Array26. Дан целочисленный массив размера N. Проверить, чередуются ли в нем четные и нечетные числа. Если чередуются, то вывести 0, если нет, то вывести порядковый номер первого элемента, нарушающего закономерность.

Array27. Дан массив ненулевых целых чисел размера N. Проверить, чередуются ли в нем положительные и отрицательные числа. Если чередуются, то вывести 0, если нет, то вывести порядковый номер первого элемента, нарушающего закономерность.

Array28. Дан массив A размера N. Найти минимальный элемент из его элементов с четными номерами: A2, A4, A6, … .

Array29. Дан массив A размера N. Найти максимальный элемент из его элементов с нечетными номерами: A1, A3, A5, … .

Array30. Дан массив размера N. Найти номера тех элементов массива, которые больше своего правого соседа, и количество таких элементов. Найденные номера выводить в порядке их возрастания.

Array31. Дан массив размера N. Найти номера тех элементов массива, которые больше своего левого соседа, и количество таких элементов. Найденные номера выводить в порядке их убывания.

Array32. Дан массив размера N. Найти номер его первого локального минимума (локальный минимум — это элемент, который меньше любого из своих соседей).

Array33. Дан массив размера N. Найти номер его последнего локального максимума (локальный максимум — это элемент, который больше любого из своих соседей).

Array34. Дан массив размера N. Найти максимальный из его локальных минимумов (определение локального минимума дано в задании Array32).

Array35. Дан массив размера N. Найти минимальный из его локальных максимумов (определение локального максимума дано в задании Array33).

Array36. Дан массив размера N. Найти максимальный из его элементов, не являющихся ни локальным минимумом, ни локальным максимумом (определения локального минимума и локального максимума даны в заданиях Array32 и Array33). Если таких элементов в массиве нет, то вывести 0 (как вещественное число).

Array37. Дан массив размера N. Найти количество участков, на которых его элементы возрастают.

Array38. Дан массив размера N. Найти количество участков, на которых его элементы убывают.

Array39. Дан массив размера N. Найти количество его промежутков монотонности (то есть участков, на которых его элементы возрастают или убывают).

Array40. Дано число R и массив A размера N. Найти элемент массива, который наиболее близок к числу R (то есть такой элемент AK, для которого величина |AK – R| является минимальной).

Array41. Дан массив размера N. Найти два соседних элемента, сумма которых максимальна, и вывести эти элементы в порядке возрастания их индексов.

Array42. Дано число R и массив размера N. Найти два соседних элемента массива, сумма которых наиболее близка к числу R, и вывести эти элементы в порядке возрастания их индексов (определение наиболее близких чисел дано в задании Array40).

Array43. Дан целочисленный массив размера N, все элементы которого упорядочены (по возрастанию или по убыванию). Найти количество различных элементов в данном массиве.

Array44. Дан целочисленный массив размера N, содержащий ровно два одинаковых элемента. Найти номера одинаковых элементов и вывести эти номера в порядке возрастания.

Array45. Дан массив размера N. Найти номера двух ближайших элементов из этого массива (то есть элементов с наименьшим модулем разности) и вывести эти номера в порядке возрастания.

Array46. Дано число R и массив размера N. Найти два различных элемента массива, сумма которых наиболее близка к числу R, и вывести эти элементы в порядке возрастания их индексов (определение наиболее близких чисел дано в задании Array40).

Array47. Дан целочисленный массив размера N. Найти количество различных элементов в данном массиве.

Array48. Дан целочисленный массив размера N. Найти максимальное количество его одинаковых элементов.

Array49. Дан целочисленный массив размера N. Если он является перестановкой, то есть содержит все числа от 1 до N, то вывести 0; в противном случае вывести номер первого недопустимого элемента.

Array50. Дан целочисленный массив A размера N, являющийся перестановкой (определение перестановки дано в задании Array49). Найти количество инверсий в данной перестановке, то есть таких пар элементов AI и AJ, в которых большее число находится слева от меньшего: AI > AJ при I < J.

Работа с несколькими массивами

Array51. Даны массивы A и B одинакового размера N. Поменять местами их содержимое и вывести вначале элементы преобразованного массива A, а затем — элементы преобразованного массива B.

Array52. Дан массив A размера N. Сформировать новый массив B того же размера, элементы которого определяются следующим образом:

BK | = | 2·AK, | если AK < 5,
 |  | AK/2 | в противном случае.

Array53. Даны два массива A и B одинакового размера N. Сформировать новый массив C того же размера, каждый элемент которого равен максимальному из элементов массивов A и B с тем же индексом.

Array54. Дан целочисленный массив A размера N. Переписать в новый целочисленный массив B все четные числа из исходного массива (в том же порядке) и вывести размер полученного массива B и его содержимое.

Array55. Дан целочисленный массив A размера N (£ 15). Переписать в новый целочисленный массив B все элементы с нечетными порядковыми номерами (1, 3, …) и вывести размер полученного массива B и его содержимое. Условный оператор не использовать.

Array56. Дан целочисленный массив A размера N (£ 15). Переписать в новый целочисленный массив B все элементы с порядковыми номерами, кратными трем (3, 6, …), и вывести размер полученного массива B и его содержимое. Условный оператор не использовать.

Array57. Дан целочисленный массив A размера N. Переписать в новый целочисленный массив B того же размера вначале все элементы исходного массива с четными номерами, а затем — с нечетными:

A2, A4, A6, …, A1, A3, A5, … .

Условный оператор не использовать.

Array58. Дан массив A размера N. Сформировать новый массив B того же размера по следующему правилу: элемент BK равен сумме элементов массива A с номерами от 1 до K.

Array59. Дан массив A размера N. Сформировать новый массив B того же размера по следующему правилу: элемент BK равен среднему арифметическому элементов массива A с номерами от 1 до K.

Array60. Дан массив A размера N. Сформировать новый массив B того же размера по следующему правилу: элемент BK равен сумме элементов массива A с номерами от K до N.

Array61. Дан массив A размера N. Сформировать новый массив B того же размера по следующему правилу: элемент BK равен среднему арифметическому элементов массива A с номерами от K до N.

Array62. Дан массив A размера N. Сформировать два новых массива B и C: в массив B записать все положительные элементы массива A, в массив C — все отрицательные (сохраняя исходный порядок следования элементов). Вывести вначале размер и содержимое массива B, а затем — размер и содержимое массива C.

Array63. Даны два массива A и B размера 5, элементы которых упорядочены по возрастанию. Объединить эти массивы так, чтобы результирующий массив C (размера 10) остался упорядоченным по возрастанию.

Array64. Даны три целочисленных массива A, B и C размера NA, NB, NC соответственно, элементы которых упорядочены по убыванию. Объединить эти массивы так, чтобы результирующий целочисленный массив D (размера NA + NB + NC) остался упорядоченным по убыванию.

Изменение элементов массива

Array65. Дан массив A размера N и целое число K (1 £ K £ N). Преобразовать массив, увеличив каждый его элемент на исходное значение элемента AK.

Array66. Дан целочисленный массив размера N. Увеличить все четные числа, содержащиеся в массиве, на исходное значение первого четного числа. Если четные числа в массиве отсутствуют, то оставить массив без изменений.

Array67. Дан целочисленный массив размера N. Увеличить все нечетные числа, содержащиеся в массиве, на исходное значение последнего нечетного числа. Если нечетные числа в массиве отсутствуют, то оставить массив без изменений.

Array68. Дан массив размера N. Поменять местами его минимальный и максимальный элементы.

Array69. Дан массив размера N (N — четное число). Поменять местами его первый элемент со вторым, третий — с четвертым и т. д.

Array70. Дан массив размера N (N — четное число). Поменять местами первую и вторую половины массива.

Array71. Дан массив размера N. Поменять порядок его элементов на обратный.

Array72. Дан массив A размера N и целые числа K и L (1 £ K < L £ N). Переставить в обратном порядке элементы массива, расположенные между элементами AK и AL, включая эти элементы.

Array73. Дан массив A размера N и целые числа K и L (1 £ K < L £ N). Переставить в обратном порядке элементы массива, расположенные между элементами AK и AL, не включая эти элементы.

Array74. Дан массив размера N. Обнулить элементы массива, расположенные между его минимальным и максимальным элементами (не включая минимальный и максимальный элементы).

Array75. Дан массив размера N. Переставить в обратном порядке элементы массива, расположенные между его минимальным и максимальным элементами, включая минимальный и максимальный элементы.

Array76. Дан массив размера N. Обнулить все его локальные максимумы (то есть числа, большие своих соседей).

Array77. Дан массив размера N. Возвести в квадрат все его локальные минимумы (то есть числа, меньшие своих соседей).

Array78. Дан массив размера N. Заменить каждый элемент массива на среднее арифметическое этого элемента и его соседей.

Array79. Дан массив размера N. Осуществить сдвиг элементов массива вправо на одну позицию (при этом A1 перейдет в A2, A2 — в A3, …, AN–1 — в AN, a исходное значение последнего элемента будет потеряно). Первый элемент полученного массива положить равным 0.

Array80. Дан массив размера N. Осуществить сдвиг элементов массива влево на одну позицию (при этом AN перейдет в AN–1, AN–1 — в AN–2, …, A2 — в A1, a исходное значение первого элемента будет потеряно). Последний элемент полученного массива положить равным 0.

Array81. Дан массив размера N и целое число K (1 £ K < N). Осуществить сдвиг элементов массива вправо на K позиций (при этом A1 перейдет в AK+1, A2 — в AK+2, …, AN–K — в AN, а исходное значение K последних элементов будет потеряно). Первые K элементов полученного массива положить равными 0.

Array82. Дан массив размера N и целое число K (1 £ K < N). Осуществить сдвиг элементов массива влево на K позиций (при этом AN перейдет в AN–K, AN–1 — в AN–K–1, …, AK+1 — в A1, а исходное значение K первых элементов будет потеряно). Последние K элементов полученного массива положить равными 0.

Array83. Дан массив размера N. Осуществить циклический сдвиг элементов массива вправо на одну позицию (при этом A1 перейдет в A2, A2 — в A3, …, AN — в A1).

Array84. Дан массив размера N. Осуществить циклический сдвиг элементов массива влево на одну позицию (при этом AN перейдет в AN–1, AN–1 — в AN–2, …, A1 — в AN).

Array85. Дан массив A размера N и целое число K (1 £ K £ 4, K < N). Осуществить циклический сдвиг элементов массива вправо на K позиций (при этом A1 перейдет в AK+1, A2 — в AK+2, …, AN — в AK). Допускается использовать вспомогательный массив из 4 элементов.

Array86. Дан массив A размера N и целое число K (1 £ K £ 4, K < N). Осуществить циклический сдвиг элементов массива влево на K позиций (при этом AN перейдет в AN–K, AN–1 — в AN–K–1, …, A1 — в AN–K+1). Допускается использовать вспомогательный массив из 4 элементов.

Array87. Дан массив размера N, все элементы которого, кроме первого, упорядочены по возрастанию. Сделать массив упорядоченным, переместив первый элемент на новую позицию.

Array88. Дан массив размера N, все элементы которого, кроме последнего, упорядочены по возрастанию. Сделать массив упорядоченным, переместив последний элемент на новую позицию.

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

Удаление и вставка элементов

Array90. Дан массив размера N и целое число K (1 £ K £ N). Удалить из массива элемент с порядковым номером K.

Array91. Дан массив размера N и целые числа K и L (1 £ K < L £ N). Удалить из массива элементы с номерами от K до L включительно и вывести размер полученного массива и его содержимое.

Array92. Дан целочисленный массив размера N. Удалить из массива все нечетные числа и вывести размер полученного массива и его содержимое.

Array93. Дан целочисленный массив размера N (> 2). Удалить из массива все элементы с четными номерами (2, 4, …). Условный оператор не использовать.

Array94. Дан целочисленный массив размера N (> 2). Удалить из массива все элементы с нечетными номерами (1, 3, …). Условный оператор не использовать.

Array95. Дан целочисленный массив размера N. Удалить из массива все соседние одинаковые элементы, оставив их первые вхождения.

Array96. Дан целочисленный массив размера N. Удалить из массива все одинаковые элементы, оставив их первые вхождения.

Array97. Дан целочисленный массив размера N. Удалить из массива все одинаковые элементы, оставив их последние вхождения.

Array98. Дан целочисленный массив размера N. Удалить из массива все элементы, встречающиеся менее трех раз, и вывести размер полученного массива и его содержимое.

Array99. Дан целочисленный массив размера N. Удалить из массива все элементы, встречающиеся более двух раз, и вывести размер полученного массива и его содержимое.

Array100. Дан целочисленный массив размера N. Удалить из массива все элементы, встречающиеся ровно два раза, и вывести размер полученного массива и его содержимое.

Array101. Дан массив размера N и целое число K (1 £ K £ N). Перед элементом массива с порядковым номером K вставить новый элемент с нулевым значением.

Array102. Дан массив размера N и целое число K (1 £ K £ N). После элемента массива с порядковым номером K вставить новый элемент с нулевым значением.

Array103. Дан массив размера N. Вставить элемент с нулевым значением перед минимальным и после максимального элемента массива.

Array104. Дан массив размера N и два целых числа K и M (1 £ K £ N, 1 £ M £ 10). Перед элементом массива с номером K вставить M новых элементов с нулевыми значениями.

Array105. Дан массив размера N и два целых числа K и M (1 £ K £ N, 1 £ M £ 10). После элемента массива с номером K вставить M новых элементов с нулевыми значениями.

Array106. Дан массив размера N. Продублировать в нем элементы с четными номерами (2, 4, …). Условный оператор в цикле не использовать.

Array107. Дан массив размера N. Утроить в нем вхождения всех элементов с нечетными номерами (1, 3, …). Условный оператор в цикле не использовать.

Array108. Дан массив размера N. Перед каждым положительным элементом массива вставить элемент с нулевым значением.

Array109. Дан массив размера N. После каждого отрицательного элемента массива вставить элемент с нулевым значением.

Array110. Дан целочисленный массив размера N. Продублировать в нем все четные числа.

Array111. Дан целочисленный массив размера N. Утроить в нем вхождения всех нечетных чисел.

Сортировка массива

Array112. Дан массив A размера N (£ 6). Упорядочить его по возрастанию методом сортировки простым обменом («пузырьковой» сортировкой): просматривать массив, сравнивая его соседние элементы (A1 и A2, A2 и A3 и т. д.) и меняя их местами, если левый элемент пары больше правого; повторить описанные действия N – 1 раз. Для контроля за выполняемыми действиями выводить содержимое массива после каждого просмотра. Учесть, что при каждом просмотре количество анализируемых пар можно уменьшить на 1.

Array113. Дан массив A размера N (£ 6). Упорядочить его по возрастанию методом сортировки простым выбором: найти максимальный элемент массива и поменять его местами с последним (N-м) элементом; выполнить описанные действия N – 1 раз, каждый раз уменьшая на 1 количество анализируемых элементов и выводя содержимое массива.

Array114. Дан массив A размера N (£ 6). Упорядочить его по возрастанию методом сортировки простыми вставками: сравнить элементы A1 и A2 и, при необходимости меняя их местами, добиться того, чтобы они оказались упорядоченными по возрастанию; затем обратиться к элементу A3 и переместить его в левую (уже упорядоченную) часть массива, сохранив ее упорядоченность; повторить этот процесс для остальных элементов, выводя содержимое массива после обработки каждого элемента (от 2-го до N-го). При выполнении описанных действий удобно использовать прием «барьера», записывая очередной элемент перед его обработкой в дополнительный элемент массива A0.

Array115. Дан массив A размера N. Не изменяя данный массив, вывести номера его элементов в том порядке, в котором соответствующие им элементы образуют возрастающую последовательность. Использовать метод «пузырьковой» сортировки (см. задание Array112), модифицировав его следующим образом: создать вспомогательный целочисленный массив номеров I, заполнив его числами от 1 до N; просматривать массив A, сравнивая пары элементов массива A с номерами I1 и I2, I2 и I3, … и меняя местами соответствующие элементы массива I, если левый элемент пары больше правого. Повторив описанную процедуру просмотра N – 1 раз, получим в массиве I требуемую последовательность номеров.

Серии целых чисел

Array116. Дан целочисленный массив A размера N. Назовем серией группу подряд идущих одинаковых элементов, а длиной серии — количество этих элементов (длина серии может быть равна 1). Сформировать два новых целочисленных массива B и C одинакового размера, записав в массив B длины всех серий исходного массива, а в массив C — значения элементов, образующих эти серии.

Array117. Дан целочисленный массив размера N. Вставить перед каждой его серией элемент с нулевым значением (определение серии дано в задании Array116).

Array118. Дан целочисленный массив размера N. Вставить после каждой его серии элемент с нулевым значением (определение серии дано в задании Array116).

Array119. Дан целочисленный массив размера N. Преобразовать массив, увеличив каждую его серию на один элемент (определение серии дано в задании Array116).

Array120. Дан целочисленный массив размера N, содержащий по крайней мере одну серию, длина которой больше 1. Преобразовать массив, уменьшив каждую его серию на один элемент (определение серии дано в задании Array116).

Array121. Дано целое число K (> 0) и целочисленный массив размера N. Преобразовать массив, удвоив длину его серии с номером K (определение серии дано в задании Array116). Если серий в массиве меньше K, то вывести массив без изменений.

Array122. Дано целое число K (> 1) и целочисленный массив размера N. Удалить из массива серию с номером K (определение серии дано в задании Array116). Если серий в массиве меньше K, то вывести массив без изменений.

Array123. Дано целое число K (> 1) и целочисленный массив размера N. Поменять местами первую серию массива и его серию с номером K (определение серии дано в задании Array116). Если серий в массиве меньше K, то вывести массив без изменений.

Array124. Дано целое число K (> 0) и целочисленный массив размера N. Поменять местами последнюю серию массива и его серию с номером K (определение серии дано в задании Array116). Если серий в массиве меньше K, то вывести массив без изменений.

Array125. Дано целое число L (> 1) и целочисленный массив размера N. Заменить каждую серию массива, длина которой меньше L, на один элемент с нулевым значением (определение серии дано в задании Array116).

Array126. Дано целое число L (> 0) и целочисленный массив размера N. Заменить каждую серию массива, длина которой равна L, на один элемент с нулевым значением (определение серии дано в задании Array116).

Array127. Дано целое число L (> 0) и целочисленный массив размера N. Заменить каждую серию массива, длина которой больше L, на один элемент с нулевым значением (определение серии дано в задании Array116).

Array128. Дан целочисленный массив размера N. Преобразовать массив, увеличив его первую серию наибольшей длины на один элемент (определение серии дано в задании Array116).

Array129. Дан целочисленный массив размера N. Преобразовать массив, увеличив его последнюю серию наибольшей длины на один элемент (определение серии дано в задании Array116).

Array130. Дан целочисленный массив размера N. Преобразовать массив, увеличив все его серии наибольшей длины на один элемент (определение серии дано в задании Array116).

Множества точек на плоскости

Для хранения данных о каждом наборе точек следует использовать по два массива: первый массив для хранения абсцисс, второй — для хранения ординат. Можно также использовать массив записей с двумя полями (см. задание Param64).

Array131. Дано множество A из N точек на плоскости и точка B (точки заданы своими координатами x, y). Найти точку из множества A, наиболее близкую к точке B. Расстояние R между точками с координатами (x1, y1) и (x2, y2) вычисляется по формуле:

R = ((x2 – x1)2 + (y2 – y1)2)1/2.

Array132. Дано множество A из N точек (точки заданы своими координатами x, y). Среди всех точек этого множества, лежащих во второй четверти, найти точку, наиболее удаленную от начала координат. Если таких точек нет, то вывести точку с нулевыми координатами.

Array133. Дано множество A из N точек (точки заданы своими координатами x, y). Среди всех точек этого множества, лежащих в первой или третьей четверти, найти точку, наиболее близкую к началу координат. Если таких точек нет, то вывести точку с нулевыми координатами.

Array134. Дано множество A из N точек (точки заданы своими координатами x, y). Найти пару точек этого множества с максимальным расстоянием между ними и само это расстояние (точки выводятся в том же порядке, в котором они перечислены при задании множества A).

Array135. Даны множества A и B, состоящие соответственно из N1 и N2 точек (точки заданы своими координатами x, y). Найти минимальное расстояние между точками этих множеств и сами точки, расположенные на этом расстоянии (вначале выводится точка из множества A, затем точка из множества B).

Array136. Дано множество A из N точек (N > 2, точки заданы своими координатами x, y). Найти такую точку из данного множества, сумма расстояний от которой до остальных его точек минимальна, и саму эту сумму.

Array137. Дано множество A из N точек (N > 2, точки заданы своими координатами x, y). Найти наибольший периметр треугольника, вершины которого принадлежат различным точкам множества A, и сами эти точки (точки выводятся в том же порядке, в котором они перечислены при задании множества A).

Array138. Дано множество A из N точек (N > 2, точки заданы своими координатами x, y). Найти наименьший периметр треугольника, вершины которого принадлежат различным точкам множества A, и сами эти точки (точки выводятся в том же порядке, в котором они перечислены при задании множества A).

Array139. Дано множество A из N точек с целочисленными координатами x, y. Порядок на координатной плоскости определим следующим образом:

(x1, y1) < (x2, y2), если либо x1 < x2, либо x1 = x2 и y1 < y2.

Расположить точки данного множества по возрастанию в соответствии с указанным порядком.

Array140. Дано множество A из N точек с целочисленными координатами x, y. Порядок на координатной плоскости определим следующим образом:

(x1, y1) < (x2, y2), если либо x1 + y1 < x2 + y2, либо x1 + y1 = x2 + y2 и x1 < x2.

Расположить точки данного множества по убыванию в соответствии с указанным порядком.

Двумерные массивы (матрицы)

Условие вида «дана матрица размера M ´ N» означает, что вначале дается фактический размер двумерного массива-матрицы (количество строк M и количество столбцов N), а затем приводятся элементы этого массива (количество элементов равно M·N). Если в задании явно не указывается, какие значения могут принимать размеры исходной матрицы, то предполагается, что и число строк, и число столбцов может изменяться в пределах от 2 до 10. Начальные значения как первого, так и второго индекса двумерного массива-матрицы всегда считаются равными 1. Ввод и вывод элементов матрицы осуществляются по строкам.

Квадратной матрицей порядка M называется двумерный массив-матрица размера M ´ M.

Если в задании, связанном с созданием или преобразованием матрицы, не описан результирующий набор данных, то предполагается, что этим набором является созданная (преобразованная) матрица, и необходимо вывести все ее элементы.

Формирование матрицы и вывод ее элементов

В заданиях на формирование матрицы предполагается, что размер результирующей матрицы не превосходит 10 ´ 10.

Matrix1. Даны целые положительные числа M и N. Сформировать целочисленную матрицу размера M ´ N, у которой все элементы I-й строки имеют значение 10·I (I = 1, …, M).

Matrix2. Даны целые положительные числа M и N. Сформировать целочисленную матрицу размера M ´ N, у которой все элементы J-го столбца имеют значение 5·J (J = 1, …, N).

Matrix3. Даны целые положительные числа M, N и набор из M чисел. Сформировать матрицу размера M ´ N, у которой в каждом столбце содержатся все числа из исходного набора (в том же порядке).

Matrix4. Даны целые положительные числа M, N и набор из N чисел. Сформировать матрицу размера M ´ N, у которой в каждой строке содержатся все числа из исходного набора (в том же порядке).

Matrix5. Даны целые положительные числа M, N, число D и набор из M чисел. Сформировать матрицу размера M ´ N, у которой первый столбец совпадает с исходным набором чисел, а элементы каждого следующего столбца равны сумме соответствующего элемента предыдущего столбца и числа D (в результате каждая строка матрицы будет содержать элементы арифметической прогрессии).

Matrix6. Даны целые положительные числа M, N, число D и набор из N чисел. Сформировать матрицу размера M ´ N, у которой первая строка совпадает с исходным набором чисел, а элементы каждой следующей строки равны соответствующему элементу предыдущей строки, умноженному на D (в результате каждый столбец матрицы будет содержать элементы геометрической прогрессии).

Matrix7. Дана матрица размера M ´ N и целое число K (1 £ K £ M). Вывести элементы K-й строки данной матрицы.

Matrix8. Дана матрица размера M ´ N и целое число K (1 £ K £ N). Вывести элементы K-го столбца данной матрицы.

Matrix9. Дана матрица размера M ´ N. Вывести ее элементы, расположенные в строках с четными номерами (2, 4, …). Вывод элементов производить по строкам, условный оператор не использовать.

Matrix10. Дана матрица размера M ´ N. Вывести ее элементы, расположенные в столбцах с нечетными номерами (1, 3, …). Вывод элементов производить по столбцам, условный оператор не использовать.

Matrix11. Дана матрица размера M ´ N. Вывести ее элементы в следующем порядке: первая строка слева направо, вторая строка справа налево, третья строка слева направо, четвертая строка справа налево и т. д.

Matrix12. Дана матрица размера M ´ N. Вывести ее элементы в следующем порядке: первый столбец сверху вниз, второй столбец снизу вверх, третий столбец сверху вниз, четвертый столбец снизу вверх и т. д.

Matrix13. Дана квадратная матрица A порядка M. Начиная с элемента A1,1, вывести ее элементы следующим образом («уголками»): все элементы первой строки; элементы последнего столбца, кроме первого (уже выведенного) элемента; оставшиеся элементы второй строки; оставшиеся элементы предпоследнего столбца и т. д.; последним выводится элемент AM,1.

Matrix14. Дана квадратная матрица A порядка M. Начиная с элемента A1,1, вывести ее элементы следующим образом («уголками»): все элементы первого столбца; элементы последней строки, кроме первого (уже выведенного) элемента; оставшиеся элементы второго столбца; оставшиеся элементы предпоследней строки и т. д.; последним выводится элемент A1,M.

Matrix15. Дана квадратная матрица A порядка M (M — нечетное число). Начиная с элемента A1,1 и перемещаясь по часовой стрелке, вывести все ее элементы по спирали: первая строка, последний столбец, последняя строка в обратном порядке, первый столбец в обратном порядке, оставшиеся элементы второй строки и т. д.; последним выводится центральный элемент матрицы.

Matrix16. Дана квадратная матрица A порядка M (M — нечетное число). Начиная с элемента A1,1 и перемещаясь против часовой стрелки, вывести все ее элементы по спирали: первый столбец, последняя строка, последний столбец в обратном порядке, первая строка в обратном порядке, оставшиеся элементы второго столбца и т. д.; последним выводится центральный элемент матрицы.

Анализ элементов матрицы

Matrix17. Дана матрица размера M ´ N и целое число K (1 £ K £ M). Найти сумму и произведение элементов K-й строки данной матрицы.

Matrix18. Дана матрица размера M ´ N и целое число K (1 £ K £ N). Найти сумму и произведение элементов K-го столбца данной матрицы.

Matrix19. Дана матрица размера M ´ N. Для каждой строки матрицы найти сумму ее элементов.

Matrix20. Дана матрица размера M ´ N. Для каждого столбца матрицы найти произведение его элементов.

Matrix21. Дана матрица размера M ´ N. Для каждой строки матрицы с нечетным номером (1, 3, …) найти среднее арифметическое ее элементов. Условный оператор не использовать.

Matrix22. Дана матрица размера M ´ N. Для каждого столбца матрицы с четным номером (2, 4, …) найти сумму его элементов. Условный оператор не использовать.

Matrix23. Дана матрица размера M ´ N. В каждой строке матрицы найти минимальный элемент.

Matrix24. Дана матрица размера M ´ N. В каждом столбце матрицы найти максимальный элемент.

Matrix25. Дана матрица размера M ´ N. Найти номер ее строки с наибольшей суммой элементов и вывести данный номер, а также значение наибольшей суммы.

Matrix26. Дана матрица размера M ´ N. Найти номер ее столбца с наименьшим произведением элементов и вывести данный номер, а также значение наименьшего произведения.

Matrix27. Дана матрица размера M ´ N. Найти максимальный среди минимальных элементов ее строк.

Matrix28. Дана матрица размера M ´ N. Найти минимальный среди максимальных элементов ее столбцов.

Matrix29. Дана матрица размера M ´ N. В каждой ее строке найти количество элементов, меньших среднего арифметического всех элементов этой строки.

Matrix30. Дана матрица размера M ´ N. В каждом ее столбце найти количество элементов, больших среднего арифметического всех элементов этого столбца.

Matrix31. Дана матрица размера M ´ N. Найти номера строки и столбца для элемента матрицы, наиболее близкого к среднему значению всех ее элементов.

Matrix32. Дана целочисленная матрица размера M ´ N. Найти номер первой из ее строк, содержащих равное количество положительных и отрицательных элементов (нулевые элементы матрицы не учитываются). Если таких строк нет, то вывести 0.

Matrix33. Дана целочисленная матрица размера M ´ N. Найти номер последнего из ее столбцов, содержащих равное количество положительных и отрицательных элементов (нулевые элементы матрицы не учитываются). Если таких столбцов нет, то вывести 0.

Matrix34. Дана целочисленная матрица размера M ´ N. Найти номер последней из ее строк, содержащих только четные числа. Если таких строк нет, то вывести 0.

Matrix35. Дана целочисленная матрица размера M ´ N. Найти номер первого из ее столбцов, содержащих только нечетные числа. Если таких столбцов нет, то вывести 0.

Matrix36. Дана целочисленная матрица размера M ´ N, элементы которой могут принимать значения от 0 до 100. Различные строки матрицы назовем похожими, если совпадают множества чисел, встречающихся в этих строках. Найти количество строк, похожих на первую строку данной матрицы.

Matrix37. Дана целочисленная матрица размера M ´ N, элементы которой могут принимать значения от 0 до 100. Различные столбцы матрицы назовем похожими, если совпадают множества чисел, встречающихся в этих столбцах. Найти количество столбцов, похожих на последний столбец данной матрицы.

Matrix38. Дана целочисленная матрица размера M ´ N. Найти количество ее строк, все элементы которых различны.

Matrix39. Дана целочисленная матрица размера M ´ N. Найти количество ее столбцов, все элементы которых различны.

Matrix40. Дана целочисленная матрица размера M ´ N. Найти номер последней из ее строк, содержащих максимальное количество одинаковых элементов.

Matrix41. Дана целочисленная матрица размера M ´ N. Найти номер первого из ее столбцов, содержащих максимальное количество одинаковых элементов.

Matrix42. Дана матрица размера M ´ N. Найти количество ее строк, элементы которых упорядочены по возрастанию.

Matrix43. Дана матрица размера M ´ N. Найти количество ее столбцов, элементы которых упорядочены по убыванию.

Matrix44. Дана матрица размера M ´ N. Найти минимальный среди элементов тех строк, которые упорядочены либо по возрастанию, либо по убыванию. Если упорядоченные строки в матрице отсутствуют, то вывести 0.

Matrix45. Дана матрица размера M ´ N. Найти максимальный среди элементов тех столбцов, которые упорядочены либо по возрастанию, либо по убыванию. Если упорядоченные столбцы в матрице отсутствуют, то вывести 0.

Matrix46. Дана целочисленная матрица размера M ´ N. Найти элемент, являющийся максимальным в своей строке и минимальным в своем столбце. Если такой элемент отсутствует, то вывести 0.

Преобразование матрицы

При выполнении заданий из данного пункта (за исключением Matrix74 и Matrix75) не следует использовать вспомогательные двумерные массивы-матрицы.

Matrix47. Дана матрица размера M ´ N и целые числа K1 и K2 (1 £ K1 < K2 £ M). Поменять местами строки матрицы с номерами K1 и K2.

Matrix48. Дана матрица размера M ´ N и целые числа K1 и K2 (1 £ K1 < K2 £ N). Поменять местами столбцы матрицы с номерами K1 и K2.

Matrix49. Дана матрица размера M ´ N. Преобразовать матрицу, поменяв местами минимальный и максимальный элемент в каждой строке.

Matrix50. Дана матрица размера M ´ N. Преобразовать матрицу, поменяв местами минимальный и максимальный элемент в каждом столбце.

Matrix51. Дана матрица размера M ´ N. Поменять местами строки, содержащие минимальный и максимальный элементы матрицы.

Matrix52. Дана матрица размера M ´ N. Поменять местами столбцы, содержащие минимальный и максимальный элементы матрицы.

Matrix53. Дана матрица размера M ´ N. Поменять местами столбец с номером 1 и последний из столбцов, содержащих только положительные элементы. Если требуемых столбцов нет, то вывести матрицу без изменений.

Matrix54. Дана матрица размера M ´ N. Поменять местами столбец с номером N и первый из столбцов, содержащих только отрицательные элементы. Если требуемых столбцов нет, то вывести матрицу без изменений.

Matrix55. Дана матрица размера M ´ N (M — четное число). Поменять местами верхнюю и нижнюю половины матрицы.

Matrix56. Дана матрица размера M ´ N (N — четное число). Поменять местами левую и правую половины матрицы.

Matrix57. Дана матрица размера M ´ N (M и N — четные числа). Поменять местами левую верхнюю и правую нижнюю четверти матрицы.

Matrix58. Дана матрица размера M ´ N (M и N — четные числа). Поменять местами левую нижнюю и правую верхнюю четверти матрицы.

Matrix59. Дана матрица размера M ´ N. Зеркально отразить ее элементы относительно горизонтальной оси симметрии матрицы (при этом поменяются местами строки с номерами 1 и M, 2 и M – 1 и т. д.).

Matrix60. Дана матрица размера M ´ N. Зеркально отразить ее элементы относительно вертикальной оси симметрии матрицы (при этом поменяются местами столбцы с номерами 1 и N, 2 и N – 1 и т. д.).

Matrix61. Дана матрица размера M ´ N и целое число K (1 £ K £ M). Удалить строку матрицы с номером K.

Matrix62. Дана матрица размера M ´ N и целое число K (1 £ K £ N). Удалить столбец матрицы с номером K.

Matrix63. Дана матрица размера M ´ N. Удалить строку, содержащую минимальный элемент матрицы.

Matrix64. Дана матрица размера M ´ N. Удалить столбец, содержащий максимальный элемент матрицы.

Matrix65. Дана матрица размера M ´ N. Удалить ее первый столбец, содержащий только положительные элементы. Если требуемых столбцов нет, то вывести матрицу без изменений.

Matrix66. Дана матрица размера M ´ N. Удалить ее последний столбец, содержащий только отрицательные элементы. Если требуемых столбцов нет, то вывести матрицу без изменений.

Matrix67. Дана матрица размера M ´ N, содержащая как положительные, так и отрицательные элементы. Удалить все ее столбцы, содержащие только положительные элементы. Если требуемых столбцов нет, то вывести матрицу без изменений.

Matrix68. Дана матрица размера M ´ N и целое число K (1 £ K £ M). Перед строкой матрицы с номером K вставить строку из нулей.

Matrix69. Дана матрица размера M ´ N и целое число K (1 £ K £ N). После столбца матрицы с номером K вставить столбец из единиц.

Matrix70. Дана матрица размера M ´ N. Продублировать строку матрицы, содержащую ее максимальный элемент.

Matrix71. Дана матрица размера M ´ N. Продублировать столбец матрицы, содержащий ее минимальный элемент.

Matrix72. Дана матрица размера M ´ N. Перед первым столбцом, содержащим только положительные элементы, вставить столбец из единиц. Если требуемых столбцов нет, то вывести матрицу без изменений.

Matrix73. Дана матрица размера M ´ N. После последнего столбца, содержащего только отрицательные элементы, вставить столбец из нулей. Если требуемых столбцов нет, то вывести матрицу без изменений.

Matrix74. Дана матрица размера M ´ N. Элемент матрицы называется ее локальным минимумом, если он меньше всех окружающих его элементов. Заменить все локальные минимумы данной матрицы на нули. При решении допускается использовать вспомогательную матрицу.

Matrix75. Дана матрица размера M ´ N. Элемент матрицы называется ее локальным максимумом, если он больше всех окружающих его элементов. Поменять знак всех локальных максимумов данной матрицы на противоположный. При решении допускается использовать вспомогательную матрицу.

Matrix76. Дана матрица размера M ´ N. Упорядочить ее строки так, чтобы их первые элементы образовывали возрастающую последовательность.

Matrix77. Дана матрица размера M ´ N. Упорядочить ее столбцы так, чтобы их последние элементы образовывали убывающую последовательность.

Matrix78. Дана матрица размера M ´ N. Упорядочить ее строки так, чтобы их минимальные элементы образовывали убывающую последовательность.

Matrix79. Дана матрица размера M ´ N. Упорядочить ее столбцы так, чтобы их максимальные элементы образовывали возрастающую последовательность.

Диагонали квадратной матрицы

Matrix80. Дана квадратная матрица A порядка M. Найти сумму элементов ее главной диагонали, то есть диагонали, содержащей следующие элементы:

A1,1, A2,2, A3,3, …, AM,M.

Matrix81. Дана квадратная матрица A порядка M. Найти среднее арифметическое элементов ее побочной диагонали, то есть диагонали, содержащей следующие элементы:

A1,M, A2,M–1, A3,M–2, …, AM,1.

Matrix82. Дана квадратная матрица A порядка M. Найти сумму элементов каждой ее диагонали, параллельной главной (начиная с одноэлементной диагонали A1,M).

Matrix83. Дана квадратная матрица A порядка M. Найти сумму элементов каждой ее диагонали, параллельной побочной (начиная с одноэлементной диагонали A1,1).

Matrix84. Дана квадратная матрица A порядка M. Найти среднее арифметическое элементов каждой ее диагонали, параллельной главной (начиная с одноэлементной диагонали A1,M).

Matrix85. Дана квадратная матрица A порядка M. Найти среднее арифметическое элементов каждой ее диагонали, параллельной побочной (начиная с одноэлементной диагонали A1,1).

Matrix86. Дана квадратная матрица A порядка M. Найти минимальный элемент для каждой ее диагонали, параллельной главной (начиная с одноэлементной диагонали A1,M).

Matrix87. Дана квадратная матрица A порядка M. Найти максимальный элемент для каждой ее диагонали, параллельной побочной (начиная с одноэлементной диагонали A1,1).

Matrix88. Дана квадратная матрица порядка M. Обнулить элементы матрицы, лежащие ниже главной диагонали. Условный оператор не использовать.

Matrix89. Дана квадратная матрица порядка M. Обнулить элементы матрицы, лежащие выше побочной диагонали. Условный оператор не использовать.

Matrix90. Дана квадратная матрица порядка M. Обнулить элементы матрицы, лежащие на побочной диагонали и ниже нее. Условный оператор не использовать.

Matrix91. Дана квадратная матрица порядка M. Обнулить элементы матрицы, лежащие на главной диагонали и выше нее. Условный оператор не использовать.

Matrix92. Дана квадратная матрица порядка M. Обнулить элементы матрицы, лежащие одновременно выше главной диагонали и выше побочной диагонали. Условный оператор не использовать.

Matrix93. Дана квадратная матрица порядка M. Обнулить элементы матрицы, лежащие одновременно выше главной диагонали и ниже побочной диагонали. Условный оператор не использовать.

Matrix94. Дана квадратная матрица порядка M. Обнулить элементы матрицы, лежащие одновременно ниже главной диагонали (включая эту диагональ) и выше побочной диагонали (также включая эту диагональ). Условный оператор не использовать.

Matrix95. Дана квадратная матрица порядка M. Обнулить элементы матрицы, лежащие одновременно ниже главной диагонали (включая эту диагональ) и ниже побочной диагонали (также включая эту диагональ). Условный оператор не использовать.

Matrix96. Дана квадратная матрица A порядка M. Зеркально отразить ее элементы относительно главной диагонали (при этом элементы главной диагонали останутся на прежнем месте, элемент A1,2 поменяется местами с A2,1, элемент A1,3 — с A3,1 и т. д.). Вспомогательную матрицу не использовать.

Matrix97. Дана квадратная матрица A порядка M. Зеркально отразить ее элементы относительно побочной диагонали (при этом элементы побочной диагонали останутся на прежнем месте, элемент A1,1 поменяется местами с AM,M, элемент A1,2 — с AM–1,M и т. д.). Вспомогательную матрицу не использовать.

Matrix98. Дана квадратная матрица A порядка M. Повернуть ее на угол 180° (при этом элемент A1,1 поменяется местами с AM,M, элемент A1,2 — с AM,M–1 и т. д.). Вспомогательную матрицу не использовать.

Matrix99. Дана квадратная матрица A порядка M. Повернуть ее на угол 90° в положительном направлении, то есть против часовой стрелки (при этом элемент A1,1 перейдет в AM,1, элемент AM,1 — в AM,M и т. д.). Вспомогательную матрицу не использовать.

Matrix100. Дана квадратная матрица A порядка M. Повернуть ее на угол 90° в отрицательном направлении, то есть по часовой стрелке (при этом элемент A1,1 перейдет в A1,M, элемент A1,M — в AM,M и т. д.). Вспомогательную матрицу не использовать.

Указания

Array1–6. При вычислении очередного элемента массива удобно использовать значение предыдущего элемента (в Array5 — значения двух предыдущих элементов). Следует заметить, что в Array6 для вычисления очередного элемента (начиная с четвертого) также достаточно использовать значение только одного предыдущего элемента.

Array7. См. решение этого задания.

Array11–15. Организуйте цикл таким образом, чтобы в нем перебирались только элементы с требуемыми индексами, учитывая, что индекс элемента на 1 меньше его порядкового номера. Например, для перебора всех нечетных индексов можно использовать выражение 2K – 1, K = 1, 2, … (нечетные индексы соответствуют четным порядковым номерам). При организации цикла важно правильно определить конечное значение параметра цикла. В Array14–15 следует учитывать, что размер массива может быть как четным, так и нечетным.

Array16–17. На каждой итерации цикла удобно выводить не один, а несколько элементов массива (два в Array16, четыре в Array17). При этом может возникнуть необходимость в особой обработке некоторых элементов массива (например, если массив имеет нечетный размер, то при выполнении Array16 средний элемент этого массива надо вывести после завершения цикла).

Array18–19. В Array18 после обнаружения первого подходящего элемента следует прервать цикл, используя оператор break. В Array19 ищется последний подходящий элемент, поэтому необходимо просмотреть все элементы, которые могут оказаться подходящими (в данном случае это элементы с индексами от 1 до 8). Для решения Array19 можно также просматривать исходный массив с конца (см. Array7); в этом случае, как и при решении Array18, достаточно обнаружить первый подходящий элемент, после чего выйти из цикла.

Array24–25. Используя два первых элемента массива, найдите возможное значение разности (знаменателя) прогрессии, после чего в цикле проверьте остальные элементы массива (например, в Array25 каждый элемент массива должен быть равен предыдущему, умноженному на найденный знаменатель).

Array26–27. В Array26 достаточно проверить, что сумма двух соседних элементов массива является нечетной, в Array27 — что произведение двух соседних элементов является отрицательным.

Array28–29. Ср. с Minmax1. См. также указание к Array11–15.

Array30–31. При переборе элементов массива следует учесть, что первый из элементов не имеет левого соседа, а последний не имеет правого.

Array32–35. При анализе элементов необходимо учитывать, что первый и последний элементы массива (граничные элементы) имеют по одному соседу, тогда как все остальные (внутренние элементы) — по два. Можно анализировать первый элемент до цикла, в котором анализируются внутренние элементы, а последний элемент — после цикла. Более короткий алгоритм можно получить, включив в массив фиктивные элементы с индексами 0 и N + 1, где N — исходный размер массива («настоящие» элементы будут иметь индексы от 1 до N). Тем самым граничные элементы превратятся во внутренние, и их можно будет обрабатывать в общем цикле. При этом необходимо так определить значения фиктивных элементов, чтобы они обеспечили правильный анализ граничных элементов. Можно, например, положить фиктивный элемент равным «настоящему» соседу граничного элемента, то есть присвоить элементу с индексом 0 значение элемента с индексом 2, а элементу с индексом N + 1 — значение элемента с индексом N – 1.

Array36. Первый и последний элементы массива можно исключить из рассмотрения, так как любой граничный элемент вещественного массива обязательно является либо локальным минимумом, либо локальным максимумом (поскольку предполагается, что все элементы вещественного массива являются различными). Анализ остальных (внутренних) элементов можно упростить, если учесть, что внутренний элемент AK является локальным экстремумом (то есть минимумом или максимумом) в том случае, когда

(AK – AK–1)·(AK – AK+1) > 0.

Array37–39. Поскольку любой участок возрастания оканчивается локальным максимумом, а любой участок убывания — локальным минимумом, эти задания сводятся к заданиям о нахождении локальных минимумов/максимумов (Array32–36). Для Array37–38 воспользуйтесь указанием к Array32–35, для Array39 — указанием к Array36.

Array44–46. Для перебора пар различных элементов массива надо использовать двойной цикл. Перебор следует организовать таким образом, чтобы в пару входили различные элементы и чтобы каждая пара анализировалась ровно один раз. Для этого достаточно в двойном цикле сделать один из пределов изменения параметра внутреннего цикла зависящим от текущего значения параметра внешнего цикла, например:

    for (i = 0; i < n - 1; i++)
        for (j = i + 1; j < n; j++)
        . . .

В Array44 вместо организации внутреннего цикла можно воспользоваться методом IndexOf класса Array (см. второй вариант решения Array47).

Array47. См. решение этого задания.

Array48. Ср. с Array47.

Array49. Элемент будет недопустимым, если его значение либо не принадлежит диапазону чисел от 1 до N, либо уже встречалось ранее в массиве. Таким образом, для решения задания достаточно выполнить соответствующую проверку для каждого элемента массива. Для проверки того, что значение элемента уже встречалось в массиве, можно просмотреть все предыдущие элементы во вложенном цикле или воспользоваться методом IndexOf класса Array (ср. с двумя вариантами решения Array47).

Array50. См. указание к Array44–46.

Array54. Поскольку размер результирующего массива B заранее не известен, следует завести вспомогательную переменную K целого типа, в которой хранить количество элементов, уже размещенных в массиве B (вначале переменная K равна нулю; при обнаружении в исходном массиве элемента, который следует записать в массив В, этот элемент присваивается элементу массива B с индексом K, после чего значение K увеличивается на 1). Когда просмотр исходного массива A завершится, в переменной K будет храниться размер сформированного массива B. Другим способом решения задания является использование для хранения массива B динамического массива типа ArrayList; при этом отпадает необходимость в переменной K (ср. со вторым вариантом решения Array116).

Array55–57. См. указание к Array11–15.

Array58–61. В этих заданиях, как и в Array6, можно обойтись без вложенных циклов.

Array62. См. указание к Array54.

Array63–64. Задания на слияние упорядоченных массивов. Используйте для каждого исходного массива вспомогательную переменную, содержащую индекс его текущего элемента, то есть первого из элементов, еще не помещенных в результирующий массив. Очередной элемент, добавляемый в результирующий массив, выбирается из текущих элементов исходных массивов, после чего происходит переход к следующему элементу в том массиве, элемент которого был добавлен в результирующий массив. При реализации этого алгоритма возникает дополнительная проблема: что делать в ситуации, когда исчерпываются элементы одного из массивов. Для ее решения удобно использовать прием барьера, добавив в конец каждого массива фиктивный элемент. Если массивы упорядочены по возрастанию (как в Array63), то эти фиктивные элементы должны быть заведомо больше «настоящих» элементов всех исходных массивов; если массивы упорядочены по убыванию (как в Array64), то фиктивные элементы должны быть заведомо меньше всех «настоящих» элементов. В качестве фиктивных элементов можно использовать поля MaxValue и MinValue соответствующего числового типа.

Array71–73. Для изменения порядка в наборе из K элементов на обратный достаточно поменять местами первый элемент с последним, второй с предпоследним и т. д.; при этом число обменов должно быть равно K/2, где символ / обозначает операцию деления нацело (распространенной ошибкой является повторение операций обмена K раз; в этом случае каждая пара элементов меняется местами дважды, и в результате порядок элементов не изменяется). Еще проще воспользоваться подходящим вариантом метода Reverse класса Array.

Array74–75. Для решения этих заданий необходимо предварительно найти индексы минимального и максимального элементов массива (ср. с Minmax4) и выяснить, в каком порядке эти элементы располагаются в массиве (то есть какой индекс является меньшим, а какой — большим). Для Array75 см. также указание к Array71–73.

Array76–78. Перед обработкой элемента массива необходимо сохранять его прежнее значение во временной переменной, которая на следующей итерации цикла будет использована при анализе следующего элемента. Подчеркнем, что в этих заданиях, как и в прочих заданиях на преобразование массива, достаточно использовать несколько простых временных переменных, не заводя вспомогательного массива. Для Array76–77 см. также указание к Array32–35.

Array79–82. Задания на простой сдвиг элементов. См. два варианта решения Array79; прочие задания решаются аналогично. При их выполнении не требуются временные переменные для хранения элементов массива. При работе с обычным массивом сдвиг влево следует проводить в направлении от начала массива к его концу, а сдвиг вправо — в направлении от конца к началу. Использование динамического массива типа ArrayList (см. второй вариант решения Array79) позволяет избежать перебора в цикле всех элементов массива при выполнении его сдвига.

Array83–86. Задания на циклический сдвиг элементов. При работе с обычным массивом направление циклического сдвига совпадает с направлением простого сдвига (см. указание к Array79–82), однако, в отличие от заданий на простой сдвиг, здесь необходимо использовать временные переменные для хранения элементов. В случае сдвига на одну позицию достаточно одной переменной (при сдвиге влево в ней сохраняется первоначальное значение первого элемента, при сдвиге вправо — первоначальное значение последнего элемента). При сдвиге на K позиций можно использовать временный массив размера 4 (так как по условию задания K ´ 4). Другой вариант решения позволяет для любого K обойтись единственной временной переменной (за счет потери в скорости выполнения): он состоит в последовательном повторении сдвига на одну позицию в требуемом направлении; число повторений равно остатку от деления K на N, где N — размер массива. Еще одним способом решения является использование динамического массива ArrayList (ср. со вторым вариантом решения Array79); в этом случае можно обойтись без использования временных переменных.

Array87–88. Для повышения эффективности алгоритма следует совместить поиск места вставки и сдвиг элементов массива для выделения пустой позиции в месте вставки. Так, для Array87 следует сохранить первый элемент массива во временной переменной X и выполнять сдвиг остальных элементов массива на одну позицию влево до тех пор, пока не будет найден элемент, больший X, после чего записать в массив значение X перед этим элементом (для Array88 следует сохранить последний элемент массива и выполнять сдвиг вправо). Необходимо также предусмотреть ситуацию, когда для достижения упорядоченности первый элемент придется переместить в конец массива (или, соответственно, последний элемент в начало). Для обработки этой особой ситуации удобно использовать прием барьера, устанавливаемого в конце массива (или, соответственно, в его начале). Данный прием описан в указании к Array63–64.

Array89. Решение следует разбить на два этапа: вначале ищется элемент, нарушающий упорядоченность, затем этот элемент перемещается в новую позицию. На первом этапе надо просматривать элементы исходного массива A в поисках возрастающей пары соседних элементов: AI < AI+1. Обнаружение такой пары означает, что элементом, нарушающим упорядоченность, является либо AI, либо AI+1. Например, для набора 6, 1, 4, 3 нарушает упорядоченность элемент 1 (то есть первый элемент возрастающей пары), для набора 6, 3, 7, 1 нарушает упорядоченность элемент 7 (то есть второй элемент возрастающей пары), наконец, для набора 6, 3, 4, 1 в качестве элемента, нарушающего упорядоченность, можно выбрать как 3, так и 4. После нахождения элемента, нарушающего упорядоченность, наступает второй этап: перемещение найденного элемента на новую позицию. На этом этапе вначале надо определить направление перемещения (влево или вправо), после чего действовать по схеме, описанной в указании к Array87–88.

Array91–95. В заданиях на удаление нескольких элементов желательно сразу перемещать оставляемый в массиве элемент на его окончательное место, избегая многократного сдвига одного и того же элемента массива. Для этого надо завести вспомогательную переменную K, в которой хранить количество элементов, уже размещенных на новых позициях в ходе просмотра массива (таким образом, очередной элемент, оставляемый в массиве, должен размещаться в позиции с индексом K). Просмотр элементов должен осуществляться в направлении от начала к концу массива. Для Array93–94 см. также указание к Array11–15. Другим (и очень простым) способом решения заданий на удаление элементов является использование динамического массива ArrayList.

Array96–97. В этих заданиях для принятия решения об удалении текущего элемента следует просматривать все элементы массива, предшествующие текущему (в случае Array96) или следующие за текущим (в случае Array97). В остальном алгоритм не будет отличаться от схемы, описанной в указании к Array91–95. Заметим, что в случае Array96 изменение начальной части массива в ходе перемещения в нее элементов, оставляемых в массиве, не повлияет на правильность алгоритма.

Array98–100. В этих заданиях для принятия решения об удалении текущего элемента следует просматривать все элементы массива, причем в данном случае (в отличие от предыдущих заданий) изменение начальной части массива при перемещении в нее элементов, оставляемых в массиве, может привести к неправильной работе алгоритма. Для того чтобы этого не произошло, можно применить следующую модификацию алгоритма, описанного в указании к Array91–95: не просто перемещать текущий элемент, оставляемый в массиве, на позицию с индексом K (стирая тем самым прежнее содержимое элемента с данным индексом), а менять местами текущий элемент и элемент с индексом K. Благодаря этому исходный состав элементов массива будет сохраняться неизменным, что позволит на любом этапе выполнения алгоритма правильно определить, надо ли удалять из массива текущий элемент.

Array101–107. При выполнении заданий на вставку элементов в массив необходимо, прежде всего, предусмотреть место в памяти для размещения новых элементов, указав в описании массива больший размер. Далее, в алгоритме вставки элементов, как и в алгоритме удаления, желательно сразу перемещать элемент массива на его новое место (см. указание к Array91–95). Отличие от алгоритма удаления состоит в том, что конечные элементы массива при вставке новых элементов должны перемещаться не влево (как при удалении), а вправо, поэтому в данном случае перебор элементов следует проводить от конца массива к его началу. В Array101–107 размер результирующего массива можно определить, не анализируя элементы исходного массива. Для Array106–107 см. также указание к Array11–15. Отметим, что задания на вставку элементов, как и задания на удаление, легко решаются с использованием динамического массива ArrayList.

Array108–111. При выполнении этих заданий необходимо предварительно определить размер результирующего массива, подсчитав количество его положительных (Array108), отрицательных (Array109), четных (Array110) или нечетных (Array111) элементов. В остальном алгоритм вставки элементов не будет отличаться от схемы, описанной в указании к Array101–107.

Array112–115. Более подробные сведения об алгоритмах сортировки содержатся, например, в [1, 3, 4].

Array116–130. См. решение Array116. Наиболее простой способ решения остальных заданий на обработку серий состоит в использовании вспомогательных массивов B и C, содержащих, соответственно, длины всех серий исходного массива A и значения элементов, образующих эти серии. После формирования массивов B и C (см. решение Array116) следует обработать эти вспомогательные массивы требуемым образом (например, в Array119 достаточно увеличить значение каждого элемента массива B на 1) и затем сформировать заново массив A по данным из массивов B и C, учитывая условия задания (например, в Array117 перед записью очередной серии надо добавлять в массив A элемент с нулевым значением). Желательно, однако, при выполнении заданий Array117–130 обойтись без использования вспомогательных массивов; в этом случае может оказаться более удобным использовать динамический массив типа ArrayList.

Array131–135. При выполнении этих заданий удобно сравнивать не расстояния между точками, а квадраты расстояний; тем самым можно будет либо полностью исключить вызовы функции Math.Sqrt (в Array131–133), либо вызывать ее только один раз при выводе результата. Задания Array132–133 сводятся к нахождению условного минимума/максимума (см. указание к Minmax12–15). При организации двойного цикла в Array134 воспользуйтесь указанием к Array44–46 (заметим, что в Array135 параметр внутреннего цикла не должен зависеть от текущего значения параметра внешнего цикла).

Array137–138. При организации тройного цикла воспользуйтесь указанием к Array44–46.

Array139–140. При выполнении этих заданий можно воспользоваться любым из описанных в Array112–115 способов сортировки.

Matrix5–6. Ср. с Array3–4.

Matrix7–8. См. варианты решения Matrix7; Matrix8 решается аналогично.

Matrix9–10. См. указание к Array11–15.

Matrix11–12. В Matrix11 организуйте внешний цикл таким образом, чтобы за одну его итерацию выводились элементы двух строк (по поводу вывода элементов массива в обратном порядке см. решение Array7). Если количество строк является нечетным, то последнюю строку выводите после основного цикла. Аналогичным образом обрабатывайте столбцы в Matrix12.

Matrix13–16. В Matrix13–14 для вывода каждого «уголка» используйте отдельную итерацию внешнего цикла, в которой будут выполняться два внутренних цикла, обеспечивающие вывод двух сторон уголка. В Matrix15–16 для вывода каждого «витка» спирали используйте отдельную итерацию внешнего цикла, в которой будут выполняться четыре внутренних цикла, обеспечивающие вывод четырех сторон витка. Поскольку каждый следующий уголок (виток спирали) имеет меньший размер, диапазон изменения параметров внутренних циклов должен зависеть от текущего значения параметра внешнего цикла. В Matrix15–16 для определения числа итераций внешнего цикла необходимо найти количество витков спирали для матрицы порядка M.

Matrix17–18. Ср. с Matrix7–8.

Matrix21–22. См. указание к Array11–15.

Matrix23–28. Задания на применение к матрицам алгоритма нахождения минимумов/максимумов (см. Minmax1). Ср. Matrix25–26 с Minmax5. В Matrix25–28 допустимо использовать вспомогательный одномерный массив, число элементов которого равно числу строк (Matrix25 и Matrix27) или столбцов (Matrix26 и Matrix28) исходной матрицы, однако желательно обойтись без вспомогательного массива.

Matrix29–31. В этих заданиях каждый элемент матрицы необходимо просматривать дважды: первый раз — для нахождения среднего арифметического (строки, столбца или всех элементов матрицы), второй — для сравнения элемента с найденным значением среднего арифметического.

Matrix32–33. Для подсчета положительных и отрицательных элементов достаточно использовать одну переменную, увеличивая ее на 1 при обнаружении положительного элемента и уменьшая на 1 при обнаружении отрицательного. Необходимо обнулять эту переменную перед анализом каждой строки (столбца). См. также указание к Array18–19.

Matrix34–35. По поводу выхода из цикла, в котором просматриваются строки (столбцы) матрицы, см. указание к Array18–19.

Matrix36–37. Два множества считаются равными, если для любого элемента каждого из множеств найдется равный ему элемент другого множества. Для поиска равных элементов удобно использовать метод IndexOf класса Array, однако это возможно только при хранении матрицы в виде невыровненного массива (массива строк в Matrix36, массива столбцов в Matrix37). По поводу невыровненных массивов см. последний вариант решения Matrix7.

Matrix38–39. Ср. с Array47.

Matrix40–41. Ср. с Array48.

Matrix42–43. Для определенности будем рассматривать Matrix42. Заведем счетчик упорядоченных строк K и вспомогательную целую переменную P. Обнулим счетчик K и организуем цикл по строкам. Перед анализом очередной строки присвоим переменной P значение 1; в ходе просмотра строки (во внутреннем цикле) будем сравнивать пары ее соседних элементов, и если обнаружим пару, нарушающую требуемый порядок (то есть такую, в которой левый элемент больше правого), то присвоим P значение 0 и немедленно выйдем из внутреннего цикла с помощью оператора break. После выхода из внутреннего цикла увеличим значение счетчика K на величину P.

Matrix44–45. Строка или столбец являются упорядоченными по возрастанию или убыванию, если ни один из их внутренних элементов не является локальным экстремумом (см. указание к Array36). Используя это наблюдение и формулу, приведенную в указании к Array36, можно получить очень короткий алгоритм для проверки строки (столбца) на упорядоченность. При нахождении минимального/максимального элемента в упорядоченных строках (столбцах) следует учитывать, что этим элементом будет либо их начальный, либо их конечный элемент.

Matrix46. Следует учитывать, что строка может содержать более одного максимального элемента, а столбец — более одного минимального. Заметим, что задание сформулировано корректно: если матрица содержит несколько элементов, удовлетворяющих указанным условиям, то все они имеют одинаковые значения (докажите это).

Matrix55–58. Верхняя половина матрицы размера M ´ N состоит из строк с номерами от 1 до M/2, левая половина — из столбцов с номерами от 1 до N/2 (символ / обозначает операцию деления нацело). Левая верхняя четверть матрицы является пересечением ее верхней и левой половин и, следовательно, содержит элементы с номерами от (1, 1) до (M/2, N/2) (напомним, что индексы элементов двумерного массива-матрицы на 1 меньше их порядковых номеров).

Matrix61–67. См. указание к Array91–95.

Matrix68–73. См. указание к Array101–107.

Matrix74–75. Первая проблема, возникающая при выполнении этих заданий, заключается в том, что граничные элементы матрицы содержат меньше соседей, чем внутренние элементы. Для единообразной обработки всех элементов удобно превратить все элементы исходной матрицы во внутренние (см. указание к Array32–35), добавив к матрице две фиктивные строки (с номерами 0 и M + 1) и два фиктивных столбца (с номерами 0 и N + 1). Значения добавленных фиктивных элементов матрицы следует выбрать таким образом, чтобы они не влияли на правильность определения граничных локальных минимумов (максимумов) исходной матрицы: например, если требуется найти локальные минимумы, то достаточно присвоить всем фиктивным элементам значение, которое заведомо больше всех «настоящих» элементов матрицы (аналогично, в случае поиска локальных максимумов надо сделать фиктивные элементы заведомо меньшими всех «настоящих» элементов). Вторая проблема заключается в том, что изменение элементов исходной матрицы в процессе выполнения задания может привести к неправильному анализу следующих элементов. В случае одномерных массивов для решения этой проблемы было достаточно воспользоваться одной временной переменной (см. задания Array76–78 и указание к ним). В случае двумерных массивов-матриц так поступить нельзя, поэтому при выполнении Matrix74–75 допустимо использовать вспомогательную матрицу размера M ´ N, записывая в нее новые значения элементов (и сохраняя тем самым неизменной исходную матрицу). Заметим, что можно было бы ограничиться вспомогательным одномерным массивом (размера min{M, N}) и несколькими вспомогательными простыми переменными, однако это существенно усложнило бы алгоритм.

Matrix76–79. При выполнении этих заданий можно воспользоваться любым из описанных в Array112–114 способов сортировки. В Matrix78–79 удобно предварительно сформировать одномерный массив минимальных (максимальных) элементов, который в дальнейшем использовать при сравнении строк (столбцов).

Matrix82–87. См. решение Matrix82. Остальные задания решаются аналогично.

Matrix88–95. Для того чтобы в двойном цикле перебирались только обнуляемые элементы, пределы изменения параметра внутреннего цикла должны зависеть от текущего значения параметра внешнего цикла. Например, для Matrix88 заголовки в двойном цикле должны иметь следующий вид:

    for (i = 1; i < m; i++)
        for (j = 0; j < i; j++)

Matrix96–98. Для обеспечения однократной перестановки элементов матрицы цикл надо организовать не по всем элементам матрицы, а только по их половине. Более точно, для Matrix96 и Matrix98 следует перебрать элементы, расположенные выше главной диагонали, для Matrix97 — элементы, расположенные выше побочной диагонали (поменяв их местами с соответствующими элементами, расположенными ниже требуемой диагонали), а для Matrix98, кроме того, надо переставить в обратном порядке элементы, расположенные на главной диагонали (ср. с Array72). Заметим, что преобразование, описанное в Matrix96, называется транспонированием матрицы.

Matrix99–100. В отличие от Matrix96–98, в этих заданиях необходимо рассматривать не пары, а четверки элементов, циклически переходящих друг в друга. Для обеспечения однократной перестановки элементов цикл надо организовать не по всем элементам, а только по одной их четверти. Например, можно рассмотреть элементы, лежащие выше главной и одновременно выше побочной диагонали (ср. с Matrix92), а также первую половину элементов главной диагонали.


Модуль 2. Одномерные и двумерные массивы

Справочные сведения

Описание массива и его инициализация

Наряду с одномерными (single-dimensional) массивами, элементы которых имеют единственный индекс, в языках платформы .NET можно использовать многомерные прямоугольные (multidimensional) массивы, в которых диапазон значений каждого индекса не зависит от значений остальных индексов. Кроме того, можно создать массив массивов; в котором размеры элементов-массивов могут различаться; такой массив массивов называется невыровненным (jagged) многомерным массивом.

Несмотря на то что для работы с массивами в библиотеке FCL предусмотрен специальный класс Array, описание и инициализация массива в каждом языке программирования платформы .NET обычно выполняется по особым правилам, определяемым синтаксисом этого языка.

Класс Array включает средства, позволяющие задавать произвольный диапазон значений для каждого индекса, однако индексы массивов, создаваемых с помощью стандартных описателей в языке C#, всегда имеют фиксированную нижнюю границу, равную 0.

В последующих примерах предполагается, что массив a является одномерным массивом символов и имеет размер 5, а массив b является двумерным массивом-матрицей целых чисел размера 3 на 4.

При описании массива достаточно указать тип его элементов и количество индексов (то есть его размерность); размер массива не указывается:

char[] a;
int[,] b;

Затем необходимо выполнить инициализацию массива. Простейшим вариантом инициализации является неявная инициализация, или инициализация по умолчанию (элементы получают нулевые значения соответствующего типа):

a = new char[5];
b = new int[3, 4];

При инициализации массива в языке C# указывается количество значений для каждого индекса (допустимо указывать не только константы, но и выражения целого типа).

Описание массива и его инициализацию можно совместить:

char[] a = new char[5];
int[,] b = new int[3, 4];

При инициализации массива можно явно указать значения его элементов:

a = new char[] {'A', 'B', 'C', 'D', 'E'};
b = new int[,] {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};

В этом случае разрешено не указывать размер массива; если же размер указывается, то он должен соответствовать числу указанных элементов.

Переменную-массив можно инициализировать повторно; при этом прежнее содержимое массива теряется. При повторной инициализации допустимо указывать новый размер массива (однако нельзя изменять размерность массива и тип его элементов):

a = new char[5];
. . .
a = new char[10];

При передаче входного параметра-массива в какой-либо метод допустимо создавать массив «на лету», используя конструкцию явной инициализации (при этом массив не сохраняется в некоторой переменной, а сразу передается в указанный метод). Если, например, метод M принимает входной параметр — символьный массив, то для передачи в него массива из трех элементов 'A', 'B', 'C' можно использовать следующий оператор:

M(new char[] {'A', 'B', 'C'});

В заключение данного пункта приведем пример описания и неявной инициализации невыровненного двумерного целочисленного массива d с элементами d0,0, d1,0, d1,1, d2,0, d2,1, d2,2 (данный массив можно использовать, например, для хранения ненулевой части нижнетреугольной матрицы порядка 3). В приведенном примере после инициализации массива нулевое значение элемента d1,0 заменяется на 5:

int[][] d = new int[3][];
d[0] = new int[1];
d[1] = new int[2];
d[2] = new int[3];
d[1][0] = 5;

Обратите внимание на то, что для невыровненных массивов каждый индекс необходимо указывать в отдельных скобках (в отличие от многомерных прямоугольных массивов, в которых используется одна пара скобок, а индексы разделяются запятыми). См. также пример использования невыровненного двумерного массива в третьем варианте решения задания Matrix7.

Класс Array

Свойства и экземплярные методы

Приведенные ниже свойства и экземплярные методы класса Array можно использовать для массивов любого типа и любой размерности (за исключением метода CopyTo, предназначенного только для одномерных массивов).

int Length { get; }

Возвращает размер массива this, то есть количество его элементов.

int Rank { get; }

Возвращает размерность массива this, то есть количество его индексов.

int GetLength(int dimension);

Возвращает количество значений для индекса с номером dimension (индексы нумеруются от 0).

int GetUpperBound(int dimension);

Возвращает максимальное значение для индекса с номером dimension (индексы нумеруются от 0). Имеется также функция GetLowerBound, которая возвращает 0 для всех массивов, описанных по стандартным правилам C#.

object Clone();

Возвращает копию массива this. Копия имеет тот же размер и содержит элементы с теми же значениями, что и исходный массив. Копия является поверхностной (shallow): если элемент массива this содержит ссылку на некоторый объект, то соответствующий элемент массива-копии будет содержать ссылку на тот же объект (иными словами, копирования объектов, на которые ссылаются элементы массива, при поверхностном копировании массива не происходит).

void CopyTo(Array a, int start);

Копирует все элементы одномерного массива this в существующий одномерный массив a (заполняются элементы массива a, начиная с индекса start). Как и в методе Clone, копирование является поверхностным. Если массив a содержит недостаточно элементов, то возбуждается исключение.

Классовые методы

Все описанные ниже классовые методы класса Array, за исключением методов Clear и Copy, предназначены для обработки одномерных массивов. В .NET Framework версии 2.0 в класс Array добавлено большое количество методов, которые используют в качестве параметров обобщенные классы (generic classes). Эти методы в данном пособии не рассматриваются.

static void Clear(Array a, int start, int count);

Обнуляет count элементов массива a, начиная с элемента, имеющего индекс start. Если массив a является многомерным, то он интерпретируется как одномерный массив, причем перебор его элементов выполняется так, чтобы быстрее изменялись правые индексы элементов массива а (в частности, двумерные массивы-матрицы перебираются по строкам). Если массив a содержит недостаточно элементов, то возбуждается исключение IndexOutOfRangeException.

static void Copy(Array src, Array dest, int count);
static void Copy(Array src, int srcStart, Array dest, int destStart, int count);

Копирует count элементов из массива src в массив dest. При наличии параметров srcStart и destStart чтение элементов в массиве src начинается с элемента с индексом srcStart, а запись в массив dest производится с элемента с индексом destStart. При отсутствии параметров srcStart и destStart чтение и запись выполняются с начала указанных массивов. Позволяет обрабатывать многомерные массивы с одинаковым количеством индексов; в этом случае перебор элементов производится так же, как и для метода Clear. Как и в методе Clone, копирование является поверхностным.

static void Reverse(Array a[, int start, int count]);

Меняет порядок следования элементов одномерного массива a на обратный. При наличии параметров start и count меняется порядок следования только той части массива, которая начинается с элемента с индексом start и состоит из count элементов. При неверном указании параметров start или count возбуждается исключение.

static void Sort(Array a[, Array b][, int start, int count][,
    System.Collections.IComparer cmp]);

Выполняет сортировку элементов одномерного массива a по возрастанию. При отсутствии параметра cmp элементы массива a должны реализовывать интерфейс IComparable (то есть содержать метод int CompareTo(object obj), позволяющий сравнивать объекты this и obj). Все элементарные типы языка C# (кроме класса object) реализуют интерфейс IComparable.

При наличии параметров start и count сортировка проводится только для count элементов массива, начиная с элемента с индексом start. При наличии параметра-массива b порядок его элементов изменяется в соответствии с порядком элементов в отсортированном массиве a (таким образом, в этом случае массив a выступает в роли массива ключей, определяющих, в каком порядке располагать элементы в массиве b). Следует заметить, что взаимное расположение элементов массива b, соответствующих одинаковым ключам из массива a, в ходе сортировки может измениться.

При наличии параметра cmp сортировка выполняется в соответствии с правилом сравнения, заданным в методе int Compare(object x, object y) объекта cmp (данный метод должен быть определен в объекте cmp, поскольку cmp реализует интерфейс IComparer). При наличии параметра cmp элементы массива a не обязаны реализовывать интерфейс IComparable.

static int IndexOf(Array a, object value[, int start[, int count]]);

Осуществляет в одномерном массиве a поиск первого вхождения элемента value и возвращает его индекс (если элемент не найден, то возвращается –1). Параметр start определяет индекс элемента, начиная с которого выполняется поиск (при отсутствии параметра start поиск осуществляется с начального элемента массива). Параметр count определяет количество элементов, которые анализируются в ходе поиска (элементы перебираются по возрастанию индексов; при отсутствии параметра count поиск выполняется вплоть до последнего элемента массива). При неверном указании параметров start или count возбуждается исключение.

static int LastIndexOf(Array a, object value[, int start[, int count]]);

Осуществляет в одномерном массиве a поиск последнего вхождения элемента value и возвращает его индекс (если элемент не найден, то возвращается –1). Параметр start определяет индекс элемента, начиная с которого выполняется поиск (при отсутствии параметра start поиск осуществляется с последнего элемента массива). Параметр count определяет количество элементов, которые анализируются в ходе поиска (элементы перебираются по убыванию индексов; при отсутствии параметра count поиск выполняется вплоть до первого элемента массива). При неверном указании параметров start или count возбуждается исключение.

В классе Array имеется классовый метод BinarySearch, также предназначенный для поиска элементов. Данный метод обеспечивает более быстрый бинарный поиск, однако может применяться только к отсортированным по возрастанию одномерным массивам. В настоящем пособии этот метод не рассматривается.

Динамический массив: класс ArrayList

Динамические массивы, в отличие от обычных, могут изменять свой размер после создания (с сохранением своего прежнего содержимого). Возможности динамического массива реализованы в классе ArrayList, который определен в пространстве имен System.Collections.

В пространстве имен System.Collections определены и другие классы для работы с динамическими структурами данных, например, стек (Stack), очередь (Queue), хеш-таблица (Hashtable), ассоциативный массив, отсортированный по ключу (SortedList), динамический массив битов (BitArray). В .NET Framework 2.0, в связи с появившейся в этой версии возможностью создания обобщенных классов (generic classes), были разработаны обобщенные классы для таких коллекций, как динамический массив, стек, очередь, хеш-таблица, и некоторых других. Эти классы определены в пространстве имен System.Collections.Generic; они обеспечивают создание коллекций, предназначенных для хранения данных определенного типа («обычные» коллекции в качестве типа своих элементов используют базовый тип object). В настоящем пособии не рассматриваются динамические структуры данных (за исключением динамического массива ArrayList) и обобщенные классы.

Свойства

int Capacity { get; set; }

Возвращает и позволяет изменять емкость динамического массива, то есть размер вспомогательного массива объектов, используемого для хранения элементов. Если в результате добавления в динамический массив новых элементов текущей емкости оказывается недостаточно, она автоматически увеличивается. Следует заметить, что при изменении емкости в памяти создается новый массив объектов (требуемого размера) и в него копируются элементы из прежнего массива, после чего прежний массив считается «мусором» и в дальнейшем уничтожается. Таким образом, изменение емкости объекта ArrayList — достаточно ресурсоемкая операция. При попытке установить емкость массива меньшей его текущего размера возбуждается исключение ArgumentOutOfRangeException.

int Count { get; }

Возвращает текущий размер динамического массива, то есть количество фактически содержащихся в нем элементов. Данное свойство изменяется автоматически при добавлении элементов к массиву или при их удалении. Оно всегда меньше или равно емкости (Capacity) динамического массива.

object [int index] { get; set; }

Возвращает и позволяет изменить элемент динамического массива с индексом index (индексация, как и в обычном массиве, начинается от 0). Данное свойство-индексатор в языке C# не имеет имени; параметр index в квадратных скобках должен указываться непосредственно после имени динамического массива, например, a[2].

Если значение параметра index является недопустимым (меньше 0 или больше Count — 1), то возбуждается исключение ArgumentOutOfRangeException.

Поскольку объект ArrayList является массивом элементов типа object, элементам массива можно присваивать значения любого типа. Однако при чтении элемента массива ArrayList всегда возвращается объект базового типа object, который для дальнейшей обработки, как правило, должен быть явно преобразован к своему фактическому (то есть «истинному») типу.

Конструкторы и другие способы создания динамического массива

ArrayList([int capacity]);

Создает массив типа ArrayList заданной емкости capacity. Если параметр capacity отсутствует, то в .NET Framework версии 1.1 емкость полагается равной 16, а в версии 2.0 — равной 0. Созданный массив не содержит элементов, то есть его свойство Count равно 0.

ArrayList(ICollection c);

Создает массив типа ArrayList и заполняет его элементами, взятыми из коллекции c (в том же порядке, в котором они размещаются в коллекции). Емкость массива при этом полагается равной его фактическому размеру. В качестве параметра c можно указывать объект любого класса, реализующего интерфейс ICollection; примерами таких классов являются обычные массивы, а также динамические массивы ArrayList и все прочие динамические структуры из пространства имен System.Collections, упомянутые в начале данного раздела.

object Clone();

Возвращает копию динамического массива this. Копия имеет ту же емкость, тот же размер и содержит элементы с теми же значениями, что и исходный массив. Как и в случае обычного массива Array, метод Clone осуществляет поверхностное копирование (см. описание данного метода для массива Array).

static ArrayList Repeat(object value, int count);

Возвращает динамический массив, содержащий count копий объекта value. Параметр value может быть равен null. Если value является ссылкой на объект, то при выполнении данного метода копируются только ссылки; новых копий самого объекта не создается (то есть копирование является поверхностным, как и в методе Clone, описанном выше). Емкость созданного массива полагается равной числу max{16; count} в .NET Framework 1.1 и числу max{4; count} в версии 2.0. Если параметр count является отрицательным, то возбуждается исключение.

Совместимость объектов Array и ArrayList

Обычные массивы и динамические массивы ArrayList не совместимы по присваиванию. Для преобразования обычного массива к объекту ArrayList необходимо воспользоваться соответствующим конструктором класса ArrayList. Для обратного преобразования (объекта ArrayList к обычному массиву) надо воспользоваться одним из описанных ниже методов класса ArrayList. Заметим, что во всех этих методах выполняется поверхностное копирование элементов динамического массива в обычный (по поводу поверхностного копирования см. описание метода Clone).

object[] ToArray();

Возвращает массив типа object[], содержащий все элементы динамического массива this. Размер созданного массива (Length) равен фактическому размеру (Count) динамического массива this.

Array ToArray(Type type);

Возвращает массив типа Array, содержащий все элементы динамического массива this, преобразованные к типу type. Если преобразование каких-либо элементов массива this к типу type невозможно, то возбуждается исключение InvalidCastException. Размер созданного массива (Length) равен фактическому размеру (Count) динамического массива this.

Для указания типа type можно использовать метод GetType, применив его к объекту требуемого типа (например, если переменная s имеет тип string, то для получения этого типа достаточно воспользоваться выражением s.GetType()). В языке C# имеется также специальный оператор typeof, который позволяет получить требуемый тип непосредственно по его имени, например, typeof(string).

Поскольку неявное преобразование типа Array к конкретному типу массива невозможно, при использовании данного метода необходимо также выполнить явное преобразование возвращенного им объекта Array к массиву требуемого типа. В языке C# для этого надо использовать операцию преобразования типа.

Приведем пример использования данного метода, в котором предполагается, что переменная d (типа ArrayList) содержит только элементы типа double:
double[] a = (double[])d.ToArray(typeof(double));

void CopyTo(Array array[, int arrayStart]);
void CopyTo(int start, Array array, int arrayStart, int count);

Копирует count элементов из динамического массива this (начиная с элемента с индексом start) в одномерный массив array, записывая их в массив array, начиная с элемента с индексом arrayStart. Если параметры start и count не указаны, то копирование начинается с первого элемента динамического массива (с индексом 0) и выполняется вплоть до последнего элемента динамического массива. Если параметр arrayStart не указан, то заполняются элементы массива array, начиная с первого элемента (с индексом 0).

Копируемые элементы должны иметь тип, совместимый с типом элементов массива array; размер массива array должен быть достаточным для хранения всех копируемых элементов; при указании параметров start и count динамический массив this должен содержать элементы с индексами от start до start + count – 1. При нарушении любого из этих условий возбуждается исключение.

Преобразование динамического массива

int Add(object value);
void Insert(int index, object value);

Метод Add добавляет в конец динамического массива this объект value и возвращает индекс добавленного элемента. Метод Insert вставляет объект value в позицию с индексом index.

При выполнении данных методов размер массива (Count) увеличивается на 1 и возможно увеличение емкости массива (Capacity). Параметр value может быть равен null. Если значение index меньше 0 или больше Count, то возбуждается исключение.

void AddRange(ICollection c);
void InsertRange(int start, ICollection c);
void SetRange(int start, ICollection c);

Метод AddRange добавляет в конец динамического массива this все элементы из коллекции c. Метод InsertRange вставляет все элементы из коллекции c в массив this, начиная с позиции с индексом start. Метод SetRange заменяет существующие элементы динамического массива this, начиная с индекса start, на элементы коллекции c. В качестве коллекции c можно указывать объект любого класса, реализующего интерфейс ICollection; примерами таких классов являются обычные массивы, а также динамические массивы ArrayList и другие структуры данных из пространства имен System.Collections, упомянутые в начале данного раздела.

При выполнении методов AddRange и InsertRange увеличивается размер массива (Count) и возможно увеличение емкости массива (Capacity); при выполнении метода SetRange размер массива не изменяется. Если параметр c равен null, а также если значение start меньше 0 или больше Count, то возбуждается исключение. При выполнении метода SetRange исключение возбуждается также в случае, когда размер массива this недостаточен для хранения всех элементов коллекции c.

void Remove(object value);

Удаляет из динамического массива this первое вхождение элемента со значением value (данное значение может быть равно null); при этом размер массива (Count) уменьшается. Если элемент с указанным значением не найден, то метод не выполняет никаких действий.

void RemoveAt(int index);
void RemoveRange(int start, int count);

Метод RemoveAt удаляет из динамического массива this элемент с индексом index. Метод RemoveRange удаляет из массива this count элементов, начиная с элемента с индексом start. При удалении элементов размер массива уменьшается. Если параметр index отрицателен или больше или равен свойству Count, а также если параметры start и count задают диапазон индексов, не соответствующий существующим элементам массива this, то возбуждается исключение.

void Clear();

Удаляет из динамического массива this все элементы и полагает его размер Count равным 0. Емкость массива (Capacity) не изменяется.

void TrimToSize();

Уменьшает емкость (Capacity) динамического массива this, полагая ее равной текущему размеру массива (Count). Если массив не содержит элементов (то есть свойство Count равно 0), то устанавливается емкость по умолчанию, равная 16.

void Reverse([int start, int count]);

Меняет порядок следования элементов динамического массива this на обратный. При наличии параметров start и count меняется порядок следования только той части массива, которая начинается с элемента с индексом start и состоит из count элементов. При неверном указании параметров start или count возбуждается исключение.

void Sort([[int start, int count,] System.Collections.IComparer cmp]);

Выполняет сортировку элементов динамического массива this по возрастанию. При отсутствии параметра cmp элементы массива должны реализовывать интерфейс IComparable (то есть содержать метод int CompareTo(object obj), позволяющий сравнивать объекты this и obj). Все элементарные типы языка C# (кроме класса object) реализуют интерфейс IComparable.

При наличии параметра cmp сортировка выполняется в соответствии с правилом сравнения, заданным в методе int Compare(object x, object y) объекта cmp (данный метод должен быть определен в объекте cmp, поскольку cmp реализует интерфейс IComparer). Если параметр cmp указан, то элементы массива this не обязаны реализовывать интерфейс IComparable.

При наличии параметров start и count сортировка проводится только для count элементов массива, начиная с элемента с индексом start.

Заметим, что если динамический массив отсортирован по возрастанию, то в нем, как и в обычном массиве, можно выполнять быстрый бинарный поиск, применяя для этого метод BinarySearch. В настоящем пособии метод BinarySearch не рассматривается.

Поиск в динамическом массиве

bool Contains(object value);

Возвращает true, если динамический массив this содержит элемент со значением value, и false в противном случае.

int IndexOf(object value[, int start[, int count]]);
int LastIndexOf(object value[, int start[, int count]]);

Метод IndexOf осуществляет в динамическом массиве this поиск первого вхождения элемента со значением value, а метод LastIndexOf — поиск последнего вхождения. По поводу возвращаемых значений и назначения параметров start и count см. описание одноименных методов класса Array.

Перебор элементов в цикле foreach

Удобным способом перебора элементов массива является оператор цикла foreach. В заголовке данного цикла указывается переменная (параметр цикла), в которую последовательно помещаются значения всех элементов массива, начиная с элемента с индексом 0. Для многомерных массивов перебор элементов выполняется таким образом, чтобы быстрее изменялись правые индексы. Из-за подобной организации цикла foreach элементы массива в нем можно использовать только для чтения. Параметр цикла foreach должен иметь тип, совместимый с типом элементов массива; его можно описать в заголовке цикла, в этом случае он будет существовать только при выполнении данного цикла.

Оператор foreach может использоваться не только для обычных массивов, но и для объектов других классов, например, для динамических структур данных (в частности, динамических массивов ArrayList) и строк типа string (но не StringBuilder). Признаком допустимости использования цикла foreach для объекта является наличие у этого объекта интерфейса IEnumerable.

В качестве примера использования цикла foreach найдем с его помощью сумму всех элементов динамического массива a, содержащего целочисленные данные (предполагается, что переменная s имеет целый тип и инициализирована нулем):

foreach (int v in a)
    s += v;

Приведем пример решения этой же задачи с использованием цикла for:

for (int i = 0; i < a.Count; i++)
    s += (int)a[i];

Обратите внимание на то, что при использовании цикла for для обработки элементов динамических массивов обычно приходится выполнять явное преобразование типа, а для цикла foreach это не требуется, поскольку в теле цикла производится обращение не к элементам массива, а к параметру цикла. Примеры использования цикла foreach содержатся также в последнем варианте решения задания Array116 и втором варианте решения задания String41.

Заметим, что цикл foreach наиболее эффективен для обработки динамических структур данных (в частности, динамических массивов), в то время как для обычных массивов и строк быстрее выполняется цикл for (см. [2], п. 21.11, 22.9).


Модуль 2. Одномерные и двумерные массивы

Контрольные вопросы

Для каждого вопроса щелкните мышью на радиокнопке с номером варианта ответа, который, по вашему мнению, является правильным. Во всех фрагментах кода предполагается, что в программе указаны операторы using со всеми требуемыми пространствами имен. После того как вы ответите на все вопросы, проверьте правильность ответов, нажав на кнопку «Проверка».

1. Укажите правильный вариант описания и инициализации двумерного целочисленного массива-матрицы a размера 6 ´ 7.

(1) | int[6, 7] a; | (2) | int[,] a[6, 7];
(3) | int[,] a = new int[6, 7]; | (4) | int[][] a = new int[6][7];

2. Укажите неверное утверждение.

(1) | При создании массива все его элементы по умолчанию инициализируются нулевыми значениями | (2) | Все массивы, описанные с помощью стандартных средств языка C#, индексируются от 0
(3) | Действия по созданию массива необходимо выполнить в том же операторе, в котором этот массив описан | (4) | В любой программе на C# попытка обращения к элементу массива с недопустимым индексом всегда приводит к возбуждение исключения

3. Укажите, какой из перечисленных методов класса Array не является статическим (то есть классовым)

(1) | Метод клонирования Clone | (2) | Метод сортировки Sort
(3) | Метод поиска IndexOf | (4) | Метод инвертирования Reverse

4. Укажите, какое из перечисленных соотношений для свойств Capacity и Count объекта класса ArrayList никогда не выполняется.

(1) | Capacity == Count | (2) | Capacity < Count
(3) | Capacity > Count | (4) | Count == 0

5. Укажите оператор, который обеспечивает сохранение содержимого одномерного массива x в объекте y типа ArrayList.

(1) | y = (ArrayList)x; | (2) | y = ArrayList(x);
(3) | y = new ArrayList(x); | (4) | y = ArrayList.Copy(x);

6. Для того чтобы найти сумму всех вещественных чисел, содержащихся в объекте a типа ArrayList, за исключением первого числа, учащийся написал следующий фрагмент программы:

double sum = 0;
a[0] = 0;
foreach (double a0 in a)
    sum += a0;

Укажите, что произойдет при обработке этого фрагмента.

(1) | При компиляции фрагмента будет выведено сообщение об ошибке | (2) | При выполнении фрагмента будет возбуждено исключение
(3) | После выполнения фрагмента в переменной sum будет содержаться ошибочный результат | (4) | После выполнения фрагмента в переменной sum будет содержаться правильный результат


Модуль 2. Одномерные и двумерные массивы

Итоговое задание

Ввести с клавиатуры положительное целое число M, не превосходящее 9 (при вводе недопустимого числа положить M равным 9). Сгенерировать с помощью датчика случайных чисел квадратную матрицу порядка M с вещественными элементами, лежащими в диапазоне от –10 до 10.

Вывести элементы этой матрицы по строкам; для вывода каждого элемента использовать 8 экранных позиций, из которых 2 позиции отвести под вывод дробной части.

Для полученной матрицы вывести следующие данные:

  • количество строк L, содержащих только положительные элементы;
  • количество столбцов C, для которых число положительных и отрицательных элементов отличается не более чем на 1;
  • номер N строки с наибольшей суммой элементов и саму эту сумму S (считать, что строки нумеруются от 1);

Кроме того, вывести элементы побочной диагонали исходной матрицы в отсортированном порядке (по возрастанию).

Приведем образец текста, который должен выводиться программой, выполняющей проектное задание:

M = 5
    2,21    9,77    2,05    8,85    3,35
    4,56    3,92   -1,19   -0,68    7,09
   -7,26    6,61    8,09    6,65   -5,19
    6,49   -1,10    1,31   -1,24    2,77
    2,06    6,03   -2,71    7,07   -1,59
L = 1
C = 3
N = 1, S = 26,22
Отсортированные элементы побочной диагонали:
   -1,10   -0,68    2,06    3,35    8,09


Модуль 3. Символы и строки

Комплексная цель

Изучить основные алгоритмы, связанные с обработкой символьных и строковых данных, в том числе с алгоритмами поиска и замены в строке и выделения слов из строки. Ознакомиться с базовыми типами char и string, а также с классом StringBuilder стандартной библиотеки .NET. Изучить приемы форматирования данных различных типов, в том числе с основными спецификаторами формата и форматными настройками. Ознакомиться с форматами кодирования текстовых данных, в частности, с методами класса Encoding.

Структура модуля

  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание


Модуль 3. Символы и строки

Решения типовых задач

Формирование строк: String10

String10. Дана строка. Вывести строку, содержащую те же символы, но расположенные в обратном порядке.

Данное задание можно выполнить с помощью операции сцепления (конкатенации) +, если учесть, что для доступа к i-му символу строки s в C# следует использовать конструкцию s[i].

Task("String10");
string s = GetString(), s1 = "";
for (int i = s.Length - 1;
        i >= 0; i--)
    s1 = s1 + s[i];
Put(s1);

Символы исходной строки s перебираются в цикле по убыванию индексов; это обеспечивает требуемый порядок их размещения в результирующей строке s1.

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

Приведем другой, более эффективный, способ решения задания String10, в котором результирующая строка заполняется посимвольно, как обычный массив (ср. с решением задания Array7). В этом решении используется класс StringBuilder, определенный в пространстве имен System.Text, поэтому в начале программы следует указать данное пространство имен, используя оператор using.

Task("String10");
string s = GetString();
int n = s.Length;
StringBuilder s1 =
    new StringBuilder(n);
s1.Length = n;
for (int i = 0; i < n; i++)
    s1[i] = s[n - i - 1];
Put(s1.ToString());

Поскольку длина исходной строки многократно используется в тексте программы, она сохраняется во вспомогательной переменной n. Переменная n указывается в конструкторе класса StringBuilder; это позволяет задать емкость строки s1 (то есть размер массива символов, используемого для хранения данной строки). Однако длина созданной строки s1 (то есть количество фактически содержащихся в ней символов) остается равной 0, и для того чтобы положить ее равной длине исходной строки, требуется изменить свойство s1.Length.

Необходимость использования класса StringBuilder объясняется тем, что «обычные» строки (типа string) являются неизменяемыми; в частности, их символы, как и свойство Length, доступны только для чтения.

Так как в методе Put можно указывать только «обычные» строки (см. приложение), для s1 приходится вызывать метод ToString, преобразующий данную строку к типу string. Заметим, что это преобразование выполняется очень быстро, так как не приводит к копированию символов в новую область памяти: созданная «на лету» строка типа string будет ссылаться на ту же последовательность символов, что и исходная строка s1 (см. [6], гл. 12, с. 231, а также раздел о связи типов string и StringBuilder в справочных сведениях).

Возможен еще один вариант решения, в котором преобразуется нужным образом сама исходная строка (естественно, для этого необходимо использовать строку типа StringBuilder):

Task("String10");
StringBuilder s = new StringBuilder(GetString());
int n = s.Length;
char c;
for (int i = 0; i < n / 2; i++)
{
    c = s[i];
    s[i] = s[n - i - 1];
    s[n - i - 1] = c;
}
Put(s.ToString());

Количество итераций цикла равно N/2, где N — длина исходной строки (выполняется целочисленное деление); благодаря этому каждая пара символов меняется местами ровно один раз. В данном случае нет необходимости настраивать длину строки s, изменяя ее свойство Length, так как использованный вариант конструктора StringBuilder записывает в s исходную строку и при этом автоматически устанавливает свойство Length равным длине исходной строки.

Возвращаясь к первому варианту решения, основанному на сцеплении строк, отметим, что его реализация с использованием класса StringBuilder обладает существенно большей эффективностью ([2], п. 21.20), поскольку не требует создания временных строк. Приведем эту эффективную реализацию первого варианта решения:

Task("String10");
string s = GetString();
StringBuilder s1 = new StringBuilder(s.Length);
for (int i = s.Length - 1; i >= 0; i--)
    s1.Append(s[i]);
Put(s1.ToString());

Для добавления данных к строке типа StringBuilder используется специальный метод Append. Обратите внимание на явное указание емкости строки s1 при вызове ее конструктора; это повышает эффективность программы, так как позволяет обойтись без дополнительного выделения памяти при дополнении строки.

Преобразование строк в числа, обработка исключений: String19

String19. Дана строка. Если она представляет собой запись целого числа, то вывести 1, если вещественного (с дробной частью) — вывести 2; если строку нельзя преобразовать в число, то вывести 0. Считать, что дробная часть вещественного числа отделяется от его целой части десятичной точкой «.».

Использование в качестве десятичного разделителя точки не соответствует текущим форматным настройкам, принятым в русской версии Windows (в этих настройках используется десятичная запятая). В качестве числового формата с разделителем-точкой можно использовать формат из региональных настроек для США ("en-US"). Поскольку задание новых региональных настроек будет осуществляться в программе с помощью класса CultureInfo, определенного в пространстве имен System.Globalization, данное пространство имен надо указать в начале программы с помощью оператора using (дополнительная информация о региональных настройках приводится в справочных сведениях к модулю № 1).

Task("String19");
string s = GetString();
try
{
    int.Parse(s);
    Put(1);
    return;
}
catch
{}
try
{
    double.Parse(s,
        new CultureInfo("en-US"));
    Put(2);
    return;
}
catch
{}
Put(0);

Для преобразования строки в число требуемого типа используется метод Parse соответствующего числового типа. Помимо параметра — исходной строки этот метод может содержать параметр, определяющий региональные настройки, которые используются при преобразовании (если данный параметр не указан, то используются текущие региональные настройки).

Если преобразование к числу требуемого типа невозможно, то метод Parse возбуждает исключение (exception). Для того чтобы исключение не привело к аварийному завершению программы, вызывать метод Parse следует в блоке try-catch (раздел try содержит операторы, которые могут вызвать исключение, а раздел catch — операторы, которые следует выполнить при возникновении исключения). При возбуждении исключения в одном из операторов раздела try все оставшиеся операторы этого раздела пропускаются. В приведенной программе вначале делается попытка преобразовать введенную строку к целому типу. Если это удается, то выводится 1 и происходит немедленный выход из функции Solve с помощью оператора return; если в ходе преобразования возбуждается исключение, то происходит переход в раздел catch (в данном случае он является пустым), после чего в новом try-блоке делается попытка преобразовать строку к вещественному типу (в формате США).

Поскольку числовые значения, соответствующие исходной строке, в программе не используются, методы Parse вызываются как отдельные операторы; при этом их возвращаемые значения теряются.

В решении задания String19 была использована наиболее простая форма блока try-catch. В общем случае с разделом try можно связать несколько разделов catch, каждый из которых предназначен для обработки определенного исключения. Кроме того, после всех разделов catch может располагаться раздел finally, операторы которого выполняются и при успешном выполнении раздела try, и при возбуждении в нем исключения:
try
{
    операторы
}
catch (тип_исключения_1[ ex])
{
    операторы_1
}
catch (тип_исключения_2[ ex])
{
    операторы_2
}
. . .
[finally
{
    операторы_0
}]

Переменная-исключение ex, которая может указываться в заголовке раздела catch, имеет тип, совпадающий с типом исключения, которое обрабатывается в этом разделе. Данная переменная доступна только в том разделе catch, в заголовке которого она описана; с ее помощью можно получить дополнительную информацию о возбужденном исключении. Имя этой переменной может быть произвольным, хотя рекомендуется использовать имя ex. Подчеркнем, что в заголовке раздела catch имя переменной-исключения можно не указывать.

Полезные дополнительные сведения об обработке исключений в .NET содержатся в [2], гл. 19, и в [6], гл. 18.

Анализ слов в строке: String41

При выполнении заданий на анализ и преобразование слов главная проблема заключается в выделении каждого слова из исходной строки. Будем предполагать, что строка не является пустой, не содержит начальных и конечных пробелов, и слова в ней разделяются одним или несколькими пробелами (именно такие строки предлагаются в качестве исходных данных во всех заданиях соответствующего раздела группы String). Опишем для таких строк алгоритм выделения слов, использующий стандартные функции для работы со строками.

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

Воспользуемся описанным алгоритмом для решения задания String41 — первого задания из раздела, посвященного анализу слов в строке.

String41. Дана строка, состоящая из русских слов, разделенных пробелами (одним или несколькими). Найти количество слов в строке.

Task("String41");
string s = GetString() + " ";
int n = 0;
while (s.Length != 0)
{
    int p = s.IndexOf(' ');
    n++;
    s = s.Remove(0, p + 1).TrimStart();
}
Put(n);

Дополнительный пробел добавляется к строке при ее инициализации.

Обратите внимание на то, что удаление из строки обработанного слова (функцией Remove) и следующих за ним пробелов (функцией TrimStart) выполняется в одном операторе. Это оказывается возможным благодаря тому, что к строке, возвращенной функцией Remove, можно сразу применить функцию TrimStart. Вместо сравнения строки s с пустой строкой выполняется сравнение длины строки s с нулем; подобное сравнение выполняется быстрее (см. [2], п. 21.13).

Так как в задании String41 требуется лишь подсчитать содержащиеся в строке слова, обработка слов сводится к выполнению единственного оператора, увеличивающего на 1 переменную-счетчик n.

Заметим, что описанный алгоритм легко модифицировать так, чтобы он правильно обрабатывал строки, имеющие начальные и конечные пробелы (а также пустые строки и строки, состоящие из одних пробелов). Для этого достаточно добавить перед циклом while оператор s = s.TrimStart().

Класс string содержит специальный метод Split, обеспечивающий разбиение строки на отдельные слова (полученные слова возвращаются в строковом массиве). К сожалению, в версии 1.1 .NET Framework данный метод не позволяет считать рядом стоящие разделители (в нашем случае пробелы) одним разделителем; при наличии рядом стоящих разделителей в результирующий массив слов помещается «пустое» слово (строка длины 0), «находящееся» между этими разделителями. Учитывая данную особенность метода Split, его можно использовать для решения задания String41 следующим образом:

Task("String41");
string s = GetString();
int n = 0;
foreach (string s0 in s.Split(' '))
    if (s0.Length != 0)
        n++;
Put(n);

Обратите внимание на использованный в решении цикл foreach, позволяющий последовательно перебрать все элементы массива, указанного в его заголовке (в нашем случае данным массивом является массив, возвращаемый методом Split); на каждой итерации обрабатывается очередной элемент массива, который помещается в переменную, также указанную в заголовке цикла (в нашем случае это переменная s0). Благодаря использованию цикла foreach отпадает необходимость в описании специального массива для хранения слов (cм. раздел о цикле foreach в справочных сведениях).

В версии 2.0 .NET Framework в класс string был добавлен вариант метода Split с дополнительным параметром, позволяющим удалять из результирующего массива пустые строки. При использовании этого варианта метода решение задания String41 можно записать в виде единственного (хотя и длинного) оператора:

Task("String41");
Put(GetString().Split(new char[] {' '},
    StringSplitOptions.RemoveEmptyEntries).Length);

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

Приведенный вариант решения демонстрирует способ создания «на лету» массива с требуемыми элементами (обычно такой способ создания массива применяется для того, чтобы передать его в качестве параметра). Заметим, что в качестве первого параметра метода Split можно указать нулевую ссылку null, а в предыдущем варианте решения метод можно вызвать без параметров: Split(). В этих случаях разделителем считается любой пробельный символ (по поводу пробельных символов см. описание метода IsWhiteSpace).

Альтернативой для всех рассмотренных выше вариантов решения является посимвольный анализ строки, который, как правило, оказывается более эффективным. В случае задания String41 можно воспользоваться тем, что каждому слову в строке, кроме первого, предшествует пробел:
Task("String41");
string s = GetString();
int n = 1;
for (int i = 1; i < s.Length; i++)
    if (s[i] != ' ' && s[i - 1] == ' ')
        n++;
Put(n);

Инициализировав переменную-счетчик n значением 1, мы учли первое слово (перед которым нет пробелов). Обратите внимание на то, что в цикле анализируются все символы исходной строки, кроме первого (с индексом 0).

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


Модуль 3. Символы и строки

Учебные задания и указания к ним

Символы и строки

При выполнении заданий на обработку русских букв можно считать, что буква «ё» в исходных строковых данных отсутствует.

Символы и их коды. Формирование строк

String1. Дан символ C. Вывести его код (то есть номер в кодовой таблице).

String2. Дано целое число N (32 £ N £ 126). Вывести символ с кодом, равным N.

String3. Дан символ C. Вывести два символа, первый из которых предшествует символу C в кодовой таблице, а второй следует за символом C.

String4. Дано целое число N (1 £ N £ 26). Вывести N первых прописных (то есть заглавных) букв латинского алфавита.

String5. Дано целое число N (1 £ N £ 26). Вывести N последних строчных (то есть маленьких) букв латинского алфавита в обратном порядке (начиная с буквы «z»).

String6. Дан символ C, изображающий цифру или букву (латинскую или русскую). Если C изображает цифру, то вывести строку «digit», если латинскую букву — вывести строку «lat», если русскую — вывести строку «rus».

String7. Дана непустая строка. Вывести коды ее первого и последнего символа.

String8. Дано целое число N (> 0) и символ C. Вывести строку длины N, которая состоит из символов C.

String9. Дано четное число N (> 0) и символы C1 и C2. Вывести строку длины N, которая состоит из чередующихся символов C1 и C2, начиная с C1.

String10. Дана строка. Вывести строку, содержащую те же символы, но расположенные в обратном порядке.

String11. Дана непустая строка S. Вывести строку, содержащую символы строки S, между которыми вставлено по одному пробелу.

String12. Дана непустая строка S и целое число N (> 0). Вывести строку, содержащую символы строки S, между которыми вставлено по N символов «*» (звездочка).

Посимвольный анализ и преобразование строк. Строки и числа

String13. Дана строка. Подсчитать количество содержащихся в ней цифр.

String14. Дана строка. Подсчитать количество содержащихся в ней прописных латинских букв.

String15. Дана строка. Подсчитать общее количество содержащихся в ней строчных латинских и русских букв.

String16. Дана строка. Преобразовать в ней все прописные латинские буквы в строчные.

String17. Дана строка. Преобразовать в ней все строчные буквы (как латинские, так и русские) в прописные.

String18. Дана строка. Преобразовать в ней все строчные буквы (как латинские, так и русские) в прописные, а прописные — в строчные.

String19. Дана строка. Если она представляет собой запись целого числа, то вывести 1, если вещественного (с дробной частью) — вывести 2; если строку нельзя преобразовать в число, то вывести 0. Считать, что дробная часть вещественного числа отделяется от его целой части десятичной точкой «.».

String20. Дано целое положительное число. Вывести символы, изображающие цифры этого числа (в порядке слева направо).

String21. Дано целое положительное число. Вывести символы, изображающие цифры этого числа (в порядке справа налево).

String22. Дана строка, изображающая целое положительное число. Вывести сумму цифр этого числа.

String23. Дана строка, изображающая арифметическое выражение вида «<цифра>±<цифра>±…±<цифра>», где на месте знака операции «±» находится символ «+» или «–» (например, «4+7–2–8»). Вывести значение данного выражения (целое число).

String24. Дана строка, изображающая двоичную запись целого положительного числа. Вывести строку, изображающую десятичную запись этого же числа.

String25. Дана строка, изображающая десятичную запись целого положительного числа. Вывести строку, изображающую двоичную запись этого же числа.

Обработка строк с помощью стандартных функций. Поиск и замена

В заданиях, связанных с поиском и заменой подстрок, можно считать, что исходная строка не содержит перекрывающихся вхождений требуемых подстрок. В заданиях String32, String35 и String38, кроме этого, можно также считать, что удаление (в String32 и String35) или замена (в String38) любого вхождения подстроки не приведет к появлению в строке новых вхождений данной подстроки.

String26. Дано целое число N (> 0) и строка S. Преобразовать строку S в строку длины N следующим образом: если длина строки S больше N, то отбросить первые символы, если длина строки S меньше N, то в ее начало добавить символы «.» (точка).

String27. Даны целые положительные числа N1 и N2 и строки S1 и S2. Получить из этих строк новую строку, содержащую первые N1 символов строки S1 и последние N2 символов строки S2 (в указанном порядке).

String28. Дан символ C и строка S. Удвоить каждое вхождение символа C в строку S.

String29. Дан символ C и строки S, S0. Перед каждым вхождением символа C в строку S вставить строку S0.

String30. Дан символ C и строки S, S0. После каждого вхождения символа C в строку S вставить строку S0.

String31. Даны строки S и S0. Проверить, содержится ли строка S0 в строке S. Если содержится, то вывести true, если не содержится, то вывести false.

String32. Даны строки S и S0. Найти количество вхождений строки S0 в строку S.

String33. Даны строки S и S0. Удалить из строки S первую подстроку, совпадающую с S0. Если совпадающих подстрок нет, то вывести строку S без изменений.

String34. Даны строки S и S0. Удалить из строки S последнюю подстроку, совпадающую с S0. Если совпадающих подстрок нет, то вывести строку S без изменений.

String35. Даны строки S и S0. Удалить из строки S все подстроки, совпадающие с S0. Если совпадающих подстрок нет, то вывести строку S без изменений.

String36. Даны строки S, S1 и S2. Заменить в строке S первое вхождение строки S1 на строку S2.

String37. Даны строки S, S1 и S2. Заменить в строке S последнее вхождение строки S1 на строку S2.

String38. Даны строки S, S1 и S2. Заменить в строке S все вхождения строки S1 на строку S2.

String39. Дана строка, содержащая по крайней мере один символ пробела. Вывести подстроку, расположенную между первым и вторым пробелом исходной строки. Если строка содержит только один пробел, то вывести пустую строку.

String40. Дана строка, содержащая по крайней мере один символ пробела. Вывести подстроку, расположенную между первым и последним пробелом исходной строки. Если строка содержит только один пробел, то вывести пустую строку.

Анализ и преобразование слов в строке

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

String41. Дана строка, состоящая из русских слов, разделенных пробелами (одним или несколькими). Найти количество слов в строке.

String42. Дана строка, состоящая из русских слов, набранных заглавными буквами и разделенных пробелами (одним или несколькими). Найти количество слов, которые начинаются и заканчиваются одной и той же буквой.

String43. Дана строка, состоящая из русских слов, набранных заглавными буквами и разделенных пробелами (одним или несколькими). Найти количество слов, которые содержат хотя бы одну букву «А».

String44. Дана строка, состоящая из русских слов, набранных заглавными буквами и разделенных пробелами (одним или несколькими). Найти количество слов, которые содержат ровно три буквы «А».

String45. Дана строка, состоящая из русских слов, разделенных пробелами (одним или несколькими). Найти длину самого короткого слова.

String46. Дана строка, состоящая из русских слов, разделенных пробелами (одним или несколькими). Найти длину самого длинного слова.

String47. Дана строка, состоящая из русских слов, разделенных пробелами (одним или несколькими). Вывести строку, содержащую эти же слова, разделенные одним символом «.» (точка). В конце строки точку не ставить.

String48. Дана строка, состоящая из русских слов, набранных заглавными буквами и разделенных пробелами (одним или несколькими). Преобразовать каждое слово в строке, заменив в нем все последующие вхождения его первой буквы на символ «.» (точка). Например, слово «МИНИМУМ» надо преобразовать в «МИНИ.У.». Количество пробелов между словами не изменять.

String49. Дана строка, состоящая из русских слов, набранных заглавными буквами и разделенных пробелами (одним или несколькими). Преобразовать каждое слово в строке, заменив в нем все предыдущие вхождения его последней буквы на символ «.» (точка). Например, слово «МИНИМУМ» надо преобразовать в «.ИНИ.УМ». Количество пробелов между словами не изменять.

String50. Дана строка, состоящая из русских слов, разделенных пробелами (одним или несколькими). Вывести строку, содержащую эти же слова, разделенные одним пробелом и расположенные в обратном порядке.

String51. Дана строка, состоящая из русских слов, набранных заглавными буквами и разделенных пробелами (одним или несколькими). Вывести строку, содержащую эти же слова, разделенные одним пробелом и расположенные в алфавитном порядке.

String52. Дана строка-предложение на русском языке. Преобразовать строку так, чтобы каждое слово начиналось с заглавной буквы. Словом считать набор символов, не содержащий пробелов и ограниченный пробелами или началом/концом строки. Слова, не начинающиеся с буквы, не изменять.

String53. Дана строка-предложение на русском языке. Подсчитать количество содержащихся в строке знаков препинания.

String54. Дана строка-предложение на русском языке. Подсчитать количество содержащихся в строке гласных букв.

String55. Дана строка-предложение на русском языке. Вывести самое длинное слово в предложении. Если таких слов несколько, то вывести первое из них. Словом считать набор символов, не содержащий пробелов, знаков препинания и ограниченный пробелами, знаками препинания или началом/концом строки.

String56. Дана строка-предложение на русском языке. Вывести самое короткое слово в предложении. Если таких слов несколько, то вывести последнее из них. Словом считать набор символов, не содержащий пробелов, знаков препинания и ограниченный пробелами, знаками препинания или началом/концом строки.

String57. Дана строка-предложение с избыточными пробелами между словами. Преобразовать ее так, чтобы между словами был ровно один пробел.

Дополнительные задания на обработку строк

String58. Дана строка, содержащая полное имя файла, то есть имя диска, список каталогов (путь), собственно имя и расширение. Выделить из этой строки имя файла (без расширения).

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

String60. Дана строка, содержащая полное имя файла. Выделить из этой строки название первого каталога (без символов «\»). Если файл содержится в корневом каталоге, то вывести символ «\».

String61. Дана строка, содержащая полное имя файла. Выделить из этой строки название последнего каталога (без символов «\»). Если файл содержится в корневом каталоге, то вывести символ «\».

String62. Дана строка-предложение на русском языке. Зашифровать ее, выполнив циклическую замену каждой буквы на следующую за ней в алфавите и сохранив при этом регистр букв («А» перейдет в «Б», «а» — в «б», «Б» — в «В», «я» — в «а» и т. д.). Букву «ё» в алфавите не учитывать («е» должна переходить в «ж»). Знаки препинания и пробелы не изменять.

String63. Дана строка-предложение на русском языке и число K (0 < K < 10). Зашифровать строку, выполнив циклическую замену каждой буквы на букву того же регистра, расположенную в алфавите на K-й позиции после шифруемой буквы (например, для K = 2 «А» перейдет в «В», «а» — в «в», «Б» — в «Г», «я» — в «б» и т. д.). Букву «ё» в алфавите не учитывать, знаки препинания и пробелы не изменять.

String64. Дано зашифрованное предложение на русском языке (способ шифрования описан в задании String63) и кодовое смещение K (0 < K < 10). Расшифровать предложение.

String65. Дано зашифрованное предложение на русском языке (способ шифрования описан в задании String63) и его расшифрованный первый символ C. Найти кодовое смещение K и расшифровать предложение.

String66. Дана строка-предложение. Зашифровать ее, поместив вначале все символы, расположенные на четных позициях строки, а затем, в обратном порядке, все символы, расположенные на нечетных позициях (например, строка «Программа» превратится в «ргамамроП»).

String67. Дано предложение, зашифрованное по правилу, описанному в задании String66. Расшифровать это предложение.

String68. Дана строка, содержащая цифры и строчные латинские буквы. Если буквы в строке упорядочены по алфавиту, то вывести 0; в противном случае вывести номер первого символа строки, нарушающего алфавитный порядок.

String69. Дана строка, содержащая латинские буквы и круглые скобки. Если скобки расставлены правильно (то есть каждой открывающей соответствует одна закрывающая), то вывести число 0. В противном случае вывести или номер позиции, в которой расположена первая ошибочная закрывающая скобка, или, если закрывающих скобок не хватает, число –1.

String70. Дана строка, содержащая латинские буквы и скобки трех видов: «()», «[]», «{}». Если скобки расставлены правильно (то есть каждой открывающей соответствует закрывающая скобка того же вида), то вывести число 0. В противном случае вывести или номер позиции, в которой расположена первая ошибочная скобка, или, если закрывающих скобок не хватает, число –1.

Указания

String1–2. Для определения кодового номера символа используйте преобразование символа к типу int. Для определения символа с данным кодовым номером используйте преобразование номера к типу char.

String3–5. Для получения символа, следующего в кодовой таблице за данным символом c, можно использовать выражение (char)((int)c + 1) (для получения предшествующего символа достаточно заменить в указанном выражении знак + на –). Для того чтобы получить в той же символьной переменной c следующий или предыдущий символ, можно также использовать операцию инкремента c++ или декремента c–– соответственно.

String6. Чтобы проверить, является ли цифрой символ c, можно использовать операции сравнения: c >= '0' && c <= '9'. Для проверки цифр можно также использовать метод IsDigit. Буквы анализируются с помощью операций сравнения; следует учитывать, что латинские и русские буквы, имеющие одинаковые изображения (например, «а»), считаются различными символами, поскольку имеют разные кодовые номера.

String7. Для доступа к i-му символу строки s достаточно применить к строке операцию индексирования (как к обычному массиву): s[i]. Для определения длины строки используйте свойство Length класса string.

String8–9. Используйте операцию + сцепления (конкатенации) строк. Операндами этой операции могут быть как строки, так и символы. Задание String8 легко выполнить с помощью подходящего конструктора класса string.

String10. См. решение этого задания.

String11–12. Ср. с первым вариантом решения String10 и его модификацией. В String12 следует вначале создать вспомогательную строку, содержащую N символов '*', а затем использовать ее при формировании требуемой строки.

String13–15. См. указание к String6 и указание к String7.

String16–18. См. указания к String3–5, String6 и String7. При преобразовании символов надо учитывать, что для латинских букв и русских букв в кодировке Unicode кодовое смещение между одинаковыми прописными и строчными буквами равно 32. Вместо явных действий с кодами символов для изменения регистра букв можно использовать методы ToUpper и ToLower.

String19. См. решение этого задания.

String20–21. Для получения строкового представления числа примените к данному числу метод ToString. Обратите внимание на то, что надо вывести не строку, а последовательность символов. Для String21 cм. также решение String10.

String22–23. Для того чтобы по символу c, изображающему цифру, определить соответствующее число, можно воспользоваться либо функцией Parse (см. решение String19), либо следующим выражением: (int)c – (int)'0'. В String23 удобно вначале обработать первый символ строки (то есть первую цифру), а затем в цикле обрабатывать по два символа: знак операции и очередную цифру.

String24–25. В String24 надо на основе анализа исходной строки сформировать требуемое целое число, которое затем преобразовать в строку с помощью метода ToString. В String25, наоборот, удобнее вначале преобразовать исходную строку в целое число (методом Parse), а затем на основе анализа этого числа сформировать его двоичное представление в виде строки.

String26. Используйте метод Remove и операцию сцепления.

String27. Используйте метод Substring и операцию сцепления.

String28–30. Просмотр символов исходной строки следует проводить с конца, применяя операцию индексирования (см. указание к String7); благодаря просмотру с конца, внесенные в строку изменения не повлияют на расположение тех символов, которые еще не проанализированы. Для вставки символов и строк используйте метод Insert.

String31. Используйте метод IndexOf.

String32. Используйте в цикле while вариант метода IndexOf с двумя параметрами: на каждой последующей итерации цикла начинайте поиск с позиции, следующей за ранее найденным вхождением подстроки S0. Цикл завершится, когда при очередном вызове метод IndexOf вернет –1.

String33. Используйте методы IndexOf и Remove.

String34. Используйте методы LastIndexOf и Remove.

String35. Используйте в цикле while методы IndexOf и Remove. Можно также использовать метод Replace со вторым параметром — пустой строкой.

String36–38. Ср. String36 с String33, String37 с String34, String38 с String35. Для вставки подстроки S2 в строку S используйте метод Insert.

String39–40. Определите позицию первого пробела (с помощью метода IndexOf) и удалите все символы строки до первого пробела включительно (с помощью метода Remove). Теперь для поиска следующего пробела (в String39) можно использовать метод IndexOf (так как после удаления начального фрагмента строки этот пробел станет первым пробелом), а для поиска последнего пробела (в String40) — метод LastIndexOf. Следует также учитывать, что исходная строка может содержать единственный пробел.

String41–46. См. решение String41. Варианты этого решения, использующие выделение слова из исходной строки с его последующим удалением, легко модифицировать для остальных заданий этой группы. Более эффективными являются решения, основанные на посимвольном анализе исходной строки (как в последнем варианте решения String41).

String47. Ср. с String41. Действуя, как в первом варианте решения String41, можно выделять слова из исходной строки и сразу добавлять их к новой строке, разделяя символами '.'. Опишем другой способ решения, основанный на посимвольном анализе (как в последнем варианте решения String41). Просматриваем исходную строку с конца в поисках пробела; найдя пробел, действуем следующим образом: если перед найденным пробелом также находится пробел, то удаляем найденный пробел, в противном случае заменяем найденный пробел на символ '.' (строку следует хранить в объекте типа StringBuilder).

String48–49. Используйте посимвольный анализ строки (как в последнем варианте решения String41). В String48 просмотр строки выполняйте от начала к концу, обрабатывая нужным образом следующие ситуации: 1) найдено начало очередного слова; 2) найдена буква, совпадающая с начальной буквой текущего слова. В String49 просмотр строки выполняйте от конца к началу, обрабатывая следующие ситуации: 1) найден конец очередного слова; 2) найдена буква, совпадающая с конечной буквой текущего слова. Для хранения строки используйте объект типа StringBuilder.

String50–51. Используйте выделение слов из исходной строки (как в первом варианте решения String41). В String50 выделенное слово можно сразу добавлять в начало результирующей строки; в String51 необходимо сформировать вспомогательный массив слов, упорядочить его по алфавиту (используя любой из вариантов сортировки, описанных в Array112–114) и после этого объединить отсортированные слова в результирующую строку. Заметим, что для сортировки элементов массива можно использовать метод Sort класса Array, а для объединения элементов в одну строку — метод Join класса string.

String52. Для нахождения начала очередного слова используйте посимвольный анализ строки (как во втором варианте решения String41); строку храните в объекте типа StringBuilder. О преобразовании строчных букв в прописные (заглавные) см. указание к String16–18.

String53–54. Поскольку ни знаки препинания, ни гласные буквы не располагаются в кодовой таблице символов подряд, можно сформировать вспомогательный массив, содержащий все требуемые символы, и при просмотре символов строки проверять, принадлежат ли они этому массиву, используя метод IndexOf класса Array. Для отбора знаков препинания проще воспользоваться методом IsPunctuation.

String55–56. Эти задания, подобно String41–46, можно решить, либо выделяя слова из исходной строки, либо организуя ее посимвольный просмотр (см. варианты решения String41). В отличие от String41–46, разделителями слов здесь являются не только пробелы, но и знаки препинания, поэтому для поиска разделителей при посимвольном просмотре удобно воспользоваться методом IsPunctuation, определенным для типа char.

String57. Ср. с String47.

String58–61. Имя диска отделяется от пути двоеточием и обратной косой чертой \, путь отделяется от имени файла обратной косой чертой, имя файла отделяется от расширения точкой, например: C:\Dir1\File1.txt. Следует иметь в виду, что косая черта также разделяет каталоги, перечисленные в пути (C:\Dir1\Dir2\Dir3\File1.txt), что файл может находиться непосредственно в корневом каталоге (C:\File1.txt) и что расширение файла может отсутствовать (C:\Dir1\File1) или быть пустым (C:\Dir1\File1.). При выполнении данных заданий удобно использовать методы класса Path.

String62. См. указание к String3–5. Для обработки букв придется использовать вложенные условные операторы, в одном из которых должны обрабатываться диапазоны букв «А»–«Ю» и «а»–«ю» (см. указание к String6), а в другом — буквы «Я» и «я» (эти буквы, в отличие от остальных русских букв, надо переводить не в следующую букву алфавита, а в буквы «А» и «а» соответственно). Строку храните в объекте типа StringBuilder.

String63–65. Ср. с String62. В данном случае диапазоны букв, которые должны перемещаться по алфавиту «вперед» и «назад», зависят от значения числа K. При выполнении задания можно воспользоваться «универсальной» формулой, применимой как для букв, перемещаемых «вперед», так и для букв, перемещаемых «назад» (в этой формуле будет присутствовать операция взятия остатка от деления).

String66. См. указание к Array11–15.

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

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

String69. Заведите вспомогательную переменную-счетчик с нулевым начальным значением; в ходе просмотра строки увеличивайте эту переменную на 1 при обнаружении открывающей скобки и уменьшайте на 1 при появлении скобки закрывающей. При правильной расстановке скобок счетчик в ходе просмотра строки не должен принимать отрицательные значения, а после завершения просмотра должен равняться нулю.

String70. В данном случае нельзя обойтись вспомогательными переменными- счетчиками (как в String69), поскольку с их помощью невозможно распознать ошибочную ситуацию вида "[a(b]c)". Можно использовать вспомогательную строку (типа StringBuilder), в которую записывать все открывающие скобки, а при появлении закрывающей скобки сравнивать ее с последней скобкой из вспомогательной строки: если скобки имеют одинаковый тип, то из вспомогательной строки удаляется последняя скобка и проверка продолжается; если же скобки имеют различный тип, значит, текущая закрывающая скобка является ошибочной. Требуется также обеспечить контроль за ситуацией, когда число закрывающих скобок не равно числу открывающих.


Модуль 3. Символы и строки

Справочные сведения

Символы и их коды. Класс char и его методы

Для определения кода по символу c или, наоборот, символа по его коду n в C# достаточно выполнить явное преобразование типов: (int)c и (char)n.

Символы-литералы в языке C# заключаются в одинарные кавычки: 'A', '1' (в отличие от строк-литералов, заключаемых в двойные кавычки).

При работе с символами любые языки платформы .NET используют кодировку Unicode, в которой каждый символ кодируется двухбайтным целым числом.

static bool IsControl(char c);
static bool IsDigit(char c);
static bool IsLetter(char c);
static bool IsLetterOrDigit(char c);
static bool IsLower(char c);
static bool IsUpper(char c);
static bool IsPunctuation(char c);
static bool IsSymbol(char c);
static bool IsSeparator(char c);
static bool IsWhiteSpace(char c);

Данные методы класса char позволяют определить вид символа c: IsControl — управляющий символ (символы с кодами в диапазонах 0–31 и 127–159); IsDigit — цифра (символы 0–9, а также символы из других алфавитов, обозначающие цифры); IsLetter — буква, IsLower — строчная (маленькая) буква; IsUpper — прописная (заглавная) буква; IsPunctuation — знак пунктуации (символы ! " # % & ' ( ) * , - . / ? @ [ \ ] _ { } < > — – « » : и некоторые другие); IsSymbol — специальный символ, не являющийся знаком пунктуации (символы $ + < = > ^ ` | ~ § © ° _ № и некоторые другие); IsSeparator — разделитель (пробел (код 32), неразрывный пробел (код 160) и некоторые символы из других алфавитов); IsWhiteSpace — пробельный символ (символы с кодами 9 (знак табуляции), 10 (новая строка), 11–12, 13 (возврат каретки), 32, 133, 160 и некоторые символы из других алфавитов).

Для всех этих методов есть вариант с двумя параметрами: (string s, int index); в этом варианте анализируется символ строки s с индексом index.

static char ToLower(char c);
static char ToUpper(char c);

Возвращает символ c, преобразованный к нижнему или верхнему регистру соответственно (если символ не является буквой, то возвращается этот же символ).

string ToString();
static string ToString(char c);

Экземплярный метод возвращает строковое представление символа this, классовый метод — строковое представление символа c. Заметим, что типы string и char не являются совместимыми по присваиванию, поэтому для того чтобы присвоить значение символа c строке s, необходимо предварительно преобразовать символ в строку: s = c.ToString() или s = char.ToString(c).

Класс string

Класс string предназначен для хранения строк в кодировке Unicode, причем размер строк на практике ограничивается только размерами доступной оперативной памяти. В настоящем разделе описываются все основные возможности класса string, за исключением интернирования строк (об интернировании строк см. [6], гл. 12, с. 223).

Особенности инициализации. Неизменяемость объектов string

Простейший и самый распространенный способ инициализации объекта типа string — присваивание ему литеральной строки (то есть строковой константы, заключенной в кавычки), например:

string s = "ABCD";

Для инициализации строки можно также использовать несколько вариантов конструктора. Приведем описание двух часто используемых вариантов.

string(char value, int count);

Создает строку длины count, состоящую из одинаковых символов value.

string(char[] value);

Создает строку, состоящую из символов массива value.

При использовании этих и других конструкторов необходимо применять стандартный синтаксис с ключевым словом new, например:

string s = new string('A', 5);

Кроме того, строка может быть создана с помощью различных методов класса string (в частности, метода Format), а также метода ToString класса object и всех его потомков.

После создания и размещения в памяти объект типа string нельзя изменить; в частности, доступ к символам строки возможен только для чтения. Любые методы, предназначенные для изменения строк, не модифицируют существующую строку, а создают новую строку с нужными изменениями.

Класс string является ссылочным типом, поэтому при присваивании строковых переменных происходит лишь копирование ссылок на одну и ту же неизменяемую строку, размещенную в памяти. Если же в программе, начиная с некоторого момента, не содержится ни одной ссылки на данный строковый объект, то этот объект считается «мусором» и удаляется из памяти при очередной активизации сборщика мусора — особой подсистемы среды выполнения .NET Framework (заметим, что подобным образом уничтожаются и объекты других ссылочных типов).

Поля и свойства

Класс string имеет одно классовое поле и два экземплярных свойства. И поле, и свойства класса string доступны только для чтения.

static readonly string Empty;

Данное поле возвращает пустую строку. Заметим, что вместо поля string.Empty в языке C# допустимо использовать литеральную строку "" (две подряд расположенные двойные кавычки).

int Length { get; }

Возвращает текущую длину строки this (то есть количество содержащихся в ней символов).

char this [int index] { get; }

Возвращает символ строки this с индексом index (символы строки, подобно элементам массива, индексируются от 0). Данное свойство-индексатор в языке C# не имеет имени; параметр index в квадратных скобках должен указываться непосредственно после имени объекта-строки, например, s[2]. При указании недопустимого индекса (меньшего 0 или большего Length – 1) возбуждается исключение IndexOutOfRangeException.

Отметим, что в качестве индексируемого объекта-строки можно указывать не только переменную, но и любое выражение типа string. Например, "ABCD"[2] вернет символ 'C'. Экземплярные методы класса string также можно применять к любому строковому выражению.

Сравнение строк

Все методы, сравнивающие две строки (Compare, CompareOrdinal и CompareTo), возвращают отрицательное число, если первая строка меньше второй, число 0, если строки равны, и положительное число, если первая строка больше второй (строки сравниваются посимвольно). Значение null считается меньшим, чем пустая строка "" (строка длины 0); пустая строка считается меньшей любой непустой строки; строка, все символы которой совпадают с начальными символами более длинной строки, считается меньшей, чем эта более длинная строка.

В языке C# сравнение строк на равенство/неравенство может проводиться с помощью операций == и != (при этом учитывается регистр и не учитываются региональные настройки); операции <, <=, >, >= для строк не определены.

static int Compare(string strA, string strB[, bool ignoreCase[,
CultureInfo culture]]);
static int Compare(string strA, int startA, string strB, int startB,
int count[, bool ignoreCase[, CultureInfo culture]]);

Сравнивает строки strA и strB с учетом региональных настроек. При наличии параметров startA, startB и count сравниваются подстроки строк strA и strB, начинающиеся с символов с индексами startA и startB соответственно, причем во внимание принимаются не более count символов.

Если параметр ignoreCase не указан или равен false, то при сравнении строк учитывается регистр букв; если указан параметр ignoreCase, равный true, то регистр не учитывается. При наличии параметра culture при сравнении принимаются во внимание региональные настройки, заданные этим параметром (если данный параметр отсутствует, то учитываются текущие региональные настройки). По поводу использования класса CultureInfo см. описание метода Parse.

static int CompareOrdinal(string strA, string strB);
static int CompareOrdinal(string strA, int startA,
string strB, int startB, int count);

Сравнивает строки strA и strB путем сравнения кодов символов. Таким образом, данный метод не учитывает региональные настройки, но всегда учитывает регистр букв. Это самый быстрый из методов сравнения.

Смысл параметров startA, startB и count — тот же, что и для метода Compare.

int CompareTo(string str);
bool StartsWith(string str);
bool EndsWith(string str);

Метод CompareTo сравнивает строки this и str. Метод StartsWith возвращает true, если строка this начинается со строки str, и false в противном случае. Метод EndsWith возвращает true, если строка this оканчивается строкой str, и false в противном случае. Все три метода учитывают регистр букв и текущие региональные настройки.

Поиск в строке

Поиск в строках осуществляется с учетом регистра и без учета региональных настроек.

int IndexOf(char value[, int start[, int count]]);
int IndexOf(string value[, int start[, int count]]);

Осуществляет в строке this поиск первого вхождения символа (или строки) value и возвращает индекс найденного символа (или начального символа найденной строки); если ни одного вхождения value не найдено, то возвращается –1. Параметр start определяет индекс символа, начиная с которого выполняется поиск (при отсутствии параметра start поиск осуществляется с начала строки). Параметр count определяет количество символов в строке this, которые анализируются в ходе поиска (символы перебираются в направлении от начала к концу строки; при отсутствии параметра count просматривается вся строка). Если параметр value является пустой строкой "", то метод возвращает значение параметра start (или 0, если параметр start не указан). При указании неверных параметров start или count, а также в случае параметра value, равного null, возбуждается исключение.

int LastIndexOf(char value[, int start[, int count]]);
int LastIndexOf(string value[, int start[, int count]]);

Осуществляет в строке this поиск последнего вхождения символа (или строки) value и возвращает индекс найденного символа (или начального символа найденной строки); если ни одного вхождения не найдено, то возвращается –1. Параметр start определяет индекс символа, начиная с которого выполняется поиск (при отсутствии параметра start поиск осуществляется с конца строки). Параметр count определяет количество символов, которые анализируются в ходе поиска (символы перебираются в направлении от конца к началу строки; при отсутствии параметра count просматривается вся строка). Если параметр value является пустой строкой, то метод возвращает значение параметра start (или индекс последнего символа в строке, если параметр start не указан). При указании неверных параметров start или count, а также в случае параметра value, равного null, возбуждается исключение.

int IndexOfAny(char[] values[, int start[, int count]]);

Осуществляет в строке this поиск любого из символов, указанных в массиве values, и возвращает индекс первого найденного символа (или –1, если ни один из символов в строке не обнаружен). Параметры start и count имеют тот же смысл, что и для метода IndexOf.

int LastIndexOfAny(char[] values[, int start[, int count]]);

Осуществляет в строке this поиск любого из символов, указанных в массиве values, и возвращает индекс последнего найденного символа (или –1, если ни один из символов в строке не обнаружен). Параметры start и count имеют тот же смысл, что и для метода LastIndexOf.

Преобразование строки

Поскольку объекты типа string являются неизменяемыми, все методы, связанные с преобразованием строки, не изменяют исходную строку, а возвращают новую строку с нужными изменениями.

string Insert(int start, string str);

Возвращает строку, полученную из строки this вставкой в нее строки str (вставка осуществляется в позицию с индексом start). Если параметр start равен длине строки this, то строка str добавляется в конец строки this; если параметр start отрицателен или превосходит длину строки this, то возбуждается исключение ArgumentOutOfRangeException.

string Remove(int start[, int count]);

Возвращает строку, полученную из строки this удалением из нее count символов, начиная с символа с индексом start. Если параметр count не указан, то удаляются все символы вплоть до конца строки. Заметим, что вариант без параметра count доступен в .NET Framework, начиная с версии 2.0.

string Replace(char value, char newValue);
string Replace(string value, string newValue);

Возвращает строку, полученную из строки this заменой всех вхождений символа (или строки) value на символ (или, соответственно, строку) newValue. Поиск осуществляется с учетом регистра; в случае поиска строки учитываются текущие региональные настройки. Во вставленных фрагментах newValue повторный поиск фрагментов value не производится. Если в качестве newValue указана пустая строка "" или null, то результатом работы метода является удаление всех вхождений строки value; если в качестве value указана пустая строка или null, то возбуждается исключение.

string Substring(int start[, int count]);

Возвращает подстроку строки this. Если параметр count указан, то подстрока содержит count символов строки this, начиная с символа с индексом start; если параметр count не указан, то подстрока содержит все символы строки this, начиная с символа с индексом start. Если параметр start равен длине строки или count равен 0, то возвращается пустая строка. Если параметр start отрицателен или превышает длину строки, а также если в строке содержится менее count символов, начиная с позиции start, то возбуждается исключение ArgumentOutOfRangeException.

string PadLeft(int total[, char padChar]);
string PadRight(int total[, char padChar]);

Метод PadLeft возвращает строку this, дополненную слева символами padChar так, чтобы суммарная длина строки стала равна total. Если параметр padChar не указан, то строка дополняется слева пробелами. Метод PadRight работает аналогично, за исключением того, что символы padChar добавляются к исходной строке справа. Если значение параметра total меньше исходной длины строки this, то любой из данных методов возвращает строку, равную строке this.

string Trim([params char[] trimChars]);
string TrimStart([params char[] trimChars]);
string TrimEnd([params char[] trimChars]);

Метод Trim возвращает строку, полученную из строки this удалением начальных и конечных символов, входящих в массив trimChars. Методы TrimStart и TrimEnd работают аналогично, за исключением того, что TrimStart удаляет символы только из начала строки, а TrimEnd — только из ее конца.

Атрибут params означает, что вместо параметра-массива можно указывать произвольное количество параметров типа char. Методы допустимо вызывать без параметров; в этом случае удаляются все пробельные символы (то есть пробелы, символы табуляции, перехода на новую строку и т. п. — см. описание метода IsWhiteSpace).

string ToLower([CultureInfo culture]);
string ToUpper([CultureInfo culture]);

Метод ToLower возвращает строку, полученную из строки this преобразованием всех букв к нижнему регистру; метод ToUpper возвращает строку, полученную из строки this преобразованием всех букв к верхнему регистру. Если указан параметр culture, то при преобразовании учитываются региональные настройки, определяемые данным параметром (подробности, связанные с использованием объектов типа CultureInfo, приводятся в описании метода Parse); если параметр не указан, то используются текущие региональные настройки. И в случае региональных настроек для России ("ru-RU"), и в случае настроек для США ("en-US") данные методы правильно обрабатывают как латинские, так и русские буквы.

Объединение и разбиение строк

Для сцепления (конкатенации) нескольких строк в языке C# предусмотрена операция +. При сцеплении строки s и объекта x другого типа в языке C# автоматически выполняется преобразование объекта x к его строковому представлению (то есть для x вызывается метод ToString).

static string Concat(params object[] a);

Возвращает строку, полученную в результате сцепления строковых представлений всех элементов массива a. Атрибут params означает, что вместо параметра-массива можно указывать произвольное количество параметров типа object, то есть фактически любого типа, так как любой тип является потомком класса object.

static string Join(string sep, string[] a[, int start, int count]);

Возвращает строку, полученную в результате объединения элементов массива строк a, при котором между соседними элементами помещается строка-разделитель sep. Если параметры start и count указаны, то выполняется объединение count элементов массива a, начиная с элемента с индексом start. Если параметры start и count не указаны, то выполняется объединение всех элементов массива a.

char[] ToCharArray([int start, int count]);

Возвращает массив символов, входящих в строку this. При наличии параметров start и count в массив помещается count символов, начиная с символа с индексом start; если параметры отсутствуют, то в массив помещаются все символы строки. Если параметр count равен 0, то возвращается массив нулевого размера (но не равный null). Если параметр start отрицателен или превышает длину строки, а также если в строке содержится менее count символов, начиная с позиции start, то возбуждается исключение ArgumentOutOfRangeException.

void CopyTo(int start, char[] array, int arrayStart, int count);

Копирует count символов из строки this (начиная с символа с индексом start) в символьный массив array, записывая их в элементы массива array, начиная с элемента с индексом arrayStart. Если числовые параметры отрицательны или после стартовых элементов в строке this или массиве array содержится менее count элементов, то возбуждается исключение ArgumentOutOfRangeException.

string[] Split([params char[] sep]);
string[] Split(char[] sep, int count);
string[] Split(char[] sep[, int count], StringSplitOptions opt);
string[] Split(string[] sep[, int count], StringSplitOptions opt);

Разбивает строку this на подстроки, разделенные символами (или строками), указанными в массиве sep, и возвращает массив полученных подстрок. Если указан параметр count, то возвращается не более count первых найденных подстрок (если count отрицателен, то возбуждается исключение ArgumentOutOfRangeException). Параметр opt может принимать одно из двух значений перечислимого типа StringSplitOptions: None и RemoveEmptyEntries. При значении StringSplitOptions.RemoveEmptyEntries в массив подстрок не помещаются пустые строки (то есть подряд идущие разделители считаются одним разделителем, а начальные и завершающие разделители игнорируются); при значении StringSplitOptions.None или при отсутствии параметра opt массив подстрок может содержать пустые строки (если в строке this располагается подряд несколько разделителей или строка this содержит начальные или завершающие разделители). Сами разделители в результирующие подстроки не включаются. Варианты метода Split, содержащие параметр opt, доступны в .NET Framework, начиная с версии 2.0.

Атрибут params означает, что вместо одного параметра-массива в данном варианте метода можно указывать произвольное количество параметров типа char.

При поиске разделителей не учитываются региональные настройки и учитывается регистр символов. Если массив разделителей равен null или метод Split вызван без параметров, то разделителем считается любой пробельный символ (см. описание метода IsWhiteSpace). В случае строкового массива разделителей важным является порядок его элементов, поскольку при поиске разделителей элементы массива sep перебираются по возрастанию индексов. Например, если в массиве разделителей содержатся строки "B" и "BC" в указанном порядке, то строка "ABCD" будет разбита на подстроки "A" и "CD", а если изменить порядок строк в массиве sep, то будут возвращены подстроки "A" и "D".

Класс StringBuilder

Основной особенностью объектов класса StringBuilder является возможность их изменения, в том числе посимвольного (это отличает данный класс от класса string, объекты которого являются неизменяемыми). Класс StringBuilder определен в пространстве имен System.Text.

Свойства и конструкторы

int Capacity { get; set; }

Возвращает и позволяет изменять емкость строки, то есть размер массива символов, выделенного для ее хранения. По умолчанию емкость равна 16. Если в результате преобразования строки текущей емкости оказывается недостаточно, она автоматически удваивается. Следует заметить, что при изменении емкости в памяти создается новый массив символов (требуемого размера) и в него копируются символы строки из прежнего массива, после чего прежний массив считается «мусором» и в дальнейшем уничтожается. Таким образом, изменение емкости строки StringBuilder — достаточно ресурсоемкая операция.

int MaxCapacity { get; }

Возвращает максимальную емкость строки. По умолчанию равна значению int.MaxValue. Другое ее значение может быть указано в конструкторе при создании объекта типа StringBuilder. Изменить значение свойства MaxCapacity уже созданного объекта нельзя.

int Length { get; set; }

Возвращает и позволяет изменить текущую длину строки, то есть количество фактически содержащихся в ней символов. При уменьшении свойства Length строка урезается справа, при увеличении — дополняется справа пробелами. Увеличение свойства Length может приводить к увеличению емкости (Capacity) строки. При попытке задать свойству Length отрицательное значение или значение, большее максимальной емкости (MaxCapacity), возбуждается исключение.

char this [int index] { get; set; }

Возвращает и позволяет изменить символ строки с индексом index (символы, подобно элементам массива, индексируются от 0). Данное свойство-индексатор в языке C# не имеет имени; параметр index в квадратных скобках должен указываться непосредственно после имени объекта-строки, например, s[2]. Данное свойство отличается от одноименного свойства класса string тем, что доступно как для чтения, так и для записи.

Если параметр index является недопустимым (то есть меньше 0 или больше Length — 1), то возбуждается исключение IndexOutOfRangeException (при доступе на чтение) или ArgumentOutOfRangeException (при доступе на запись).

StringBuilder([int capacity[, int maxCapacity]]);

Создает объект типа StringBuilder заданной емкости capacity и максимальной емкости maxCapacity. Если параметр maxCapacity отсутствует, то максимальная емкость полагается равной int.MaxValue. Если параметр capacity отсутствует, то емкость полагается равной 16. Созданный объект является пустой строкой, то есть его свойство Length равно 0.

StringBuilder(string value[[, int start, int count], int capacity]);

Создает объект типа StringBuilder заданной емкости capacity и инициализирует его строкой value или, при наличии параметров start и count, подстрокой строки value длины count, начинающейся с символа с индексом start. Параметр value может быть равен null, в этом случае созданный объект является пустой строкой. При указании неверных значений параметров start или count возбуждается исключение ArgumentOutOfRangeException. Если параметр capacity не указан или его значение меньше, чем длина L созданной строки, то емкость полагается равной числу max{16, 2N}, где 2N — минимальная степень, большая или равная L.

Совместимость объектов string и StringBuilder

Объекты string и StringBuilder не совместимы по присваиванию. Для преобразования «обычной» строки (типа string) к объекту StringBuilder необходимо воспользоваться соответствующим конструктором класса StringBuilder. Для обратного преобразования (объекта StringBuilder к обычной строке) надо воспользоваться методом ToString класса StringBuilder, описанным ниже.

string ToString([int start, int count]);

Возвращает строку, содержащуюся в объекте this типа StringBuilder. При наличии параметров возвращает подстроку длины count, которая начинается с символа с индексом start. При указании неверных значений параметров возбуждается исключение ArgumentOutOfRangeException.

Метод ToString выполняется быстро и не приводит копированию символов исходной строки типа StringBuilder: созданная строка типа string просто ссылается на тот же вспомогательный массив символов (или часть массива), что и исходная строка StringBuilder. Однако изменение строки StringBuilder после вызова ее метода ToString приводит к копированию ее символов в новый вспомогательный массив (для сохранения постоянства строки, возвращенной методом ToString). Поэтому для повышения эффективности программы желательно не изменять строку StringBuilder после вызова ее метода ToString (или, иначе говоря, желательно завершить формирование строки StringBuilder перед вызовом ее метода ToString).

Преобразование строки типа StringBuilder

Текст числовой_тип, используемый при описании параметра value, обозначает любой из рассматриваемых в настоящем пособии числовых типов.

StringBuilder Append(object value);
StringBuilder Append(bool value);
StringBuilder Append(числовой_тип value);
StringBuilder Append(char value[, int charCount]);
StringBuilder Append(char[] value[, int start, int count]);
StringBuilder Append(string value[, int start, int count]);
StringBuilder AppendLine([string value]);

Добавляет к объекту-строке this типа StringBuilder строковое представление параметра value (или элементы символьного массива, если value имеет тип char[]) и возвращает измененный объект this. При наличии параметра charCount добавляет к строке charCount копий символа value. Параметры start и count определяют, какая часть символьного массива (или строки) будет добавлена к объекту this (добавляется count элементов, начиная с элемента с индексом start); при отсутствии этих параметров добавляются все элементы массива (соответственно, вся строка). Для получения строкового представления параметра value числового типа, типа bool и object используется метод ToString данного типа. Метод AppendLine добавляет к объекту this маркер конца строки EOLN (при наличии параметра value данный параметр добавляется к объекту this перед добавлением маркера EOLN). Метод AppendLine доступен в .NET Framework, начиная с версии 2.0.

Для добавления данных к объекту-строке типа StringBuilder можно также использовать метод AppendFormat, который описывается в разделе, посвященном форматированию.

Добавление данных к строке StringBuilder — более эффективная операция, чем сцепление обычных строк, поэтому сцепление заранее неизвестного числа строк рекомендуется проводить с помощью их добавления к строке StringBuilder (см. последний вариант решения задания String10, а также [2], п. 21.20).

StringBuilder Insert(int index, object value);
StringBuilder Insert(int index, bool value);
StringBuilder Insert(int index, числовой_тип value);
StringBuilder Insert(int index, char value);
StringBuilder Insert(int index, char[] value[, int start, int count]);
StringBuilder Insert(int index, string value[, int stringCount]);

Вставляет в строку this типа StringBuilder, начиная с позиции index, строковое представление параметра value (или элементы символьного массива, если value имеет тип char[]) и возвращает измененный объект this. При наличии параметра stringCount вставляет stringCount копий строки value. Если указаны параметры start и count, то вставляет count элементов массива, начиная с элемента с индексом start (если параметры не указаны, то вставляет все элементы массива). Для получения строкового представления параметра value типа bool, object и числовых типов используется метод ToString данного типа. Если index меньше 0 или больше Length, то возбуждается исключение ArgumentOutOfRangeException.

StringBuilder Remove(int start, int count);

Удаляет из объекта-строки this count символов, начиная с символа с индексом start и возвращает измененный объект this. При выполнении данного метода емкость (Capacity) объекта this не изменяется, а длина (Length) уменьшается на значение параметра count. Если start меньше 0 или сумма start + count больше Length, то возбуждается исключение ArgumentOutOfRangeException.

StringBuilder Replace(char value, char newValue[, int start, int count]);
StringBuilder Replace(string value, string newValue[, int start, int count]);

Заменяет в объекте-строке this все вхождения символа (или строки) value на символ (или, соответственно, строку) newValue и возвращает измененный объект this. Поиск вхождений выполняется без учета региональных настроек и с учетом регистра. При наличии параметров start и count преобразованию подвергается только часть объекта-строки длины count, начиная с символа с индексом start. Параметр newValue может быть равен null или пустой строке ""; в этом случае все вхождения value удаляются. Если параметр value равен null или является пустой строкой, а также если start меньше 0 или сумма start + count больше Length, то возбуждается исключение.

Дополнительные методы класса StringBuilder

void CopyTo(int start, char[] array, int arrayStart, int count);

Копирует count символов из строки this (начиная с символа с индексом start) в символьный массив array, записывая их в элементы массива array, начиная с элемента с индексом arrayStart. Если числовые параметры отрицательны или после стартовых элементов в строке this или массиве array содержится менее count элементов, то возбуждается исключение ArgumentOutOfRangeException. Данный метод доступен в .NET Framework, начиная с версии 2.0.

int EnsureCapacity(int capacity);

Изменяет емкость Capacity объекта this следующим образом: если текущая емкость меньше, чем значение параметра, то она полагается равной данному параметру; в противном случае емкость объекта не изменяется. Метод возвращает новое значение емкости объекта this.

Форматирование данных

Форматирование по умолчанию и явное форматирование

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

string ToString(string fmt, IFormatProvider p);

Первый параметр (fmt) определяет форматную строку, а второй (p) — региональные настройки. Данный вариант метода ToString входит в интерфейс IFormattable, который реализуют все классы, допускающие явное форматирование. Кроме того, многие классы (в частности, все числовые типы) реализуют варианты метода ToString, содержащие только один из указанных параметров.

Использование региональных настроек было продемонстрировано при описании метода Parse). В следующих двух пунктах основное внимание будет уделено форматным строкам.

Спецификаторы формата

Простейшим вариантом форматной строки является спецификатор формата, задаваемый единственным символом (например, "D"), который может быть дополнен спецификатором точности (например, "D6"). Приведем основные числовые спецификаторы формата.

  • C или c — денежный формат (currency); спецификатор точности определяет количество дробных знаков. По умолчанию это количество, а также обозначение и размещение знака валюты определяется региональными настройками.
  • D или d — десятичный целочисленный формат (decimal); спецификатор точности определяет минимальное количество цифр в числе, при необходимости число дополняется слева нулями.
  • E или e — экспоненциальный числовой формат (exponential); спецификатор точности определяет количество дробных знаков, по умолчанию это количество равно 6. Вид десятичного разделителя определяется региональными настройками; регистр буквы e, предваряющей порядок числа, совпадает с регистром спецификатора формата.
  • F или f — числовой формат с фиксированной точкой (fixed-point); спецификатор точности определяет количество дробных знаков. Количество дробных знаков по умолчанию и вид десятичного разделителя определяются региональными настройками.
  • P или p — процентный формат (percent); спецификатор точности определяет количество дробных знаков. Число дробных знаков по умолчанию, вид десятичного разделителя и размещение знака % определяются региональными настройками.
  • X или x — шестнадцатеричный целочисленный формат (hexadecimal); спецификатор точности определяет минимальное количество цифр в числе, при необходимости число дополняется слева нулями. Регистр букв, используемых в шестнадцатеричном представлении, совпадает с регистром спецификатора формата.
  • G или g — общий формат (general), в котором используется наиболее краткое из возможных представлений числа данного типа. В частности, для вещественных чисел в качестве G выбирается более короткий из форматов E или F.

Общий формат G определен для всех типов, допускающих форматирование. Если форматная строка в методе ToString не указана, является пустой строкой "" или равна null, то объект отображается в формате G.

Следует заметить, что для числовых данных можно настраивать формат, используя так называемые символы-заполнители (см., например, [5], гл. 25, с. 450 и [8], гл. 20, с. 565). Однако в большинстве случаев форматирования вполне достаточно тех возможностей, которые предоставляются стандартными спецификаторами формата.

Одновременное форматирование нескольких объектов: метод Format

Для одновременного форматирования нескольких объектов с помощью одной форматной строки основным инструментом является метод Format класса string:

static string Format([IFormatProvider p,] string fmt, params object[] args);

Параметр args не обязательно должен быть «настоящим» массивом объектов; атрибут params означает, что вместо одного этого параметра можно указывать произвольное число параметров типа object, то есть фактически любого типа, поскольку любой тип является потомком класса object. При выполнении метода все эти параметры считаются элементами одного массива args, длина которого равна количеству параметров. Отметим, что имеются перегруженные варианты метода Format, в которых вместо параметра-массива args указывается один, два или три параметра типа object (эти варианты обеспечивают генерацию более эффективного исполняемого кода, поскольку в них не используется вспомогательный массив).

Если указан параметр p, то при форматировании учитываются региональные настройки, соответствующие этому параметру; если параметр p отсутствует, то используются текущие региональные настройки.

В форматной строке fmt можно указывать обычный текст и форматные настройки для каждого из форматируемых параметров. Эти настройки имеют вид

{ind[, width][:spec]}

Опишем атрибуты, входящие в форматные настройки:

  • ind — целое число, которое определяет индекс форматируемого элемента в массиве args (индексация ведется от 0; данный атрибут является обязательным);
  • width — целое число, модуль которого задает минимальную ширину поля вывода (то есть минимальное число позиций, отводимое для форматируемого элемента), а знак определяет способ выравнивания элемента в пределах поля вывода;
  • spec — строка, которая задает спецификатор формата для данного элемента (эта строка может также содержать формат, явно определяемый с помощью символов-заполнителей). Между двоеточием и строкой spec не должно быть пробелов.

Если атрибут width отсутствует, то используется ширина поля вывода, минимально необходимая для отображения отформатированного элемента. Если отформатированный элемент не занимает всего поля вывода, то он дополняется пробелами: пробелы добавляются слева, если атрибут width положителен, и справа, если атрибут width отрицателен. Таким образом, при положительном значении width выполняется выравнивание по правой границе поля вывода, а при отрицательном — по левой границе.

Если атрибут spec отсутствует, то выбирается вариант форматирования по умолчанию (этот вариант соответствует спецификатору формата G).

Для обрамления каждой форматной настройки используются фигурные скобки. Если требуется указать фигурную скобку { или } в обычном тексте, входящем в строку fmt, ее надо ввести дважды: {{ или }}.

При ошибочном задании форматной строки fmt (в частности, при указании недопустимого значения атрибута ind) возбуждается исключение FormatException.

Приведем пример. При вызове метода string.Format("Формат D:{0, 6:D4}, формат X:{0, 6:X4}", 246) будет возвращена следующая строка:

Формат D:  0246, формат X:  00F6

Одновременное форматирование нескольких объектов может выполняться и в некоторых других методах. Например, подобной возможностью обладает метод AppendFormat класса StringBuilder:

StringBuilder AppendFormat([IFormatProvider p,] string fmt, params object[] args);

Данный метод выполняет форматирование массива объектов args с помощью форматной строки fmt (учитывая региональные настройки, если они переданы с помощью параметра p), после чего добавляет сформированную строку к объекту this, вызвавшему этот метод. Метод возвращает новое значение объекта this.

Возможность форматирования обеспечивается также в методах, связанных с выводом данных в текстовые потоки (см. описания методов Write и WriteLine класса StreamWriter и одноименных методов класса Console).

Заметим, что метод Format может использоваться и для сцепления строк, причем делает это более эффективно, чем обычная операция сцепления (см. [2], п. 21.19).

Кодирование и декодирование символьных данных

Кодирование данных по умолчанию: формат UTF-8

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

По умолчанию при записи символьных данных в файлы применяется формат кодирования UTF-8 (аббревиатура UTF расшифровывается как Unicode Transformation Format, то есть «формат преобразования Unicode-символов»). В этом формате для хранения символов с кодами от 0 до 127 (символов ASCII) используется один байт, содержащий код символа (один байт равен 8 битам — именно поэтому в имени данного формата указано число 8). Таким образом, при кодировании этих символов размер необходимой для их хранения памяти уменьшается вдвое (напомним, что в кодировке Unicode, используемой в приложениях .NET, каждый символ занимает 2 байта). Подобное уменьшение важно по двум причинам. Во-первых, символы ASCII — это наиболее часто используемые символы, поскольку в их число входят цифры, латинские буквы, стандартные знаки препинания и знаки операций, поэтому экономия дискового пространства оказывается довольно значительной. Во-вторых, текст, содержащий только символы ASCII в 8-битной кодировке, будет правильно обрабатываться как .NET-приложениями (которые при этом автоматически выполняют его обратное декодирование к стандартному формату Unicode), так и традиционными Windows-приложениями (которые интерпретируют данный текст как текст в «обычной» ASCII-кодировке).

Символы Unicode с кодами от 128 до 2047 (содержащие символы различных европейских и среднеазиатских языков) преобразуются при кодировании UTF-8 в 2 байта, а прочие символы преобразуются в 3 или 4 байта.

Если требуется прочесть или сохранить символьные данные в другом формате, то формат кодирования необходимо явно указать в конструкторах соответствующих потоков-оболочек для чтения (типа BinaryReadеr или StreamReader) или для записи (типа BinaryWriter или StreamWriter).

Явная установка формата кодирования: класс Encoding

Для указания формата кодирования используется класс Encoding, определенный в пространстве имен System.Text и предоставляющий, наряду с другими свойствами и методами, набор классовых свойств и методов, задающих различные форматы кодирования. Перечислим самые важные из них.

static Encoding UTF8 { get; }

Возвращает описанный выше формат кодирования UTF-8 (при файловом вводе-выводе используется по умолчанию).

static Encoding Unicode { get; }

Возвращает формат кодирования UTF-16, переводящий символы в Unicode-кодировку. При чтении/записи символов Unicode с использованием данного формата кодирования никакого преобразования символов не происходит. Это обеспечивает более высокую скорость операций ввода-вывода (по сравнению с операциями, использующими другие форматы кодирования), но приводит к увеличению размеров файла.

static Encoding Default { get; }
Возвращает формат кодирования, соответствующий той кодовой странице 8-битной ANSI-кодировки, которая используется по умолчанию операционной системой Windows (для русской версии Windows это кодовая страница 1251 «Cyrillic (Windows)»).
static Encoding GetEncoding(int codepage);

Возвращает формат кодирования, соответствующий кодовой странице 8-битной кодировки с номером codepage. Например, для Windows-кодировки кириллицы «Cyrillic (Windows)» параметр codepage надо положить равным 1251, а для того чтобы записать или считать данные в альтернативной кодировке кириллицы для DOS «Cyrillic (DOS)», надо указать кодовую страницу 866.

При выполнении учебных заданий с применением задачника Programming Taskbook следует всегда использовать формат кодирования, соответствующий ANSI-кодировке Windows по умолчанию, указывая при создании потоков для ввода и вывода параметр Encoding.Default.


Модуль 3. Символы и строки

Контрольные вопросы

Для каждого вопроса щелкните мышью на радиокнопке с номером варианта ответа, который, по вашему мнению, является правильным. Во всех фрагментах кода предполагается, что в программе указаны операторы using со всеми требуемыми пространствами имен. После того как вы ответите на все вопросы, проверьте правильность ответов, нажав на кнопку «Проверка».

1. Укажите имя, которое не является именем метода класса char.

(1) | IsSpace | (2) | IsLetter
(3) | IsDigit | (4) | IsPunctuation

2. Укажите верное высказывание.

(1) | Символы в строках типа string индексируются от 1 | (2) | Указание неверного индекса i при попытке доступа к символу s[i] строки s всегда приводит к возбуждению исключения
(3) | С помощью свойства Length класса string можно изменять длину строки | (4) | С помощью индексного доступа s[i] к символам строки s можно изменять ее отдельные символы

3. Строка s типа string содержит текст " a  b  c " (в начале и конце строки имеется один пробел, между соседними буквами — два пробела). Укажите, какое число будет выведено на экран при выполнении следующего оператора:

Console.WriteLine(s.Split().Length);
(1) | 3 | (2) | 5
(3) | 7 | (4) | 9

4. В строке s типа StringBuilder содержится более 5 символов. Для того чтобы уменьшить ее длину до 5 символов, учащийся использовал следующий оператор:

s.Length = 5;

Что произойдет при обработке этого оператора?

(1) | При компиляции оператора будет выведено сообщение об ошибке | (2) | При выполнении оператора будет возбуждено исключение
(3) | В результате выполнения оператора из строки s будут удалены все символы, кроме пяти начальных | (4) | В результате выполнения оператора из строки s будут удалены все символы, кроме пяти конечных

5. Укажите спецификатор формата, который нельзя использовать для чисел типа double.

(1) | C | (2) | F
(3) | P | (4) | X

6. Для получения в строке s типа string строкового значения переменной целого типа n вместе с комментарием учащийся использовал следующий оператор

s = string.Format("n = {0}" + n);

Укажите, что произойдет при обработке данного оператора, если считать, что текущее значение переменной n равно 10.

(1) | При компиляции оператора будет выведено сообщение об ошибке | (2) | При выполнении оператора будет возбуждено исключение
(3) | В результате выполнения оператора строка s будет содержать текст "n = 10" | (4) | В результате выполнения оператора строка s будет содержать текст "n = {0}10"


Модуль 3. Символы и строки

Итоговое задание

Ввести с клавиатуры целое число N, лежащее в диапазоне от 10 до 70 (при вводе значения, не лежащего в указанном диапазоне, положить число N равным 70). Сгенерировать с помощью датчика случайных чисел строку длины N, состоящую из символов пробела, цифр «1» и «2» и строчных (маленьких) латинских букв от «a» до «d». Вывести на экран полученную строку, заключив ее в двойные кавычки. Для полученной строки вывести следующие данные:

  • количество D содержащихся в строке цифр;
  • количество L содержащихся в строке букв;
  • количество W содержащихся в строке слов (словом считать последовательность символов, не содержащую пробелов, и ограниченную пробелами или началом/концом строки);
  • самое длинное слово в строке (если таких слов несколько, то вывести последнее из них, если строка состоит только из пробелов, то вывести текст EMPTY).

Преобразовать исходную строку следующим образом (после выполнения каждого из указанных преобразований выводить полученную строку на экран, заключая ее в двойные кавычки; каждое последующее преобразование должно применяться к строке, полученной в ходе выполнения предыдущего преобразования):

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

Если в результате одного из преобразований строка станет пустой, то вывести только обрамляющие ее кавычки: "".

Приведем образец текста, который должен выводиться программой, выполняющей проектное задание:

N = 60
" 221 b a2d11d21cd1  adabbbbc  d 1211d 1 b22d2d2bcd212aa1dca2"
D = 23
L = 27
W = 8
Самое длинное слово: b22d2d2bcd212aa1dca2
Результат первого преобразования:
"221 b a2d11d21cd1 adabbbbc d 1211d 1 b22d2d2bcd212aa1dca2"
Результат второго преобразования:
"221 B A2d11d21cd1 Adabbbbc D 1211d 1 B22d2d2bcd212aa1dca2"
Результат третьего преобразования:
"B Adabbbbc D"


Модуль 4. Двоичные файлы

Комплексная цель

Изучить основные алгоритмы, связанные с обработкой двоичных файлов, в том числе использующие прямой доступ к файловым элементам. Ознакомиться с классами FileStream, BinaryReader и BinaryWriter стандартной библиотеки .NET. Рассмотреть особенности обработки нетипизированных и строковых двоичных файлов.

Структура модуля

  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание


Модуль 4. Двоичные файлы

Решения типовых задач

Все классы библиотеки .NET Framework, обеспечивающие работу с файлами, определены в пространстве имен System.IO. Для того чтобы при обращении к именам этих классов не требовалось дополнительно указывать имя пространства имен, в начале программы надо поместить следующий оператор:

using System.IO;

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

Создание файла, ввод и вывод его элементов: File48

File48. Даны три файла целых чисел одинакового размера с именами SA, SB, SC и строка SD. Создать новый файл с именем SD, в котором чередовались бы элементы исходных файлов с одним и тем же номером:

A1, B1, C1, A2, B2, C2, … .

Напомним, что программу-заготовку для решения данного задания можно создать с помощью ярлыка Load.lnk. В состав данной заготовки будет входить файл File48.cs. Функция Solve, описанная в этом файле, будет содержать вызов функции Task с параметром "File48", инициализирующий данное задание (см. текст аналогичного файла-заготовки для задания Minmax1, приведенный в модуле № 1).

После запуска этой программы-заготовки на экране появится окно, подобное приведенному ниже.

В первой строке раздела исходных данных указаны имена трех исходных файлов (SA, SB и SC) и одного результирующего (SD). В трех последних строках раздела исходных данных показано содержимое исходных файлов. Для отображения содержимого каждого файла отводится по одной строке. Элементы файлов изображаются бирюзовым цветом, чтобы подчеркнуть их отличие от обычных исходных данных (желтого цвета) и комментариев (светло-серого цвета).

Поскольку размер файлов, как правило, превышает количество элементов, которое может уместиться на одной экранной строке, предусмотрена возможность прокрутки (листания) элементов файла с помощью мыши или клавиатуры. Для листания содержимого файла с помощью мыши достаточно щелкнуть на одной из двух кнопок, расположенных справа от строки с содержимым нужного файла. С помощью клавиатуры можно выполнять поэлементную или «постраничную» прокрутку. Для поэлементной прокрутки используются клавиши со стрелками [Right] или [Down] (прокрутка вперед) и [Left] или [Up] (прокрутка назад); для «постраничной» прокрутки, обеспечивающей переход к новой «порции» элементов, умещающейся на экранной строке, используются клавиши [Ctrl]+[Right] или [PgDn] (прокрутка вперед) и [Ctrl]+[Left] или [PgUp] (прокрутка назад). Кроме того, клавиша [Home] обеспечивает перемещение на начало, а клавиша [End] — на конец файла.

Примечание. Режим листания предусмотрен также для ситуации, когда текст формулировки задания содержит более 5 строк. В этом случае кнопки, аналогичные кнопкам листания для файлов, появляются рядом с разделом формулировки задания. Для прокрутки текста задания, помимо этих кнопок, можно использовать все описанные выше клавиатурные комбинации.

Если окно программы содержит несколько строк с файловыми данными, то прокрутка с помощью клавиатуры выполняется для выделенного файла (выделенный файл помечается в окне маркером в виде черного кружка, расположенным слева от файловой строки). Для смены выделенного файла предусмотрены клавиши [+] и [–]: первая из них обеспечивает циклический перебор всех отображенных на экране файлов в направлении сверху вниз, а вторая — в направлении снизу вверх.

Для того чтобы определить, какие элементы файла отображаются в данный момент на экране, предусмотрен указатель текущей позиции, значение которого равно номеру первого из элементов, отображенных на экране. Данный указатель располагается в начале строки с элементами файла и отделяется от них двоеточием. Нумерация элементов файла начинается с единицы.

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

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

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

Поскольку мы собираемся работать с четырьмя файлами, удобно предусмотреть массив потоков (как и в предыдущих примерах решений, будем приводить только содержимое функции Solve):

Task("File48");
FileStream[] f = new FileStream[4];
for (int i = 0; i < 3; i++)
    f[i] = new FileStream(GetString(), FileMode.Open);

Мы намеренно ограничились тремя итерациями цикла, оставив непрочитанным имя результирующего файла. Прочитанные имена файлов не сохраняются в строковых переменных, а сразу передаются в конструкторы соответствующих потоков, поскольку после создания потоков все остальные действия с файлами в нашей программе будут осуществляться с использованием потоков, без обращения к именам файлов.

Запуск нового варианта программы уже не будет считаться ознакомительным, поскольку в программе выполняется ввод исходных данных. Так как имя результирующего файла осталось непрочитанным, этот вариант решения будет признан неверным и приведет к сообщению «Введены не все требуемые исходные данные. Количество прочитанных данных: 3 (из 4)».

Изменим программу так, чтобы в цикле выполнялось не 3, а 4 итерации:

for (int i = 0; i < 4; i++)

Теперь запуск программы приведет к следующему сообщению об ошибке: «Error System.IO.FileNotFoundException: Could not find file "C:\PT4Work\d8kf.tst"» (имя файла, естественно, будет другим). Сообщение задачника, начинающееся со слова «Error», означает, что при работе программы произошла ошибка времени выполнения. В программах, разработанных для платформы .NET, любая ошибка времени выполнения приводит к возбуждению исключения (см. пункт, посвященный исключениям, в модуле № 3). В тексте сообщения вначале указывается тип возникшего исключения (в данном случае FileNotFoundException), а затем — его краткое описание на английском языке.

Чтобы избежать ошибки времени выполнения, отсутствующий файл результатов следует открыть не в режиме FileMode.Open, а в режиме FileMode.Create, что обеспечит создание данного файла. Для этого восстановим прежнее количество итераций цикла, равное 3, а после цикла добавим оператор, открывающий последний, четвертый файловый поток в режиме создания:

f[3] = new FileStream(GetString(), FileMode.Create);

Запуск этого варианта программы не приведет к ошибке времени выполнения, однако будет выведено сообщение «Результирующий файл не закрыт».

Для закрытия файловых потоков предусмотрен специальный метод Close, который рекомендуется вызывать сразу после того, как работа с данным потоком закончена (особенно важно выполнять эту рекомендацию в отношении потоков, связанных с записью данных в файл, поскольку при отсутствии явного вызова метода Close часть информации, предназначенной для записи в файл, может быть потеряна — см. описание метода Close в справочных сведениях, а также [2], п. 24.7). Заметим, что если при выполнении заданий с использованием задачника Programming Taskbook результирующие файловые потоки не закрыты методом Close, то решение будет считаться ошибочным (с указанной выше диагностикой).

В нашем случае вызов метода Close удобно организовать в цикле. Внеся это добавление, мы получим следующий вариант функции Solve:

Task("File48");
FileStream[] f = new FileStream[4];
for (int i = 0; i < 3; i++)
    f[i] = new FileStream(GetString(), FileMode.Open);
f[3] = new FileStream(GetString(), FileMode.Create);
// Обработка файловых данных
for (int i = 0; i < 4; i++)
    f[i].Close();

Комментарий Обработка файловых данных расположен в том месте программы, в котором можно выполнять операции ввода-вывода для всех файлов: они уже открыты в режиме Open или Create и еще не закрыты методом Close.

При выполнении данного варианта программы результирующий файл будет создан, однако он останется пустым, то есть не содержащим ни одного элемента. Поэтому в окне задачника на информационной панели появится сообщение «Ошибочное решение», а строка, которая должна содержать элементы результирующего файла, примет вид

EOF:

Особое значение EOF (End Of File — «конец файла») для указателя текущей файловой позиции означает, что данный файл существует, но не содержит ни одного элемента. Таким образом, нам осталось реализовать фрагмент алгоритма, обеспечивающий ввод и вывод файловых данных.

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

BinaryReader[] r = new BinaryReader[3];
for (int i = 0; i < 3; i++)
    r[i] = new BinaryReader(f[i]);
BinaryWriter w = new BinaryWriter(f[3]);
for (int i = 0; i < 3; i++)
    w.Write(r[i].ReadDouble());

Данный фрагмент создает вспомогательные потоки-оболочки типа BinaryReader и BinaryWriter (обеспечивающие считывание из файла и запись в файл элементов различных типов — см. раздел о потоках-оболочках в справочные сведениях), после чего производит считывание одного вещественного элемента для каждого из трех исходных файлов и запись этих элементов в результирующий файл (в требуемом порядке). Подчеркнем, что метод ввода ReadDouble в данном случае использовать не следовало, так как по условию в файлах содержатся элементы целого типа. Тем не менее, компиляция программы пройдет успешно, а после ее запуска не произойдет ошибок времени выполнения.

Результат работы программы будет неожиданным: судя по экранной строке с содержимым результирующего файла, в него будут записаны не три, а шесть элементов, по два начальных элемента из каждого исходного файла. Объясняется это тем, что считывание из файла и последующая запись в файл одного «вещественного элемента» фактически приводит к считыванию и записи блока данных размером 8 байтов, содержащего два последовательных целочисленных элемента исходного файла.

Итак, мы выяснили, что ошибки, связанные с несоответствием типов файловых элементов, не выявляются при компиляции и не всегда приводят к ошибкам времени выполнения. Это следует иметь в виду, и при появлении «странных» результирующих данных начинать поиск ошибки с проверки того, соответствуют ли используемые методы ввода-вывода типу файловых элементов.

В нашем случае для исправления ошибки достаточно заменить метод ReadDouble на ReadInt32 (данный метод обеспечивает считывание из файла элемента целого типа). После запуска исправленной программы мы получим все еще неверное, но вполне «понятное» решение: первые три элемента результирующего файла будут совпадать с контрольными (то есть «правильными»), а прочие элементы будут отсутствовать.

При выполнении этого варианта программы следует обратить внимание на одну полезную возможность: при переключении между вкладками «Полученные результаты» и «Пример верного решения» (см. рисунок) указатель текущей позиции для результирующего файла не меняется. Это облегчает поиск несоответствий между полученными и контрольными файловыми элементами.

Приведем, наконец, верное решение задания File48:

Task("File48");
FileStream[] f = new FileStream[4];
for (int i = 0; i < 3; i++)
    f[i] = new FileStream(GetString(), FileMode.Open);
f[3] = new FileStream(GetString(), FileMode.Create);
BinaryReader[] r = new BinaryReader[3];
for (int i = 0; i < 3; i++)
    r[i] = new BinaryReader(f[i]);
BinaryWriter w = new BinaryWriter(f[3]);
while (f[0].Position < f[0].Length)
    for (int i = 0; i < 3; i++)
        w.Write(r[i].ReadInt32());
for (int i = 0; i < 3; i++)
    r[i].Close();
w.Close();

От предыдущего варианта данное решение отличается добавлением цикла while, условие которого будет истинным, пока в первом файле будут оставаться непрочитанные элементы. Таким образом, цикл while обеспечивает считывание всех элементов из исходных файлов (напомним, что по условию задания все исходные файлы имеют одинаковый размер) и запись их в результирующий файл в нужном порядке. В программе, кроме того, изменен фрагмент, связанный с закрытием файловых потоков: вместо вызова метода Close объектов FileStream выполняется вызов одноименных методов объектов BinaryReader и BinaryWriter. Это обеспечивает закрытие всех потоков, так как при вызове метода Close для потока-оболочки BinaryReader или BinaryWriter происходит автоматический вызов метода Close для связанного с ним базового файлового потока FileStream.

После запуска этого варианта мы получим сообщение «Верное решение. Тест номер 1 (из 5)», а после пяти запусков — сообщение «Задание выполнено!».

Заметим, что при решении задания File48 мы не пользовались специальной функцией Put, реализованной в электронном задачнике Programming Taskbook. Это объясняется тем, что ввод и вывод файловых элементов при решении любого учебного задания осуществляется с использованием стандартных методов библиотеки .NET Framework.

После выхода из среды Visual Studio можно убедиться, что в рабочем каталоге не осталось ни одного из тех файлов (как исходных, так и результирующих), которые создавались при выполнении задания. Таким образом, «засорения» диска ненужными в дальнейшем файлами не происходит.

Программу, решающую данное задание, можно сделать более краткой, если не использовать массив объектов FileStream, а создавать эти объекты «на лету», сразу передавая их в конструкторы объектов BinaryReader и BinaryWriter. Кроме того, для создания файловых потоков вместо конструкторов класса FileStream можно использовать методы класса File. Приведем соответствующий вариант решения.

Task("File48");
BinaryReader[] r = new BinaryReader[3];
for (int i = 0; i < 3; i++)
    r[i] = new BinaryReader(File.OpenRead(GetString()));
BinaryWriter w = new BinaryWriter(File.Create(GetString()));
while (r[0].BaseStream.Position < r[0].BaseStream.Length)
    for (int i = 0; i < 3; i++)
        w.Write(r[i].ReadInt32());
for (int i = 0; i < 3; i++)
    r[i].Close();
w.Close();

Обратите внимание на использование свойства BaseStream потока-оболочки BinaryReader, которое обеспечивает доступ к свойствам Position и Length файлового потока, связанного с данным потоком-оболочкой.

Преобразование файла: File25

File25. Дан файл вещественных чисел. Заменить в нем все элементы на их квадраты.

В задании File25, в отличие от рассмотренного ранее задания File48, требуется не создать новый файл, а преобразовать уже имеющийся.

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

Task("File25");
FileStream f = new FileStream(GetString(), FileMode.Open);
BinaryReader r = new BinaryReader(f);
BinaryWriter w = new BinaryWriter(f);
for (long i = 1; i <= f.Length / 8; i++)
{
    double a = r.ReadDouble();
    f.Position -= 8;
    w.Write(a * a);
}
r.Close();
w.Close();

В программе использован цикл for, число итераций которого равно количеству вещественных чисел, содержащихся в файле. Для нахождения этого количества свойство f.Length, равное размеру файла f в байтах, делится на 8, то есть на размер в байтах вещественного числа (типа double). Обратите внимание на то, что параметром цикла for в данном случае является длинное целое (long), поскольку такой тип имеет свойство f.Length.

Изменение данных непосредственно в исходном файле обязательно требует прямого доступа к элементам файла, поскольку после считывания каждого элемента файловый указатель автоматически перемещается к следующему элементу файла, и для того чтобы записать преобразованный элемент на прежнее место, приходится «возвращать» файловый указатель назад на один элемент. В данном случае элемент является вещественным числом, занимающим в файле 8 байтов, поэтому для возврата файлового указателя используется оператор f.Position -= 8. Отметим, что оператор вида f.Position = f.Length перемещает указатель за последний элемент файла (на маркер конца файла EOF); данный оператор используется при добавлении в конец файла новых элементов. Для перемещения файлового указателя можно также использовать метод Seek.

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

Task("File25");
FileStream f1 = File.OpenRead(GetString()),
    f2 = File.Create("$F25$.tmp");
BinaryReader r = new BinaryReader(f1);
BinaryWriter w = new BinaryWriter(f2);
for (long i = 1; i <= f.Length / 8; i++)
{
    double a = r.ReadDouble();
    w.Write(a * a);
}
r.Close();
w.Close();
File.Delete(f1.Name);
File.Move(f2.Name, f1.Name);

Данный вариант решения использует последовательный доступ к элементам файлов, причем для исходного файлового потока f1 — только для чтения, а для вспомогательного файлового потока f2 — только для записи. Имя вспомогательного файла следует подобрать таким образом, чтобы обеспечить его уникальность. В нем можно использовать символы # $ % & ( ), редко встречающиеся в именах обычных файлов, а также стандартное расширение .tmp для временных файлов.

Заметим, что чтение очередного элемента данных из исходного файла и запись квадрата этого элемента во вспомогательный файл можно было бы оформить в виде одного оператора, воспользовавшись для возведения элемента в квадрат функцией Pow класса Math:

w.Write(Math.Pow(r.ReadDouble(), 2));

Однако с точки зрения быстродействия предпочтительным является вариант, приведенный в программе, в котором используется обычное умножение.

После заполнения вспомогательного файла и закрытия обоих файлов исходный файл уничтожается, а имя вспомогательного файла заменяется на имя исходного (для выполнения этих действий используются методы Delete и Move класса File). В результате на диске остается один преобразованный файл, имя которого совпадает с именем исходного файла.

Заметим, что без предварительного уничтожения исходного файла вызов метода Move приведет к ошибке времени выполнения, так как при переименовании файла ему нельзя присваивать имя существующего файла. Обратите внимание на то, что имена файлов (свойства Name объектов типа FileStream) остаются доступными и после закрытия соответствующих файловых потоков.

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

Нетипизированные двоичные файлы: File43

File43. Дан файл произвольного типа. Создать его копию с новым именем.

Задание File43 относится к группе заданий на обработку нетипизированных двоичных файлов. В этих заданиях тип элементов исходных файлов не известен, и требуется выполнить действие над файлами «в целом»: скопировать содержимое файла в новый файл, добавить это содержимое в конец другого файла и т. д. В подобной ситуации файл следует рассматривать как последовательность байтов и осуществлять действия по вводу-выводу данных с помощью методов, предусмотренных в самом классе FileStream, не создавая вспомогательные потоки-оболочки. Заметим, что методы Read и Write класса FileStream позволяют за одну операцию ввода-вывода обрабатывать не один байт, а целый блок данных размером в сотни и тысячи байтов. Разумеется, при обработке нетипизированного двоичного файла сохраняется возможность прямого доступа к любому байту, содержащемуся в данном файле.

Приводимое ниже решение задания File43 является хорошей иллюстрацией особенностей работы с нетипизированными двоичными файлами:

Task("File43");
FileStream f1 = File.OpenRead(GetString()),
    f2 = File.Create(GetString());
byte[] b = new byte[1024];
while (f1.Position < f1.Length)
    f2.Write(b, 0, f1.Read(b, 0, b.Length));
f1.Close();
f2.Close();

Использованный в программе метод Read считывает из исходного файла 1024 байта (или менее, если в исходном файле недостаточно данных) и помещает их в элементы массива b, начиная с элемента с индексом 0. Возвращаемым значением метода Read является количество фактически считанных байтов. Метод Write записывает в результирующий файл байты, содержащиеся в массиве b, начиная с элемента с индексом 0. Количество записываемых байтов указывается в третьем параметре, в качестве которого мы поместили вызов метода Read. Таким образом, в одном операторе мы выполнили и ввод, и вывод блока данных размером 1 килобайт.

Необходимость копирования файлов возникает в программах настолько часто, что в классе File предусмотрен метод Copy, обеспечивающий подобное копирование. С использованием этого метода решение задания File43 можно записать в виде единственного оператора:

Task("File43");
File.Copy(GetString(), GetString());

Итак, при копировании файла можно обойтись без применения методов Read и Write (и даже без использования файловых потоков). Однако данные методы оказываются полезными во многих других ситуациях, например, при объединении файлов (см. задания File46 и File47).

Символьные и строковые файлы: File63

Символьными файлами называют двоичные файлы, содержащие данные типа char.

Строковыми файлами называют двоичные файлы, содержащие строковые данные. Если длины всех строк, содержащихся в строковом файле, одинаковы, то при обработке файла можно использовать прямой доступ к его элементам. Кроме того, строковые файлы можно открыть одновременно на чтение и на запись. Эти возможности отличают строковые файлы от текстовых (см. модуль № 5), при обработке которых возможен только последовательный доступ, причем либо только на чтение, либо только на запись. К числу недостатков строковых файлов следует отнести невозможность их просмотра и редактирования в обычных текстовых редакторах, поскольку при записи строк в двоичные файлы используется специальный формат (по поводу этого формата см. описание метода ReadString в справочных сведениях).

При выполнении заданий на строковые файлы с использованием электронного задачника Programming Taskbook следует предполагать, что все строки, содержащиеся в строковых файлах, имеют одинаковую длину, равную 80 символам (строки меньшей длины дополняются справа пробелами). Если для обработки строк, прочитанных из строкового файла, необходимо удалить завершающие пробелы, то это проще всего сделать с помощью метода TrimEnd класса string. Перед записью строки в двоичный файл следует дополнить ее пробелами до 80 символов; это проще всего сделать с помощью метода PadRight класса string. Подробное описание методов TrimEnd и PadRight см. в справочных сведениях.

Подчеркнем, что условие на длину элементов строковых файлов накладывается задачником Programming Taskbook; в обычных программах, не связанных с выполнением учебных заданий, в строковые файлы можно записывать строки любой длины.

При обработке как символьных, так и строковых файлов (а также текстовых файлов, которые рассматриваются в следующих пунктах) следует учитывать, что данные в них хранятся в закодированном виде, и для их правильной обработки необходимо при считывании данных использовать тот формат кодирования, который использовался при их записи в файл. Различные форматы кодирования и их особенности описаны в справочных сведениях.

При выполнении заданий с использованием задачника Programming Taskbook следует указывать формат кодирования Encoding.Default, используемый по умолчанию операционной системой Windows. Впрочем, если исходные и результирующие файлы входящие в учебное задание, содержат только символы ASCII, то есть символы с кодами от 1 до 127, в явном указании данного формата нет необходимости (примерами таких заданий являются File67–File73). Поскольку класс Encoding определен в пространстве имен System.Text, при использовании в программе класса Encoding без предваряющего имени пространства System.Text в начале программы следует указать оператор

using System.Text;

В качестве примера задания на обработку строковых файлов рассмотрим задание File63.

File63. Дано целое число K (> 0) и строковый файл. Создать два новых файла: строковый, содержащий первые K символов каждой строки исходного файла, и символьный, содержащий K-й символ каждой строки (если длина строки меньше K, то в строковый файл записывается вся строка, а в символьный файл записывается пробел).

Task("File63");
int k = GetInt();
BinaryReader
    r = new BinaryReader(File.OpenRead(GetString()), Encoding.Default);
BinaryWriter
    w1 = new BinaryWriter(File.Create(GetString()), Encoding.Default),
    w2 = new BinaryWriter(File.Create(GetString()), Encoding.Default);
while (r.BaseStream.Position < r.BaseStream.Length)
{
    string s = r.ReadString();
    char c = ' ';
    if (k <= s.TrimEnd().Length)
    {
        s = s.Substring(0, k).PadRight(80);
        c = s[k - 1];
    }
    w1.Write(s);
    w2.Write(c);
}
r.Close();
w1.Close();
w2.Close();

Обратите внимание на второй параметр (Encoding.Default), использованный в конструкторах классов BinaryReader и BinaryWriter, а также на дополнение подстроки, полученной из строки s, завершающими пробелами до 80 символов с помощью метода PadRight (если этого не сделать, то при выполнении задания будет выведено сообщение об ошибке «Длина строки в строковом файле не равна 80»).


Модуль 4. Двоичные файлы

Учебные задания и указания к ним

Двоичные файлы

Условие вида «дан файл (целых чисел, вещественных чисел и т. д.)» означает, что в наборе исходных данных указано имя данного файла (текстовая строка), а сам исходный файл существует и находится в текущем каталоге. Если существование исходных файлов требуется проверять в ходе выполнения задания, то это особо оговаривается в формулировке задания. Элементы исходных файлов следует считывать с помощью стандартных потоков ввода.

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

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

Если о минимальном размере исходного файла в задании ничего не сказано, то предполагается, что он равен 2 (то есть файл содержит по крайней мере два элемента). Максимальный размер исходных файлов не устанавливается, поэтому при решении заданий не следует использовать вспомогательные массивы, содержащие все элементы исходных файлов, однако допускается использование вспомогательных файлов.

Создание файла, ввод и вывод его элементов

File1. Дана строка S. Если S является допустимым именем файла, то создать пустой файл с этим именем и вывести true. Если файл с именем S создать нельзя, то вывести false.

File2. Дано имя файла и целое число N (> 1). Создать файл целых чисел с данным именем и записать в него N первых положительных четных чисел (2, 4, …).

File3. Дано имя файла и вещественные числа A и D. Создать файл вещественных чисел с данным именем и записать в него 10 первых членов арифметической прогрессии с начальным членом A и разностью D:

A, A + D, A + 2·D, A + 3·D, … .

File4. Даны имена четырех файлов. Найти количество файлов с указанными именами, которые имеются в текущем каталоге.

File5. Дано имя файла целых чисел. Найти количество элементов, содержащихся в данном файле. Если файла с таким именем не существует, то вывести –1.

File6. Дано целое число K и файл, содержащий неотрицательные целые числа. Вывести K-й элемент файла (элементы нумеруются от 1). Если такой элемент отсутствует, то вывести –1.

File7. Дан файл целых чисел, содержащий не менее четырех элементов. Вывести первый, второй, предпоследний и последний элементы данного файла.

File8. Даны имена двух файлов вещественных чисел. Известно, что первый из них существует и является непустым, а второй в текущем каталоге отсутствует. Создать отсутствующий файл и записать в него начальный и конечный элементы существующего файла (в указанном порядке).

File9. Даны имена двух файлов вещественных чисел. Известно, что один из них (не обязательно первый) существует и является непустым, а другой в текущем каталоге отсутствует. Создать отсутствующий файл и записать в него конечный и начальный элементы существующего файла (в указанном порядке).

File10. Дан файл целых чисел. Создать новый файл, содержащий те же элементы, что и исходный файл, но в обратном порядке.

File11. Дан файл вещественных чисел. Создать два новых файла, первый из которых содержит элементы исходного файла с нечетными номерами (1, 3, …), а второй — с четными (2, 4, …).

File12. Дан файл целых чисел. Создать два новых файла, первый из которых содержит четные числа из исходного файла, а второй — нечетные (в том же порядке). Если четные или нечетные числа в исходном файле отсутствуют, то соответствующий результирующий файл оставить пустым.

File13. Дан файл целых чисел. Создать два новых файла, первый из которых содержит положительные числа из исходного файла (в обратном порядке), а второй — отрицательные (также в обратном порядке). Если положительные или отрицательные числа в исходном файле отсутствуют, то соответствующий результирующий файл оставить пустым.

File14. Дан файл вещественных чисел. Найти среднее арифметическое его элементов.

File15. Дан файл вещественных чисел. Найти сумму его элементов с четными номерами.

File16. Дан файл целых чисел. Найти количество содержащихся в нем серий (то есть наборов последовательно расположенных одинаковых элементов). Например, для файла с элементами 1, 5, 5, 5, 4, 4, 5 результат равен 4.

File17. Дан файл целых чисел. Создать новый файл целых чисел, содержащий длины всех серий исходного файла (серией называется набор последовательно расположенных одинаковых элементов, а длиной серии — количество этих элементов). Например, для исходного файла с элементами 1, 5, 5, 5, 4, 4, 5 содержимое результирующего файла должно быть следующим: 1, 3, 2, 1.

File18. Дан файл вещественных чисел. Найти его первый локальный минимум (локальным минимумом называется элемент, который меньше своих соседей).

File19. Дан файл вещественных чисел. Найти его последний локальный максимум (локальным максимумом называется элемент, который больше своих соседей).

File20. Дан файл вещественных чисел. Найти общее количество его локальных экстремумов, то есть локальных минимумов и локальных максимумов (определения локального минимума и локального максимума даны в заданиях File18 и File19).

File21. Дан файл вещественных чисел. Создать файл целых чисел, содержащий номера всех локальных максимумов исходного файла в порядке возрастания (определение локального максимума дано в задании File19).

File22. Дан файл вещественных чисел. Создать файл целых чисел, содержащий номера всех локальных экстремумов исходного файла в порядке убывания (определение локального экстремума дано в задании File20).

File23. Дан файл вещественных чисел. Создать файл целых чисел, содержащий длины всех убывающих последовательностей элементов исходного файла. Например, для исходного файла с элементами 1.7, 4.5, 3.4, 2.2, 8.5, 1.2 содержимое результирующего файла должно быть следующим: 3, 2.

File24. Дан файл вещественных чисел. Создать файл целых чисел, содержащий длины всех монотонных последовательностей элементов исходного файла. Например, для исходного файла с элементами 1.7, 4.5, 3.4, 2.2, 8.5, 1.2 содержимое результирующего файла должно быть следующим: 2, 3, 2, 2.

Преобразование файла

File25. Дан файл вещественных чисел. Заменить в нем все элементы на их квадраты.

File26. Дан файл вещественных чисел. Поменять в нем местами минимальный и максимальный элементы.

File27. Дан файл целых чисел с элементами A1, A2, …, AN (N — количество элементов в файле). Заменить исходное расположение его элементов на следующее:

A1, AN, A2, AN–1, A3, … .

File28. Дан файл вещественных чисел. Заменить в файле каждый элемент, кроме начального и конечного, на его среднее арифметическое с предыдущим и последующим элементом.

File29. Дан файл целых чисел, содержащий более 50 элементов. Уменьшить его размер до 50 элементов, удалив из файла необходимое количество конечных элементов.

File30. Дан файл целых чисел, содержащий четное количество элементов. Удалить из данного файла вторую половину элементов.

File31. Дан файл целых чисел, содержащий более 50 элементов. Уменьшить его размер до 50 элементов, удалив из файла необходимое количество начальных элементов.

File32. Дан файл целых чисел, содержащий четное количество элементов. Удалить из данного файла первую половину элементов.

File33. Дан файл целых чисел. Удалить из него все элементы с четными номерами.

File34. Дан файл целых чисел. Удалить из него все отрицательные числа.

File35. Дан файл целых чисел, содержащий менее 50 элементов. Увеличить его размер до 50 элементов, записав в начало файла необходимое количество нулей.

File36. Дан файл целых чисел. Удвоить его размер, записав в конец файла все его исходные элементы (в том же порядке).

File37. Дан файл целых чисел. Удвоить его размер, записав в конец файла все его исходные элементы (в обратном порядке).

File38. Дан файл целых чисел. Продублировать в нем все элементы с нечетными номерами.

File39. Дан файл целых чисел. Продублировать в нем все числа, принадлежащие диапазону 5–10.

File40. Дан файл целых чисел. Заменить в нем каждый элемент с четным номером на два нуля.

File41. Дан файл целых чисел. Заменить в нем каждое положительное число на три нуля.

Обработка нетипизированных двоичных файлов

File42. Даны два файла произвольного типа. Поменять местами их содержимое.

File43. Дан файл произвольного типа. Создать его копию с новым именем.

File44. Даны три файла одного и того же типа, но разного размера. Заменить содержимое самого длинного файла на содержимое самого короткого.

File45. Даны три файла одного и того же типа, но разного размера. Заменить содержимое самого короткого файла на содержимое самого длинного.

File46. Дана строка S0, целое число N (£ 4) и N файлов одного и того же типа с именами S1, …, SN. Объединить содержимое этих файлов (в указанном порядке) в новом файле с именем S0.

File47. Даны два файла одного и того же типа. Добавить к первому файлу содержимое второго файла, а ко второму файлу — содержимое первого.

Работа с несколькими числовыми файлами. Файлы-архивы

File48. Даны три файла целых чисел одинакового размера с именами SA, SB, SC и строка SD. Создать новый файл с именем SD, в котором чередовались бы элементы исходных файлов с одним и тем же номером:

A1, B1, C1, A2, B2, C2, … .

File49. Даны четыре файла целых чисел разного размера с именами SA, SB, SC, SD и строка SE. Создать новый файл с именем SE, в котором чередовались бы элементы исходных файлов с одним и тем же номером (как в задании File48). «Лишние» элементы более длинных файлов в результирующий файл не записывать.

File50. Даны два файла вещественных чисел с именами S1 и S2, элементы которых упорядочены по возрастанию. Объединить эти файлы в новый файл с именем S3 так, чтобы его элементы также оказались упорядоченными по возрастанию.

File51. Даны три файла вещественных чисел с именами S1, S2 и S3, элементы которых упорядочены по убыванию. Объединить эти файлы в новый файл с именем S4 так, чтобы его элементы также оказались упорядоченными по убыванию.

File52. Дана строка S0, целое число N (£ 4) и N файлов целых чисел с именами S1, …, SN. Объединить их содержимое в новом файле-архиве с именем S0, используя следующий формат: в первом элементе файла-архива хранится число N, в следующих N элементах хранится размер (число элементов) каждого из исходных файлов, а затем последовательно размещаются данные из каждого исходного файла.

File53. Дана строка S, целое число N (> 0) и файл-архив целых чисел, содержащий данные из нескольких файлов в формате, описанном в задании File52. Восстановить из файла-архива файл с номером N и сохранить его под именем S. Если файл-архив содержит данные из менее чем N файлов, то оставить результирующий файл пустым.

File54. Дана строка S и файл-архив целых чисел, содержащий данные из нескольких (не более шести) файлов в формате, описанном в задании File52. Для каждого из файлов, содержащихся в архиве, найти среднее арифметическое всех его элементов (вещественное число) и записать найденные числа (в том же порядке) в файл вещественных чисел с именем S.

File55. Дана строка S0, целое число N (£ 4) и N файлов целых чисел с именами S1, …, SN. Объединить их содержимое в новом файле-архиве с именем S0, последовательно записывая в него следующие данные: размер (число элементов) первого исходного файла и все элементы этого файла, размер второго исходного файла и все его элементы, …, размер N-го исходного файла и все его элементы.

File56. Дана строка S, целое число N (> 0) и файл-архив целых чисел, содержащий данные из нескольких файлов в формате, описанном в задании File55. Восстановить из файла-архива файл с номером N и сохранить его под именем S. Если файл-архив содержит данные из менее чем N файлов, то оставить результирующий файл пустым.

File57. Даны строки S1, S2 и файл-архив целых чисел, содержащий данные из нескольких файлов в формате, описанном в задании File55. Создать новые файлы целых чисел с именами S1 и S2 и записать в первый из них начальные элементы всех файлов, содержащихся в архиве, а во второй — конечные элементы этих файлов (в том же порядке).

Cимвольные и строковые файлы

Символьным файлом называется двоичный файл, элементами которого являются отдельные символы. Строковым файлом называется двоичный файл, элементами которого являются текстовые строки.

File58. Дан символьный файл, содержащий по крайней мере один символ пробела. Удалить все его элементы, расположенные после первого символа пробела, включая и этот пробел.

File59. Дан символьный файл, содержащий по крайней мере один символ пробела. Удалить все его элементы, расположенные после последнего символа пробела, включая и этот пробел.

File60. Дан символьный файл, содержащий по крайней мере один символ пробела. Удалить все его элементы, расположенные перед первым символом пробела, включая и этот пробел.

File61. Дан символьный файл, содержащий по крайней мере один символ пробела. Удалить все его элементы, расположенные перед последним символом пробела, включая и этот пробел.

File62. Дан символьный файл. Упорядочить его элементы по возрастанию их кодов.

File63. Дано целое число K (> 0) и строковый файл. Создать два новых файла: строковый, содержащий первые K символов каждой строки исходного файла, и символьный, содержащий K-й символ каждой строки (если длина строки меньше K, то в строковый файл записывается вся строка, а в символьный файл записывается пробел).

File64. Дан строковый файл. Создать новый строковый файл, содержащий все строки исходного файла наименьшей длины (в том же порядке).

File65. Дан строковый файл. Создать новый строковый файл, содержащий все строки исходного файла наибольшей длины (в обратном порядке).

File66. Дан строковый файл. Создать новый строковый файл, в котором строки из исходного файла располагались бы в лексикографическом порядке, то есть по возрастанию кодов их символов, начиная с первого символа.

File67. Дан строковый файл, содержащий даты в формате «день/месяц/год», причем под день и месяц отводится по две позиции, а под год — четыре (например, «16/04/2001»). Создать два файла целых чисел, первый из которых содержит значения дней, а второй — значения месяцев для дат из исходного строкового файла (в том же порядке).

File68. Дан строковый файл, содержащий даты в формате, описанном в задании File67. Создать два файла целых чисел, первый из которых содержит значения месяцев, а второй — значения лет для дат из исходного строкового файла (в обратном порядке).

File69. Дан строковый файл, содержащий даты в формате, описанном в задании File67. Создать новый строковый файл, содержащий все летние даты из исходного файла (в том же порядке). Если даты с требуемым временем года в файле отсутствуют, то оставить результирующий файл пустым.

File70. Дан строковый файл, содержащий даты в формате, описанном в задании File67. Создать новый строковый файл, содержащий все зимние даты из исходного файла (в обратном порядке). Если даты с требуемым временем года в файле отсутствуют, то оставить результирующий файл пустым.

File71. Дан строковый файл, содержащий даты в формате, описанном в задании File67. Найти строку, содержащую самую раннюю весеннюю дату. Если даты с требуемым временем года в файле отсутствуют, то вывести пустую строку.

File72. Дан строковый файл, содержащий даты в формате, описанном в задании File67. Найти строку, содержащую самую позднюю осеннюю дату. Если даты с требуемым временем года в файле отсутствуют, то вывести пустую строку.

File73. Дан строковый файл, содержащий даты в формате, описанном в задании File67. Создать новый строковый файл, в котором даты из исходного файла располагались бы в порядке убывания.

Использование файлов для работы с матрицами

Матрицей размера M ´ N называется прямоугольная таблица чисел, содержащая M строк и N столбцов. Для работы с матрицами обычно используются двумерные массивы (см. задания группы Matrix). Данный пункт посвящен способам обработки матриц, хранящихся в двоичных файлах на внешних носителях (дисках). Как и в остальных заданиях на обработку файловых данных, при выполнении заданий из данного пункта не следует использовать вспомогательные массивы, содержащие все файловые элементы.

В заданиях данного пункта используются дополнительные понятия теории матриц. Приведем определения этих понятий.

Пусть A — матрица размера M ´ N. Матрица B называется транспонированной к матрице A, если она имеет размер N ´ M и ее элементы удовлетворяют следующему соотношению:

BI,J = AJ,I, I = 1, …, N, J = 1, …, M.

Пусть A — матрица размера M ´ N, B — матрица размера N ´ P. Матрица C называется произведением матриц A и B (и обозначается A·B), если она имеет размер M ´ P и ее элементы удовлетворяют следующему соотношению:

CI,J = AI,1·B1,J + AI,2·B2,J + … + A1,N·BN,J, I = 1, …, M, J = 1, …, P.

Квадратная матрица A называется верхнетреугольной, если все ее элементы, лежащие ниже главной диагонали, равны нулю (определение главной диагонали см. в задании Matrix80):

AI,J = 0, I > J.

Квадратная матрица A называется нижнетреугольной, если все ее элементы, лежащие выше главной диагонали, равны нулю:

AI,J = 0, I < J.

Квадратная матрица A называется трехдиагональной, если равны нулю все ее элементы, не лежащие на главной диагонали и на двух диагоналях, примыкающих к главной:

AI,J = 0, |I – J| > 1.

File74. Даны два целых числа I, J и файл вещественных чисел, содержащий элементы квадратной матрицы (по строкам). Вывести элемент матрицы, расположенный в I-й строке и J-м столбце (строки и столбцы нумеруются от 1). Если требуемый элемент отсутствует, то вывести 0.

File75. Дан файл вещественных чисел, содержащий элементы квадратной матрицы (по строкам). Создать новый файл, содержащий элементы матрицы, транспонированной к исходной.

File76. Даны два файла вещественных чисел с именами SA и SB, содержащие элементы квадратных матриц A и B (по строкам). Создать новый файл с именем SC, содержащий элементы произведения A·B. Если матрицы A и B нельзя перемножать, то оставить файл SC пустым.

File77. Даны два целых числа I, J и файл вещественных чисел, содержащий элементы прямоугольной матрицы (по строкам), причем первый элемент файла содержит количество столбцов матрицы. Вывести элемент матрицы, расположенный в I-й строке и J-м столбце (строки и столбцы нумеруются от 1). Если требуемый элемент отсутствует, то вывести 0.

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

File79. Даны два файла вещественных чисел с именами SA и SB, содержащие элементы прямоугольных матриц A и B (по строкам), причем первый элемент каждого файла содержит количество столбцов соответствующей матрицы. Создать файл той же структуры с именем SC, содержащий элементы произведения A·B. Если матрицы A и B нельзя перемножать, то оставить файл SC пустым.

File80. Дан файл вещественных чисел, содержащий элементы верхнетреугольной матрицы (по строкам). Создать новый файл, содержащий элементы ненулевой части данной матрицы (по строкам).

File81. Дан файл вещественных чисел, содержащий элементы нижнетреугольной матрицы (по строкам). Создать новый файл, содержащий элементы ненулевой части данной матрицы (по строкам).

File82. Дан файл вещественных чисел, содержащий элементы трехдиагональной матрицы (по строкам). Создать новый файл, содержащий элементы ненулевой части данной матрицы (по строкам).

File83. Даны два целых числа I, J и файл вещественных чисел, содержащий ненулевую часть верхнетреугольной матрицы (по строкам). Вывести порядок матрицы и ее элемент, расположенный в I-й строке и J-м столбце (строки и столбцы нумеруются от 1). Если требуемый элемент находится в нулевой части матрицы, то вывести 0; если элемент отсутствует, то вывести –1.

File84. Даны два целых числа I, J и файл вещественных чисел, содержащий ненулевую часть нижнетреугольной матрицы (по строкам). Вывести порядок матрицы и ее элемент, расположенный в I-й строке и J-м столбце (строки и столбцы нумеруются от 1). Если требуемый элемент находится в нулевой части матрицы, то вывести 0; если элемент отсутствует, то вывести –1.

File85. Даны два целых числа I, J и файл вещественных чисел, содержащий ненулевую часть трехдиагональной матрицы (по строкам). Вывести порядок матрицы и ее элемент, расположенный в I-й строке и J-м столбце (строки и столбцы нумеруются от 1). Если требуемый элемент находится в нулевой части матрицы, то вывести 0; если элемент отсутствует, то вывести –1.

File86. Дан файл вещественных чисел, содержащий ненулевую часть верхнетреугольной матрицы (по строкам). Создать новый файл, содержащий все элементы данной матрицы (по строкам).

File87. Дан файл вещественных чисел, содержащий ненулевую часть нижнетреугольной матрицы (по строкам). Создать новый файл, содержащий все элементы данной матрицы (по строкам).

File88. Дан файл вещественных чисел, содержащий ненулевую часть трехдиагональной матрицы (по строкам). Создать новый файл, содержащий все элементы данной матрицы (по строкам).

File89. Даны два файла вещественных чисел с именами SA и SB, содержащие ненулевые части верхнетреугольных матриц A и B (по строкам). Создать новый файл с именем SC, содержащий ненулевую часть произведения A·B (по строкам). Если матрицы A и B нельзя перемножать, то оставить файл SC пустым.

File90. Даны два файла вещественных чисел с именами SA и SB, содержащие ненулевые части нижнетреугольных матриц A и B (по строкам). Создать новый файл с именем SC, содержащий ненулевую часть произведения A·B (по строкам). Если матрицы A и B нельзя перемножать, то оставить файл SC пустым.

Указания

File1. Проще всего «переложить» проверку допустимости имени файла на операционную систему, попытавшись открыть файл с этим именем в режиме создания (для этого можно либо вызвать конструктор класса FileStream со вторым параметром FileMode.Create, либо использовать метод Create класса File). Для того чтобы в случае недопустимого имени не произошло аварийного завершения программы, необходимо воспользоваться механизмом явной обработки исключений, выполнив действия по открытию файла в try-блоке, как в приведенном ниже фрагменте программы (в этом фрагменте предполагается, что имя файла содержится в переменной s):

    try
    {
        FileStream f = File.Create(s);
        действия при успешном создании
        f.Close();
    }
    catch
    {
        действия в случае ошибки
    }

Дополнительные сведения об использовании блока try-catch приводятся в пункте, посвященном решению задания String19. Обратите внимание на то, что закрывать файл надо только при его успешном создании.

File4. Для того чтобы проверить, существует ли файл с указанным именем, достаточно воспользоваться методом Exists класса File.

File5. По поводу проверки существования файла см. указание к File4. Для нахождения количества элементов файла следует поделить размер файла в байтах на размер элемента (4 байта в случае целочисленных элементов и 8 байтов в случае вещественных элементов). Для определения размера файла в байтах используйте свойство Length класса FileStream.

File6–7. В File6 для проверки существования элемента с требуемым номером используйте свойство Length (см. указание к File5). Для перемещения к существующему элементу используйте свойство Position или метод Seek. Для чтения из файла элемента целого типа следует создать поток-оболочку BinaryReader и воспользоваться его методом ReadInt32.

File8. По поводу чтения элементов файла с требуемыми номерами см. указание к File6–7. Для записи элементов в новый файл следует создать поток-оболочку BinaryWriter и воспользоваться его методом Write.

File9. По поводу проверки существования файла см. указание к File4. Если связать существующий файл (независимо от его порядкового номера) с файловым потоком f1, а отсутствующий файл — с файловым потоком f2, то оставшаяся часть решения будет очень похожа на решение File8, в котором поток f1 связан с первым файлом, а поток f2 — со вторым.

File10. Считывайте элементы исходного файла в обратном порядке (используя метод Seek с параметром SeekOrigin.End или свойства Length и Position); это позволит заполнять результирующий файл последовательно, не выполняя действий по явному перемещению его файлового указателя.

File11. Удобно считывать по два элемента исходного файла, записывая по одному элементу в каждый из результирующих файлов. Повторять эти действия требуется N/2 раз, где N — количество элементов исходного файла, а символ / обозначает операцию целочисленного деления. В случае, когда N — нечетное, следует отдельно обработать последний элемент исходного файла.

File13. См. указание к File10.

File15. См. указание к File11.

File16–17. Ср. с Array116. Поскольку в данном случае обрабатываются элементы не массива, а файла (и поэтому нельзя обеспечить одновременный доступ к элементам), прочитанный файловый элемент необходимо сохранять во вспомогательной переменной, которую в дальнейшем использовать при анализе следующего элемента.

File18–22. Организуйте последовательное считывание элементов файла с сохранением во вспомогательных переменных трех последних прочитанных элементов (при этом средний из этих элементов можно будет проверить на локальный минимум/максимум/экстремум). При нахождении локальных минимумов/максимумов (File18–19 и File21) требуется также предусмотреть особую обработку первого и последнего элемента, которые имеют по одному соседу. При нахождении всех локальных экстремумов (File20 и File22) данные элементы анализировать не требуется, так как они обязательно являются локальными экстремумами (см. указание к Array36). Для File22 воспользуйтесь также указанием к File10.

File23–24. См. указание к Array37–39 и указание к File18–22.

File25. См. решение этого задания.

File26. Ср. с Array68.

File27. Порядок считывания элементов исходного файла задавайте с помощью метода Seek или свойства Position; прочитанные элементы записывайте в конец вспомогательного файла (см. второй вариант решения File25).

File28. Последовательно считывайте элементы исходного файла с сохранением во вспомогательных переменных трех последних прочитанных элементов, определяйте по этим трем элементам новое значение среднего из них и записывайте его во вспомогательный файл (см. второй вариант решения File25). Заметим, что задание можно решить и без вспомогательного файла, ограничившись несколькими вспомогательными переменными (ср. с Array78) и осуществляя прямой доступ к файловым элементам с помощью метода Seek или свойства Position.

File29–30. Для удаления из двоичного файла нескольких последних элементов достаточно изменить его размер, вызвав метод SetLength класса FileStream (в качестве параметра метода SetLength надо указать новый размер файла в байтах).

File31–41. Воспользуйтесь вспомогательным файлом (как во втором варианте решения File25). Задания File36–37 несложно решить и без вспомогательного файла, используя прямой доступ к файловым элементам с помощью метода Seek или свойства Position.

File42. Открывать файлы для выполнения задания не требуется. Достаточно трижды выполнить переименование файлов с помощью метода Move класса File, используя при переименовании вспомогательное имя файла (подобно тому, как при обмене содержимого двух переменных используется третья, вспомогательная переменная).

File43–45. См. решение File43. Задания File44–45 решаются аналогично; для определения размера файлов в этих заданиях используйте свойство Length.

File46–47. Используйте методы Read и Write класса FileStream (как в первом варианте решения File43). В File46 достаточно использовать две переменные типа FileStream: одна связывается с создаваемым файлом результатов, а вторая (в цикле) последовательно связывается с каждым из исходных файлов для копирования данных из этого исходного файла в конец файла результатов. В File47 удобно перед дополнением одного из файлов создать его копию (см. решение File43), которую использовать в дальнейшем при дополнении другого файла.

File48–49. См. решение File48, задание File49 решается аналогично. При решении File49 удобно перед заполнением результирующего файла определить его размер (равный размеру самого короткого из исходных файлов — см. решение Minmax1), после чего заполнить файл в цикле for.

File50–51. Воспользуйтесь алгоритмом слияния, описанным в указании к заданиям Array63–64. В данном случае можно обойтись без специальной переменной для хранения информации о текущем элементе файла, так как текущий элемент определяется позицией файлового указателя. Далее, барьер в конце файла можно реализовать двумя способами: либо просто дополнить каждый из исходных файлов барьерным элементом, либо перед чтением элемента из файла в переменную проверять, не достигнут ли конец этого файла, и при достижении конца файла записывать в эту переменную барьерное значение. Второй способ предпочтительнее, так как он не требует дополнительных действий, связанных с добавлением в файл нового элемента.

File58–61. В File58–59 воспользуйтесь методом SetLength (как в File29–30), в File60–61 воспользуйтесь вспомогательным файлом (как во втором варианте решения File25). При поиске последнего пробела в исходном файле (в File59 и File61) удобно просматривать элементы файла в обратном порядке, используя метод Seek с параметром SeekOrigin.End или свойства Length и Position.

File62. Благодаря возможности прямого доступа к элементам двоичных файлов, для их сортировки можно использовать любой из алгоритмов, описанных в Array112–114 (модифицировав алгоритм с учетом того, что обрабатываются элементы не массива, а файла). Поскольку перемена местами двух элементов файла требует нескольких файловых операций, в данном случае целесообразно воспользоваться алгоритмом сортировки простым выбором (см. Array113), в котором количество перестановок элементов не превосходит N – 1, где N — количество элементов. Впрочем, все простейшие алгоритмы сортировки, описанные в Array112–114, весьма неэффективны, особенно для файлов большого размера. Для сортировки файловых данных (в предположении, что размер файла может оказаться настолько велик, что не позволит разместить все данные в оперативной памяти и тем самым свести задачу к сортировке массива) разработаны специальные методы. Эти методы файловой, или дисковой, сортировки описаны, например, в [1, 3, 4].

File63. См. решение этого задания.

File64–65. Простейший способ решения состоит в двукратном просмотре исходного файла: при первом просмотре определяется минимальная (максимальная) длина файловой строки (см. Minmax1), при втором все строки данной длины записываются в результирующий файл. Можно, однако, реализовать и однопроходный алгоритм, совмещая нахождение строк минимальной (максимальной) длины с их записью в результирующий файл; в этом случае при обнаружении строки, длина которой меньше (или, соответственно, больше) длины ранее прочитанных строк, следует очищать результирующий файл, вызывая для него метод SetLength с параметром 0, и начинать его заполнение заново. Напомним, что перед анализом очередной прочитанной из файла строки необходимо удалять из нее завершающие пробелы, а при записи строки в файл следует дополнять ее пробелами до 80 символов (см. решение File63). Для File65 см. также указание к File10.

File66. См. указание к File62.

File67–70. Для выделения из элементов-строк требуемых подстрок используйте метод Substring (или операцию индексирования — см. указание к String7), для преобразования этих подстрок в целые числа используйте метод Parse для целочисленного типа (см. решение String19). В File69–70 можно обойтись без метода Parse, непосредственно анализируя символы, определяющие месяц (для File69 достаточно проверить, что второй из этих символов лежит в диапазоне «6»–«8», для File70 придется использовать более сложное условие отбора). Для File68 и File70 см. также указание к File10.

File71–72. В этих заданиях приходится сравнивать даты между собой. Чтобы избежать при этом сложных логических выражений, удобно предварительно преобразовать сравниваемые даты к «американскому» формату «год/месяц/день», оформив такое преобразование в виде вспомогательной функции (даты, преобразованные к американскому формату, можно сравнивать как обычные строки). Особенности алгоритмов нахождения условного минимума/максимума описаны в указании к Minmax12–15. По поводу отбора дат, соответствующих определенному времени года, см. указание к File67–70.

File73. См. указание к File62 и указание к File71–72.

File74–76. Поскольку количество элементов квадратной матрицы порядка M равно M2, порядок исходной матрицы равен квадратному корню из числа элементов файла, содержащего данную матрицу (для определения количества элементов в файловом потоке надо поделить его свойство Length на 8, то есть на размер типа double в байтах). При этом следует учитывать, что функция извлечения квадратного корня Math.Sqrt возвращает значение вещественного типа, поэтому полученное значение квадратного корня перед записью в переменную целого типа надо преобразовать к целому типу (используя оператор преобразования типа (int)).

File77–79. При определении количества столбцов матрицы необходимо учитывать, что оно хранится в первом элементе файла в виде вещественного числа, поэтому его надо прочесть методом ReadDouble, после чего преобразовать прочитанное значение в целое число и сохранить в переменной целого типа. Зная количество элементов матрицы (оно равно K – 1, где K — количество элементов соответствующего файла) и количество столбцов, можно определить и количество строк матрицы. По поводу определения количества элементов в файле и преобразования вещественного числа к целому типу см. указание к File74–76.

File80–90. Если файл содержит все элементы квадратной матрицы, то порядок матрицы определяется так же, как в File74–76. Если в файле содержится только ненулевая часть матрицы, то для определения порядка матрицы следует учесть, что количество элементов в ненулевой части треугольной матрицы порядка M равно M(M + 1)/2, а количество элементов в ненулевой части трехдиагональной матрицы порядка M равно 3M – 2.


Модуль 4. Двоичные файлы

Справочные сведения

Перечисления, связанные с обработкой файлов

Все типы-перечисления, описанные в данном разделе, определены в пространстве имен System.IO. После указания имени, входящего в перечислимый тип, в скобках указывается связанное с ним числовое значение.

FileMode

Перечисление FileMode определяет режим открытия файла:

  • CreateNew (1) — создать новый файл (если файл уже существует, то возбуждается исключение IOException);
  • Create (2) — создать новый файл; если файл уже существует, то его содержимое очищается, после чего он открывается;
  • Open (3) — открыть существующий файл (если файл не существует, то возбуждается исключение FileNotFoundException);
  • OpenOrCreate (4) — открыть существующий файл; если файл не существует, то он создается;
  • Truncate (5) — очистить содержимое существующего файла, после чего открыть его (если файл не существует, то возбуждается исключение FileNotFoundException);
  • Append (6) — открыть существующий файл на запись и переместиться в его конец; если файл не существует, то он создается.

FileAccess

Перечисление FileAccess определяет способ доступа к файлу:

  • Read (1) — доступ для чтения;
  • Write (2) — доступ для записи;
  • ReadWrite (3) — доступ для чтения и записи.

SeekOrigin

Перечисление SeekOrigin определяет позицию, от которой отсчитывается смещение файлового указателя при выполнении метода Seek:

  • Begin (0) — смещение определяется относительно начала файла (допускаются только неотрицательные смещения);
  • Current (1) — смещение определяется относительно текущей позиции файлового указателя;
  • End (2) — смещение определяется относительно конца файла (допускаются только неположительные смещения).

Двоичный файловый поток: класс FileStream

Класс FileStream (файловый поток) определен в пространстве имен System.IO. Он обеспечивает базовые возможности для работы с файлами (открытие, определение и изменение размера файла, позиционирование файлового указателя, чтение/запись байтов и массивов байтов, закрытие). В данном пункте приведены лишь наиболее важные из свойств, конструкторов и методов класса FileStream. В частности, не рассматриваются возможности, связанные с файловыми дескрипторами, с совместным доступом к файлу и с асинхронным вводом-выводом.

Свойства

string Name { get; }

Возвращает полное имя файла, связанного с файловым потоком this.

bool CanRead { get; }

Возвращает true, если файл открыт на чтение; в противном случае (если файл закрыт или открыт только на запись) возвращает false.

bool CanWrite { get; }

Возвращает true, если файл открыт на запись; в противном случае (если файл закрыт или открыт только на чтение) возвращает false.

bool CanSeek { get; }

Возвращает true, если для файла разрешен прямой доступ к элементам; в противном случае (если файл закрыт или обеспечивает только последовательный доступ) возвращает false.

long Length { get; }
long Position { get; set; }

Свойство Length возвращает размер открытого файла в байтах, свойство Position возвращает и позволяет изменить текущую позицию файлового указателя (следует обратить внимание на то, что данные свойства имеют тип long, поэтому в принципе позволяют хранить размер и позицию файлового указателя для файлов размера до 9223372036854775807 байтов, или около 9 миллионов терабайтов).

Если файл закрыт или не поддерживает прямой доступ (то есть если свойство CanSeek возвращает false), попытка вызвать данные свойства приводит к возбуждению исключения. Исключение возбуждается также при попытке присвоить свойству Position отрицательное значение, однако допускается присваивать свойству Position значение, большее значения свойства Length.

Если присвоить свойству Position значение, большее Length, то автоматического изменения размера файла не произойдет. Для увеличения размера файла необходимо произвести запись новых элементов в установленную позицию (при этом значения байтов, расположенных между старыми и новыми элементами, полагаются равными нулю). Для увеличения размера файла без записи в него новых элементов можно использовать метод SetLength.

Создание

Стандартным способом создания объекта FileStream, как и объекта любого класса, является вызов конструктора. Ниже описывается наиболее часто используемый вариант конструктора FileStream.

FileStream(string name, FileMode mode[, FileAccess access]);

Создает объект типа FileStream, связывает данный объект с файлом, имеющим имя name, и открывает данный файл в режиме, указанном в параметре mode. Если параметр access указан, то он определяет способ доступа к данному файлу; в противном случае устанавливается доступ для чтения и записи (FileAccess.ReadWrite). Совместный доступ к файлу из нескольких файловых потоков возможен только в случае, если для всех этих потоков установлен доступ только для чтения (FileAccess.Read).

В качестве имени name можно указывать как полное имя (включающее имя диска, путь, собственно имя и расширение), так и краткое имя (без имени диска и пути). Если указано краткое имя, то файл ищется в текущем каталоге, то есть в рабочем каталоге приложения (work directory). Если в имени файла указан несуществующий диск или каталог, то возбуждается исключение DirectoryNotFoundException; если полное имя файла имеет слишком большую длину (более 259 символов), то возбуждается исключение PathTooLongException.

Помимо вызова конструктора имеются и другие способы создать объект типа FileStream. В частности, объект типа FileStream возвращают перечисленные ниже классовые методы класса File (см. также описание некоторых других методов класса File в справочных сведениях к модулю № 5).

static FileStream Create(string name);

Создает файл с именем name (или очищает файл, если он уже существует) и открывает его на чтение и запись (FileAccess.ReadWrite).

static FileStream OpenRead(string name);

Открывает существующий файл с именем name на чтение (FileAccess.Read); если файл не существует, то возбуждается исключение FileNotFoundException. При открытии файла с помощью этого метода возможен совместный доступ к файлу из нескольких файловых потоков.

static FileStream OpenWrite(string name);

Открывает файл с именем name на запись (FileAccess.Write); если файл не существует, то он создается.

static FileStream Open(string name, FileMode mode[, FileAccess access]);

Работает аналогично описанному выше конструктору класса FileStream, за исключением того, что файл всегда открывается в режиме монопольного доступа, даже при открытии файла только на чтение.

Главным преимуществом методов Create, OpenRead и OpenWrite класса File является более краткая форма их вызова (по сравнению с вызовом конструктора класса FileStream).

Методы

long Seek(long offset, SeekOrigin origin);

Изменяет текущую позицию файлового указателя для файлового потока this и возвращает новую позицию файлового указателя. Параметр offset определяет смещение указателя, а параметр origin — позицию в файле, относительно которой отсчитывается смещение (см. описание перечисления SeekOrigin). Вместо вызова данного метода достаточно изменить свойство Position:

f.Seek(4, SeekOrigin.Begin) эквивалентно f.Position = 4

f.Seek(4, SeekOrigin.Current) эквивалентно f.Position += 4

f.Seek(–4, SeekOrigin.End) эквивалентно f.Position = f.Length – 4 (в последнем примере предполагается, что файл имеет размер не менее 4 байтов).

void SetLength(long value);

Изменяет размер файла (в байтах), полагая его равным значению value. Если параметр value является отрицательным, то возбуждается исключение ArgumentOutOfRangeException. Допустимо как уменьшать размер файла (при этом удаляются его последние байты), так и увеличивать его размер (при этом в конец файла добавляются новые байты с нулевыми значениями).

При уменьшении размера файла может измениться значение свойства Position. Это происходит в ситуации, когда прежнее значение свойства Position оказывается большим, чем новый размер файла. В подобной ситуации свойство Position полагается равным новому размеру файла Length (иными словами, файловый указатель перемещается на маркер конца файла EOF).

int ReadByte();

Считывает значение байта из текущей позиции файла, перемещает файловый указатель к следующему байту (то есть увеличивает значение свойства Position на 1) и возвращает значение считанного байта, преобразованное к типу int.

Если предпринимается попытка прочесть байт за концом файла, то метод возвращает –1, а значение свойства Position не изменяется. Таким образом, если файл открыт и доступен для чтения, то выполнение данного метода никогда не приведет к возбуждению исключения.

int Read(byte[] array, int start, int count);

Считывает count или менее байтов из файлового потока this (начиная с байта, на который указывает файловый указатель), последовательно записывает их в элементы массива байтов array, начиная с элемента с индексом start, и возвращает количество фактически считанных байтов. Возвращаемое значение будет равно count, если успешно считаны все требуемые байты. В противном случае (если конец файла будет достигнут раньше, чем закончится считывание count байтов) возвращаемое значение будет меньше параметра count. После выполнения метода файловый указатель перемещается вперед на количество фактически прочитанных байтов (иными словами, значение свойства Position увеличивается на величину, равную возвращаемому значению метода Read).

При выполнении данного метода исключение возбуждается в следующих случаях:

  • файл открыт только на запись;
  • в процессе чтения произошла какая-либо ошибка (например, стал недоступен диск с данным файлом);
  • параметр count отрицателен;
  • массив array равен null или не содержит элементов с индексами в диапазоне от start до start + count – 1.

Если при выполнении метода возбуждается исключение, то позиция файлового указателя не изменяется.

void WriteByte(byte value);

Записывает в текущую позицию файлового потока this один байт, равный value, и перемещает файловый указатель к следующему байту.

Если текущая позиция находится за последним байтом файла (то есть значение свойства Position равно свойству Length или превышает его), то размер файла увеличивается, а значение свойства Position становится равным новому значению Length. Кроме того, выполняется обнуление всех байтов, находящихся между последним из «старых» байтов и только что записанным байтом.

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

void Write(byte[] array, int start, int count);

Записывает count байтов из массива байтов array, начиная с элемента с индексом start, в файловый поток this, начиная с байта, на который указывает файловый указатель. После выполнения метода файловый указатель перемещается вперед на count байтов (иными словами, значение свойства Position увеличивается на величину count). При выполнении данного метода возможно увеличение размера файла.

Если запись выполняется с позиции за концом файла (то есть значение свойства Position превышает значение свойства Length), то выполняется обнуление всех байтов, находящихся между последним из «старых» байтов и только что записанными байтами.

При выполнении данного метода исключение возбуждается в следующих случаях:

  • файл открыт только на чтение;
  • в процессе записи произошла какая-либо ошибка (например, на диске недостаточно места для записи новых байтов);
  • параметр count отрицателен;
  • массив array равен null или не содержит элементов с индексами в диапазоне от start до start + count – 1.

Если при выполнении метода возбуждается исключение, то позиция файлового указателя не изменяется.

void Flush();

Записывает в файл данные, содержащиеся в файловом буфере (то есть в области оперативной памяти, в которой накапливаются данные, полученные из программы, перед их записью в файл). После этого очищает файловый буфер. Данный метод автоматически вызывается при закрытии файла методом Close, поэтому в его явном вызове, как правило, нет необходимости.

void Close();

Закрывает файл, связанный с файловым потоком this, и освобождает неуправляемые ресурсы, выделенные для работы с данным файлом. Перед закрытием файла для него выполняется метод Flush.

Когда объект, связанный с файловым потоком, разрушается (то есть удаляется из памяти), для него автоматически вызывается метод Close. Несмотря на эту «страховочную» возможность, следует всегда закрывать файловый поток сразу после завершения работы с ним, явным образом вызывая метод Close (или вызывая одноименный метод потока-оболочки, связанного с данным потоком, — см. описание метода Close для классов BinaryReader и BinaryWriter).

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

После закрытия файла можно обращаться к свойствам CanRead, CanWrite, CanSeek (все они будут возвращать false), а также к свойству Name, которое по-прежнему будет возвращать полное имя файла.

Двоичные потоки-оболочки: классы BinaryReader и BinaryWriter

Рассмотренный в предыдущем разделе класс FileStream позволяет осуществлять ввод-вывод файловых данных только в виде наборов байтов. Для возможности чтения или записи более сложных структур данных необходимо использовать «надстройки» над стандартным файловым потоком: класс BinaryReader (двоичный поток-оболочка для чтения) или класс BinaryWriter (двоичный поток-оболочка для записи) соответственно. Оба эти класса определены в пространстве имен System.IO. Они обеспечивают чтение из файла и запись в файл всех элементарных типов языка C#, кроме класса object.

Конструкторы, общие свойства и методы двоичных потоков

Хотя классы BinaryReader и BinaryWriter предназначены для выполнения принципиально различных действий, у них имеется ряд одинаковых свойств и методов, которые удобно описать совместно. Кроме того, похожими являются и конструкторы данных классов.

BinaryReader(Stream stream[, Encoding encoding]);
BinaryWriter(Stream stream[, Encoding encoding]);

Каждый из конструкторов создает соответствующий двоичный поток-оболочку, которая связывается с базовым потоком stream. Тип Stream является общим предком всех классов-потоков; при работе с файлами в качестве параметра stream указывается объект типа FileStream. Базовый поток stream необязательно предварительно сохранять в отдельной переменной; допустимо создавать его «на лету», указывая в качестве первого параметра вызов конструктора класса FileStream или подходящий метод класса File. В дальнейшем доступ к базовому потоку можно получить, используя свойство BaseStream потоков-оболочек.

Параметр encoding определяет для класса BinaryReader формат декодирования символьных данных при их чтении из файла, а для класса BinaryWriter — формат кодирования символьных данных при их записи в файл. Если данный параметр не указан, то используется формат UTF-8 (см. раздел о кодировании символьных данных).

Если поток stream не поддерживает требуемую операцию ввода-вывода (чтение в случае BinaryReader или запись в случае BinaryWriter) или если поток уже закрыт, то возбуждается исключение ArgumentException. Если какой-либо из параметров равен null, то возбуждается исключение ArgumentNullException.

Stream BaseStream { get; }

Свойство только для чтения, возвращающее базовый поток для потока-оболочки this. Приводить данное свойство (типа Stream) к типу FileStream следует только в случае, если требуется обратиться к свойству Name или методу SetLength класса FileStream, так как все прочие свойства и методы класса FileStream уже определены в классе Stream, являющемся предком всех классов-потоков. Доступ к базовому потоку с помощью свойства BaseStream возможен только при открытом потоке-оболочке (до вызова его метода Close).

Следует заметить, что действия по прямому доступу к файловым элементам для потока-оболочки BinaryReader возможны только с помощью свойств и методов потока BaseStream, поскольку поток-оболочка BinaryReader аналогичных средств для прямого доступа не имеет. Для потока-оболочки BinaryWriter реализован метод Seek, однако доступ к свойствам базового потока (в частности, Position и Length) возможен только через свойство BaseStream.

void Close();

Закрывает базовый поток BaseStream, связанный с потоком-оболочкой this, и освобождает неуправляемые ресурсы, выделенные для работы с этими потоками. Перед закрытием потока-оболочки BinaryWriter для него выполняется метод Flush. Повторное выполнение метода Close игнорируется, не возбуждая исключения.

Необходимо обязательно вызывать метод Close потока-оболочки, так как данный метод (в отличие от одноименного метода класса FileStream) не вызывается автоматически при разрушении объекта типа BinaryReader или BinaryWriter (подробности см. в [6], гл. 19, с. 411).

Поскольку метод Close потока-оболочки автоматически закрывает базовый поток, явно вызывать метод Close базового потока после закрытия потока-оболочки не требуется.

Если к одному и тому же файлу одновременно подключены два потока-оболочки (BinaryReader и BinaryWriter), то нельзя закрывать один из них до завершения работы с другим, так как закрытие одного из потоков-оболочек автоматически приведет к закрытию базового потока, связанного с файлом, а это сделает невозможным доступ к файлу для оставшегося потока-оболочки.

Чтение данных с помощью объекта BinaryReader

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

При работе с символьными данными (типа char и string) следует учитывать, что в файле они хранятся в закодированном виде, поэтому для их правильного считывания необходимо при создании потока-оболочки BinaryReader указать тот же формат кодирования, который использовался при записи этих символьных данных в файл.

Для чтения каждого элементарного типа данных (кроме класса object) в классе BinaryReader предусмотрен особый метод. Ниже приводятся только те методы, которые связаны с элементарными типами, используемыми в настоящем пособии.

bool ReadBoolean();
byte ReadByte();
int ReadInt32();
long ReadInt64();
double ReadDouble();
char ReadChar();
string ReadString();

Каждый из методов данной группы считывает из потока один элемент требуемого типа и возвращает его значение (за исключением метода ReadBoolean, который читает из потока один байт и возвращает false, если прочитанный байт равен 0, и true в противном случае).

При попытке прочесть данные за концом файла возбуждается исключение EndOfStreamException.

Метод ReadString вначале читает из потока информацию о длине строки (в байтах), а затем считывает указанное количество байтов и преобразует прочитанные байты в символы, учитывая использованный в файле формат кодирования. Следует заметить, что информация о длине строки может занимать от 1 до 5 байтов, в зависимости от размеров строки. Например, если символы строки занимают не более 127 байтов (наиболее распространенная ситуация), то длина строки кодируется в одном байте, причем значение этого байта равно количеству байтов текста. Случаи, когда символы строки в двоичном файле занимают более 127 байтов, встречаются достаточно редко и поэтому здесь подробно не описываются; отметим лишь, что если строка занимает от 128 до 16383 байтов, то ее длина кодируется двумя байтами. Подчеркнем, что длина строки определяется в байтах, а не в символах; в кодировках UTF-8 и UTF-16 (Unicode) это различие является существенным, так как, например, русские буквы в этих кодировках занимают по 2 байта.

byte[] ReadBytes(int count);
char[] ReadChars(int count);

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

Метод ReadChars выполняется аналогично, за исключением того, что считываются не байты, а символы.

int Read();

Считывает из потока один символ и возвращает его значение, преобразованное к типу int (то есть возвращается код символа в кодировке Unicode). Если предпринимается попытка прочесть символ за концом файла, то метод возвращает –1. Таким образом, данный метод работает аналогично методу ReadByte класса FileStream, за исключением того, что считывается символ, а не байт данных (как в методе ReadByte).

int Read(byte[] bytes, int start, int count);
int Read(char[] chars, int start, int count);

Вариант метода Read с параметром bytes (массивом байтов) работает в точности так же, как метод Read класса FileStream.

Вариант метода Read с параметром chars (массивом символов) работает аналогично, за исключением того, что из файла производится считывание не байтов, а символов.

Напомним, что во всех методах, считывающих символы, учитывается формат кодирования, указанный при создании объекта-оболочки BinaryReader.

Запись данных с помощью объекта BinaryWriter

Для записи данных в классе BinaryWriter предусмотрен единственный метод Write, который перегружен для различных типов записываемых данных, в том числе для всех элементарных типов (кроме класса object). Ниже приводятся только те варианты метода Write, которые связаны с элементарными типами, используемыми в настоящем пособии.

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

Текст числовой_тип, используемый при описании параметра value, обозначает любой из рассматриваемых в пособии числовых типов.

Если параметры методов равны null, то возбуждается исключение ArgumentNullException.

При работе с символьными данными (типа char и string) следует учитывать, что при их записи в файл будет выполняться кодирование, формат которого определяется при создании потока-оболочки BinaryWriter.

void Write(bool value);
void Write(числовой_тип value);
void Write(char value);
void Write(string value);

Каждый из методов данной группы записывает в поток значение параметра value соответствующего типа. Исключение составляет параметр value типа bool, вместо которого в файл записывается один байт со значением 0 (если параметр равен false) или 1 (если параметр равен true).

При записи строки в файл вначале записывается информация о длине строки (указывается длина уже закодированной строки в байтах), а затем — сами символы строки (символы кодируются с учетом формата кодирования, определенного для потока BinaryWriter). По поводу способа хранения в файле информации о длине строки см. описание метода ReadString.

void Write(byte[] array[, int start, int count]);
void Write(char[] array[, int start, int count]);

Каждый из данных методов записывает в поток элементы массива array (байты или символы). Если параметры start и count указаны, то записывается count элементов массива array, начиная с элемента с индексом start; в противном случае в поток записываются все элементы массива. Таким образом, первый из данных методов работает аналогично методу Write класса FileStream.

Дополнительные поля и методы двоичных потоков

Кроме рассмотренных выше свойств и методов класс BinaryReader содержит полезный метод PeekChar.

int PeekChar();

Возвращает код текущего символа из базового потока (преобразованный к типу int) или –1, если, начиная с текущей позиции, поток не содержит символов. Изменения позиции файлового указателя при выполнении метода PeekChar не происходит.

Данный метод удобно использовать для проверки того, достигнут ли конец файла при считывании данных с помощью потока-оболочки BinaryReader:

while (r.PeekChar() != -1)
{
    чтение и обработка очередного
    элемента из потока-оболочки r
}

Следует, однако, иметь в виду, что при использовании стандартного формата кодирования UTF-8 данный метод может возвращать –1, не доходя до фактического конца файла. Например, если последними элементами двоичного файла являются отрицательные целые числа (типа int) в диапазоне от –1 до –128, то уже при анализе первого из этих чисел метод PeekChar вернет –1 (это «странное» поведение метода связано с особенностями формата UTF-8). Чтобы избежать подобных проблем и обеспечить безопасную обработку числовых двоичных файлов с использованием метода PeekChar, необходимо при создании потока-оболочки BinaryReader указывать формат кодирования Encoding.Default, соответствующий ANSI-кодировке, используемой системой Windows по умолчанию.

В заключение опишем дополнительные поля и методы класса BinaryWriter.

static readonly BinaryWriter Null;

Данное классовое поле позволяет использовать в программе «пустой» двоичный поток-оболочку, который записывает данные «в никуда». Эта возможность оказывается полезной при отладке, а также при реализации «молчаливых» режимов работы программы.

void Flush();

Записывает в файл данные, содержащиеся в буфере потока-оболочки this. После этого очищает данный буфер.

long Seek(int offset, SeekOrigin origin);

Вызывает одноименный метод базового потока BaseStream, связанного с потоком-оболочкой this.

Подчеркнем, что два последних метода реализованы только для класса BinaryWriter (у класса BinaryReader они отсутствуют).


Модуль 4. Двоичные файлы

Контрольные вопросы

Для каждого вопроса щелкните мышью на радиокнопке с номером варианта ответа, который, по вашему мнению, является правильным. Во всех фрагментах кода предполагается, что в программе указаны операторы using со всеми требуемыми пространствами имен. После того как вы ответите на все вопросы, проверьте правильность ответов, нажав на кнопку «Проверка».

1. Для вывода на экран каждого второго элемента существующего двоичного файла вещественных чисел с именем a.dat учащийся написал следующий фрагмент:

FileStream f = File.OpenRead("a.dat");
BinaryReader r = new BinaryReader(f);
for (int i = 1; i <= f.Length / 2; i++)
{
    f.Position = 2 * i;
    Console.WriteLine(r.ReadDouble());
}

Что произойдет при обработке этого фрагмента?

(1) | При компиляции фрагмента будет выведено сообщение об ошибке | (2) | При выполнении фрагмента будет возбуждено исключение
(3) | При выполнении фрагмента исключения не возникнут; в результате выполнения фрагмента на экран будут выведены первый, третий, пятый и т. д. элементы массива. | (4) | При выполнении фрагмента исключения не возникнут; в результате выполнения фрагмента на экран будут выведены второй, четвертый, шестой и т. д. элементы массива.

2. Дан файловый поток f типа FileStream, связанный с файлом целых чисел, содержащим более 5 элементов. Какой из указанных операторов позволит уменьшить размер файла до 5 элементов?

(1) | f.Length = 5; | (2) | f.Length = 20;
(3) | f.SetLength(5); | (4) | f.SetLength(20);

3. Укажите неверное утверждение.

(1) | С помощью файлового потока FileStream можно перемещаться к требуемому файловому элементу | (2) | С помощью файлового потока FileStream можно считывать из файла отдельные байты и наборы байтов
(3) | С помощью файлового потока FileStream можно определить размер файла и текущую позицию файлового указателя | (4) | С помощью файлового потока FileStream можно записывать в файл значения переменных различных числовых типов

4. Строка длиной 150 символов, состоящая из пробелов, латинских букв и цифр, записана в двоичный строковый файл в кодировке UTF-8. Сколько байтов в файле будет отведено для хранения этой строки?

(1) | 150 | (2) | 151
(3) | 152 | (4) | 302

5. Укажите имя, которое не является именем метода класса BinaryReader.

(1) | ReadBoolean | (2) | ReadInt
(3) | ReadString | (4) | ReadChar

6. С файловым потоком f типа FileStream связаны два потока-оболочки: r типа BinaryReader и w типа BinaryWriter. Укажите правильный вариант закрытия всех потоков.

(1) r.Close();
w.Close(); 		(2) r.Close();
f.Close();
(3) w.Close();
f.Close(); 		(4) f.Close();


Модуль 4. Двоичные файлы

Итоговое задание

Ввести с клавиатуры строку Name — допустимое имя файла. Используя датчик случайных чисел, создать двоичный файл с именем Name и расширением .bin, содержащий целые числа в диапазоне от –100 до 100 (количество элементов в файле также определяется случайным образом и может лежать в диапазоне от 10 до 100).

Описать вспомогательную функцию FileOutput(name), которая выводит на экран имя двоичного файла name, содержащего целые числа, и содержимое этого файла, располагая на каждой экранной строке по 10 элементов и отводя для каждого элемента поле шириной 5 позиций. Используя функцию FileOutput, вывести на экран содержимое созданного файла.

На основе созданного файла сформировать два новых двоичных файла. Первый файл (файл А) имеет имя, полученное из имени файла Name добавлением в его начало буквы «A», расширение .bin, и содержит все положительные числа из файла Name (в исходном порядке). Второй файл (файл B) имеет имя, полученное из имени файла Name добавлением в его начало буквы «B», расширение .bin, и содержит все отрицательные числа из файла Name (в обратном порядке). Вывести содержимое файлов A и B на экран, используя функцию FileOutput.

Преобразовать файлы A и B, переместив из файла A 10 последних элементов (или все элементы, если их менее 10) в конец файла B в исходном порядке. Вывести содержимое преобразованных файлов A и B на экран, используя функцию FileOutput.

Приведем образец текста, который должен выводиться программой, выполняющей проектное задание:

Name = qwerty
qwerty.bin:
  -87  -58   -2   35  -22   46    4   12   38  -66
   81   51   48   71   -5    0    7    0  -35   30
   45  -83   24   69   74  -26   90   29   -3   37
  -38  -52   51   44  -42   13   12   82  -52   15
   54  -17   89   -2  -88   78  -79   38   94   60
   99   64   37   84  -76   96   18   30   62  -97
   57   76  -86 -100   72   32 -100  -96   13   83
   89   25  -49   50   76  -39  -71  -74   56   96
  -47  -48   36   38   38   -1   12
Aqwerty.bin:
   35   46    4   12   38   81   51   48   71    7
   30   45   24   69   74   90   29   37   51   44
   13   12   82   15   54   89   78   38   94   60
   99   64   37   84   96   18   30   62   57   76
   72   32   13   83   89   25   50   76   56   96
   36   38   38   12
Bqwerty.bin:
   -1  -48  -47  -74  -71  -39  -49  -96 -100 -100
  -86  -97  -76  -79  -88   -2  -17  -52  -42  -52
  -38   -3  -26  -83  -35   -5  -66  -22   -2  -58
  -87
Aqwerty.bin:
   35   46    4   12   38   81   51   48   71    7
   30   45   24   69   74   90   29   37   51   44
   13   12   82   15   54   89   78   38   94   60
   99   64   37   84   96   18   30   62   57   76
   72   32   13   83
Bqwerty.bin:
   -1  -48  -47  -74  -71  -39  -49  -96 -100 -100
  -86  -97  -76  -79  -88   -2  -17  -52  -42  -52
  -38   -3  -26  -83  -35   -5  -66  -22   -2  -58
  -87   89   25   50   76   56   96   36   38   38
   12


Модуль 5. Текстовые файлы

Комплексная цель

Рассмотреть различия между двоичными и текстовыми файлами и изучить основные алгоритмы, связанные с обработкой текстовых файлов. Ознакомиться с классами StreamReader, StreamWriter, File и Path стандартной библиотеки .NET. Рассмотреть особенности обработки текстовых файлов, содержащих текст в различных форматах кодирования. Изучить способы форматирования данных при их записи в текстовый файл.

Структура модуля

  • Решения типовых задач
  • Учебные задания и указания к ним
  • Справочные сведения
  • Контрольные вопросы
  • Итоговое задание


Модуль 5. Текстовые файлы

Решения типовых задач

Создание текстового файла: Text1

Текстовые файлы содержат текстовые строки, разделенные специальными маркерами конца строки EOLN (в операционной системе Windows маркер EOLN представляет собой два подряд идущих символа с кодами 13 и 10). Такие файлы можно просматривать и редактировать в обычных текстовых редакторах, например, в редакторе системы Visual Studio.

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

При выполнении учебных заданий с использованием электронного задачника Programming Taskbook следует учитывать отличия в отображении содержимого текстовых файлов. Для вывода строковых данных, составляющих содержание текстового файла, отводится не одна, а несколько (от двух до пяти) подряд идущих экранных строк, на каждой из которых размещается одна строка текстового файла. Указатель текущей позиции в данном случае содержит номер первой отображаемой на экране файловой строки (нумерация ведется от единицы) и размещается в начале первой из экранных строк, отведенных для отображения текстового файла. Прокрутка строк текстового файла обеспечивается теми же клавишами, что и прокрутка элементов двоичного файла (см. описание возможностей прокрутки в модуле № 4).

Рассмотрим задание Text1, посвященное созданию текстового файла.

Text1. Дано имя файла и целые положительные числа N и K. Создать текстовый файл с указанным именем и записать в него N строк, каждая из которых состоит из K символов «*» (звездочка).

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

Task("Text1");
StreamWriter w = new
    StreamWriter(GetString(),
    false, Encoding.Default);
int n = GetInt(), k = GetInt();
string s = new string('*', k);
for (int i = 1; i <= n; i++)
    w.WriteLine(s);
w.Close();

В данном решении вначале формируется вспомогательная строка из K символов '*' (для этого используется соответствующий конструктор класса string), а затем созданная строка N раз записывается в текстовый файл.

Обратите внимание на то, что для записи в текстовый файл используется специальный текстовый поток-оболочка StreamWriter, при создании которого указывается имя файла, режим открытия файла и формат кодирования (см. раздел о форматах кодирования в справочных сведениях к модулю № 3). Имеются два режима открытия: для перезаписи (параметр конструктора равен false; прежнее содержимое файла пропадает) и для дополнения (параметр равен true; прежнее содержимое файла дополняется новыми строками). При любом режиме файл открывается только на запись, а если файл не существует, то он автоматически создается. Для записи строки в файл используется метод WriteLine класса StreamWriter, который записывает в файл не только указанную строку, но и маркер конца строки.

Следует заметить, что символ '*', записываемый в файл, является символом ASCII, поэтому при создании текстового потока можно было не указывать формат кодирования, используемый при записи символов в файл. Однако в большинстве заданий на обработку текстовых файлов формат кодирования Encoding.Default указывать необходимо, поэтому во всех примерах будут использоваться конструкторы потоков StreamReader и StreamWriter, содержащие соответствующий параметр. Напомним, что при использовании данного параметра надо с помощью оператора using указать в начале программы пространство имен System.Text, в котором определен класс Encoding.

При формировании текстового файла можно обойтись без использования строк, записывая данные в файл посимвольно. Приведем соответствующий вариант решения задания Text1.

Task("Text1");
StreamWriter w = new
    StreamWriter(GetString(),
    false, Encoding.Default);
int n = GetInt(), k = GetInt();
for (int i = 1; i <= n; i++)
{
    for (int j = 1; j <= k; j++)
        w.Write('*');
    w.WriteLine();
}
w.Close();

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

Анализ текстового файла: Text4

Text4. Дан текстовый файл. Вывести количество содержащихся в нем символов и строк (маркеры концов строк EOLN и конца файла EOF при подсчете количества символов не учитывать).

В первом варианте решения будем считывать файловые данные построчно:

Task("Text4");
StreamReader r = new
    StreamReader(GetString(),
    Encoding.Default);
int nc = 0, ns = 0;
while (r.Peek() != -1)
{
    nc += r.ReadLine().Length;
    ns++;
}
r.Close();
Put(nc, ns);

Обратите внимание на то, что в условии цикла while использован метод Peek, который возвращает код очередного символа из файла, не считывая данный символ (и, следовательно, не перемещая файловый указатель), причем если достигнут конец файла, то данный метод возвращает –1. Начиная с версии 2.0 .NET Framework, для проверки того, достигнут ли конец текстового файла, можно использовать логическое свойство EndOfStream; при этом заголовок цикла будет иметь вид

while (!r.EndOfStream)

Обратите также внимание на вызов метода Length, выполняемый для строки, которая возвращается функцией ReadLine.

Задание Text4 можно решить и с помощью посимвольного считывания файловых данных:

Task("Text4");
StreamReader r = new
    StreamReader(GetString(),
    Encoding.Default);
int nc = 0, ns = 0;
while (r.Peek() != -1)
{
    if ((char)r.Peek() != '\r')
    {
        r.Read();
        nc++;
    }
    else
    {
        r.ReadLine();
        ns++;
    }
}
r.Close();
Put(nc, ns);

Здесь мы учли тот факт, что маркер конца строки EOLN в операционной системе Windows состоит из двух управляющих символов: '\r' (carriage return — возврат каретки, код 13) и '\n' (line feed — переход на новую строку, код 10). Для пропуска всех символов, входящих в маркер конца строки EOLN, мы воспользовались методом ReadLine. Следует помнить, что и метод Peek, и метод Read возвращают не сам символ, а его код, то есть число целого типа.

Заметим, что второй вариант решения будет правильно анализировать текстовый файл только в том случае, если каждая строка этого файла оканчивается маркером конца строки EOLN. Для текстовых файлов, которые генерируются электронным задачником Programming Taskbook, это условие всегда выполняется, поэтому данный вариант решения будет признан правильным. Однако для произвольного текстового файла возможна ситуация, когда за последней строкой файла следует не маркер конца строки EOLN, а сразу маркер конца файла EOF (это произойдет, если последний элемент данных будет записан в текстовый файл с помощью метода Write, а не WriteLine). В этом случае при использовании второго из рассмотренных алгоритмов найденное количество строк будет на 1 меньше фактического, так как строки в этом алгоритме подсчитываются по маркерам EOLN, а последняя строка такого маркера не имеет. Первый вариант решения лишен подобного недостатка, поскольку метод ReadLine правильно считывает последнюю файловую строку и в том случае, когда эта строка оканчивается не маркером EOLN, а сразу маркером EOF. ервый вариант решения является более эффективным и с точки зрения быстродействия.

Преобразование текстового файла: Text21

Text21. Дан текстовый файл, содержащий более трех строк. Удалить из него последние три строки.

Аналогичные задания на удаление последних элементов из двоичного файла (см. File29, File30, File58, File59) решаются просто: достаточно определить количество байтов n, которое должно остаться в «урезанном» файле и вызвать метод SetLength(n) класса FileStream. Однако в случае текстовых файлов вызов метода SetLength базового потока приводит к возбуждению исключения. Поэтому для преобразования исходного файла придется воспользоваться вспомогательным файлом (как во втором варианте решения задания File25). Для определения количества строк, содержащихся в исходном файле, проще всего выполнить их считывание.

Учитывая эти замечания, получаем первый вариант решения:

Task("Text21");
string s1 = GetString(),
    s2 = "$T21$.tmp";
StreamReader r = new
    StreamReader(s1,
    Encoding.Default);
StreamWriter w = new
    StreamWriter(s2, false,
    Encoding.Default);
int n = 0;
while (r.Peek() != -1)
{
    r.ReadLine();
    n++;
}
r.Close();
r = new StreamReader(s1,
    Encoding.Default);
for (int i = 1; i <= n - 3; i++)
    w.WriteLine(r.ReadLine());
r.Close();
w.Close();
File.Delete(s1);
File.Move(s2, s1);

Этот вариант решения является неэффективным, поскольку требует двух просмотров исходного файла: первый — для определения его размера, который записывается в переменную n, второй — для создания вспомогательного файла, содержащего все строки исходного файла, кроме трех последних. Заметим, что после первого просмотра и закрытия исходного файла приходится повторно вызывать конструктор класса StreamReader для повторного открытия этого же файла.

Задание Text21 можно выполнить и за один просмотр исходного файла, если воспользоваться следующим наблюдением: строка должна быть записана во вспомогательный файл, если после нее в исходном файле содержатся по крайней мере три строки. Таким образом, записывать очередную строку во вспомогательный файл следует только после считывания из исходного файла трех следующих за ней строк. Благодаря такому упреждающему считыванию необходимость в предварительном определении размера исходного файла отпадает. Для хранения строк, которые уже считаны из исходного файла, но еще не записаны во вспомогательный файл, заведем массив из трех элементов.

Приведем этот вариант решения.

Task("Text21");
string s1 = GetString(),
    s2 = "$T21$.tmp";
string[] s = new string[3];
StreamReader r = new
    StreamReader(s1,
    Encoding.Default);
StreamWriter w = new
    StreamWriter(s2, false,
    Encoding.Default);
int n = 0;
for (int i = 0; i < 3; i++)
    s[i] = r.ReadLine();
while (r.Peek() != -1)
{
    w.WriteLine(s[n]);
    s[n] = r.ReadLine();
    n = (n + 1) % 3;
}
r.Close();
w.Close();
File.Delete(s1);
File.Move(s2, s1);

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

На каждой итерации цикла while во вспомогательный файл записывается строка s[n], то есть текущий элемент массива s (в этот момент уже известно, что после данной строки в исходном файле содержатся, по крайней мере, три строки: две из этих строк уже прочитаны в массив, а на третьей строке располагается файловый указатель). Затем из исходного файла считывается очередная строка, которая записывается на место только что сохраненного во вспомогательном файле элемента массива s. Наконец, выполняется циклическое изменение номера n: 0 переходит в 1, 1 — в 2, 2 — в 0.

После завершения цикла while во вспомогательный файл будут записаны все строки исходного файла, кроме последних трех, а эти три последние строки будут содержаться в массиве s.

Итак, использовав вспомогательный массив из трех строк, мы получили эффективный однопроходный алгоритм решения задания Text21. Аналогичным способом можно решить задания Text22 и Text23, в которых требуется удалить или скопировать K последних строк текстового файла.


Модуль 5. Текстовые файлы

Учебные задания и указания к ним

Текстовые файлы

Условие вида «дан текстовый файл» означает, что в наборе исходных данных указано имя данного файла (текстовая строка). Все исходные файлы в заданиях данной группы считаются существующими. Элементы исходных файлов следует считывать с помощью стандартных текстовых потоков ввода.

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

Максимальный размер исходных файлов не устанавливается, поэтому при решении заданий не следует использовать вспомогательные массивы, содержащие все элементы исходных файлов, однако допускается использование вспомогательных файлов.

Используемые в заданиях двоичные файлы удовлетворяют условиям, которые перечислены в соответствующем разделе модуля «Двоичные файлы».

Основные операции с текстовыми файлами

Text1. Дано имя файла и целые положительные числа N и K. Создать текстовый файл с указанным именем и записать в него N строк, каждая из которых состоит из K символов «*» (звездочка).

Text2. Дано имя файла и целое число N (0 < N < 27). Создать текстовый файл с указанным именем и записать в него N строк: первая строка должна содержать строчную (то есть маленькую) латинскую букву «a», вторая — буквы «ab», третья — буквы «abc» и т. д.; последняя строка должна содержать N начальных строчных латинских букв в алфавитном порядке.

Text3. Дано имя файла и целое число N (0 < N < 27). Создать текстовый файл с указанным именем и записать в него N строк длины N; строка с номером K (K = 1, …, N) должна содержать K начальных прописных (то есть заглавных) латинских букв, дополненных справа символами «*» (звездочка). Например, для N = 4 файл должен содержать строки «A***», «AB**», «ABC*», «ABCD».

Text4. Дан текстовый файл. Вывести количество содержащихся в нем символов и строк (маркеры концов строк EOLN и конца файла EOF при подсчете количества символов не учитывать).

Text5. Дана строка S и текстовый файл. Добавить строку S в конец файла.

Text6. Даны два текстовых файла. Добавить в конец первого файла содержимое второго файла.

Text7. Дана строка S и текстовый файл. Добавить строку S в начало файла.

Text8. Даны два текстовых файла. Добавить в начало первого файла содержимое второго файла.

Text9. Дано целое число K и текстовый файл. Вставить пустую строку перед строкой файла с номером K. Если строки с таким номером нет, то оставить файл без изменений.

Text10. Дано целое число K и текстовый файл. Вставить пустую строку после строки файла с номером K. Если строки с таким номером нет, то оставить файл без изменений.

Text11. Дан текстовый файл. Продублировать в нем все пустые строки.

Text12. Дана строка S и текстовый файл. Заменить в файле все пустые строки на строку S.

Text13. Дан непустой текстовый файл. Удалить из него первую строку.

Text14. Дан непустой текстовый файл. Удалить из него последнюю строку.

Text15. Дано целое число K и текстовый файл. Удалить из файла строку с номером K. Если строки с таким номером нет, то оставить файл без изменений.

Text16. Дан текстовый файл. Удалить из него все пустые строки.

Text17. Даны два текстовых файла. Добавить в конец каждой строки первого файла соответствующую строку второго файла. Если второй файл короче первого, то оставшиеся строки первого файла не изменять.

Text18. Дано целое число K и текстовый файл. Удалить из каждой строки файла первые K символов (если длина строки меньше K, то удалить из нее все символы).

Text19. Дан текстовый файл. Заменить в нем все прописные русские буквы на строчные, а все строчные — на прописные.

Text20. Дан текстовый файл. Заменить в нем все подряд идущие пробелы на один пробел.

Text21. Дан текстовый файл, содержащий более трех строк. Удалить из него последние три строки.

Text22. Дано целое число K (0 < K < 10) и текстовый файл, содержащий более K строк. Удалить из файла последние K строк.

Text23. Дано целое число K (0 < K < 10) и текстовый файл, содержащий более K строк. Создать новый текстовый файл, содержащий K последних строк исходного файла.

Анализ и форматирование текста

Text24. Дан текстовый файл. Найти количество абзацев в тексте, если абзацы отделяются друг от друга одной или несколькими пустыми строками.

Text25. Дано целое число K и текстовый файл. Удалить из файла абзац с номером K (абзацы отделяются друг от друга одной или несколькими пустыми строками). Пустые строки, предшествующие и следующие за удаляемым абзацем, не удалять. Если абзац с данным номером отсутствует, то оставить файл без изменений.

Text26. Дан текстовый файл. Найти количество абзацев в тексте, если первая строка каждого абзаца начинается с 5 пробелов («красная строка»). Пустые строки между абзацами не учитывать.

Text27. Дано целое число K и текстовый файл. Удалить из файла абзац с номером K (абзацы выделяются с помощью красной строки — см. задание Text26). Пустые строки между абзацами не учитывать и не удалять. Если абзац с данным номером отсутствует, то оставить файл без изменений.

Text28. Дан текстовый файл. Абзацы выделяются в нем с помощью красной строки (см. задание Text26), а пустых строк нет. Вставить между соседними абзацами по одной пустой строке (в начало и конец файла пустые строки не добавлять).

Text29. Дан текстовый файл. Вывести первое слово текста наибольшей длины. Словом считать набор символов, не содержащий пробелов и ограниченный пробелами или началом/концом строки.

Text30. Дан текстовый файл. Вывести последнее слово текста наименьшей длины. Словом считать набор символов, не содержащий пробелов и ограниченный пробелами или началом/концом строки.

Text31. Дано целое число K и текстовый файл. Создать строковый файл и записать в него все слова длины K из исходного файла. Словом считать набор символов, не содержащий пробелов, знаков препинания и ограниченный пробелами, знаками препинания или началом/концом строки. Если исходный файл не содержит слов длины K, то оставить результирующий файл пустым.

Text32. Дан символ C — прописная (заглавная) русская буква и текстовый файл. Создать строковый файл и записать в него все слова из исходного файла, начинающиеся на эту букву (прописную или строчную). Словом считать набор символов, не содержащий пробелов, знаков препинания и ограниченный пробелами, знаками препинания или началом/концом строки. Если исходный файл не содержит подходящих слов, то оставить результирующий файл пустым.

Text33. Дан символ C — строчная (маленькая) русская буква и текстовый файл. Создать строковый файл и записать в него все слова из исходного файла, содержащие хотя бы одну букву C (прописную или строчную). Словом считать набор символов, не содержащий пробелов, знаков препинания и ограниченный пробелами, знаками препинания или началом/концом строки. Если исходный файл не содержит подходящих слов, то оставить результирующий файл пустым.

Text34. Дан текстовый файл, содержащий текст, выровненный по левому краю. Выровнять текст по правому краю, добавив в начало каждой непустой строки нужное количество пробелов (ширину текста считать равной 50).

Text35. Дан текстовый файл, содержащий текст, выровненный по левому краю. Выровнять текст по центру, добавив в начало каждой непустой строки нужное количество пробелов (ширину текста считать равной 50). Строки нечетной длины перед центрированием дополнять слева пробелом.

Text36. Дан текстовый файл, содержащий текст, выровненный по правому краю. Выровнять текст по центру, удалив из каждой непустой строки половину начальных пробелов. В строках с нечетным количеством начальных пробелов перед центрированием удалять первый начальный пробел.

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

Text38. Дано целое число K (> 25) и текстовый файл, содержащий текст, выровненный по левому краю. Абзацы текста отделяются друг от друга одной пустой строкой. Отформатировать текст так, чтобы его ширина не превосходила K позиций, и выровнять текст по левому краю, сохранив деление на абзацы. Пробелы в конце строк удалить. Сохранить отформатированный текст в новом текстовом файле.

Text39. Дано целое число K (> 25) и текстовый файл, содержащий текст, выровненный по левому краю. Абзацы выделяются в нем с помощью красной строки (5 начальных пробелов), а пустых строк нет. Отформатировать текст так, чтобы его ширина не превосходила K позиций, и выровнять текст по левому краю, сохранив деление на абзацы. Пробелы в конце строк удалить. Сохранить отформатированный текст в новом текстовом файле.

Текстовые файлы с числовой информацией

В заданиях на обработку текстовых файлов с числовой информацией предполагается, что в изображениях вещественных чисел, содержащихся в текстовых файлах, дробная часть отделяется от целой части десятичной точкой.

Text40. Даны два файла целых чисел одинакового размера. Создать текстовый файл, содержащий эти числа, расположенные в два столбца шириной по 30 символов (в первом столбце содержатся числа из первого исходного файла, во втором — из второго файла). В начало и конец каждой строки текстового файла добавить разделитель «|» (код 124). Числа выравниваются по правому краю столбца.

Text41. Даны три файла целых чисел одинакового размера. Создать текстовый файл, содержащий эти числа, расположенные в три столбца шириной по 20 символов (в каждом столбце содержатся числа из соответствующего исходного файла). В начало и конец каждой строки текстового файла добавить разделитель «|» (код 124). Числа выравниваются по левому краю столбца.

Text42. Даны вещественные числа A, B и целое число N. Создать текстовый файл, содержащий таблицу значений функции (x)1/2 на промежутке [A, B] с шагом (B – A)/N. Таблица состоит из двух столбцов: с аргументами x (10 позиций, из них 4 под дробную часть) и со значениями (x)1/2 (15 позиций, из них 8 под дробную часть). Столбцы выравниваются по правому краю.

Text43. Даны вещественные числа A, B и целое число N. Создать текстовый файл, содержащий таблицу значений функций sin(x) и cos(x) на промежутке [A, B] с шагом (B – A)/N. Таблица состоит из трех столбцов: с аргументами x (8 позиций, из них 4 под дробную часть) и со значениями sin(x) и cos(x) (по 12 позиций, из них 8 под дробную часть). Столбцы выравниваются по правому краю.

Text44. Дан текстовый файл, каждая строка которого изображает целое число, дополненное слева и справа несколькими пробелами. Вывести количество этих чисел и их сумму.

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

Text46. Дан текстовый файл, каждая строка которого содержит изображения нескольких чисел, разделенные пробелами (вещественные числа имеют ненулевую дробную часть). Создать файл вещественных чисел, содержащий (в том же порядке) все числа из исходного файла, имеющие ненулевую дробную часть.

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

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

Text49. Дан текстовый файл и файл целых чисел. Добавить в конец каждой строки текстового файла изображение соответствующего числа из файла целых чисел. Если файл целых чисел короче текстового файла, то оставшиеся строки текстового файла не изменять.

Text50. Дан текстовый файл. В каждой его строке первые 30 позиций отводятся под текст, а оставшаяся часть — под вещественное число. Создать два файла: строковый файл, содержащий текстовую часть исходного файла, и файл вещественных чисел, содержащий числа из исходного файла (в том же порядке).

Text51. Дан текстовый файл, содержащий таблицу из трех столбцов вещественных чисел. Ширина столбцов таблицы и способ их выравнивания являются произвольными, специальных символов-разделителей таблица не содержит. Создать три файла вещественных чисел, каждый из которых содержит числа из соответствующего столбца таблицы (в том же порядке).

Text52. Дан текстовый файл, содержащий таблицу из трех столбцов целых чисел. В начале и в конце каждой строки таблицы, а также между ее столбцами располагается символ-разделитель. Ширина столбцов таблицы, способ их выравнивания и вид символа-разделителя являются произвольными. Создать файл целых чисел, содержащий сумму чисел из каждой строки исходной таблицы.

Дополнительные задания на обработку текстовых файлов

Text53. Дан текстовый файл. Создать символьный файл, содержащий все знаки препинания, встретившиеся в текстовом файле (в том же порядке).

Text54. Дан текстовый файл. Создать символьный файл, содержащий все символы, встретившиеся в тексте, включая пробел и знаки препинания (без повторений). Символы располагать в порядке их первого появления в тексте.

Text55. Дан текстовый файл. Создать символьный файл, содержащий все символы, встретившиеся в тексте, включая пробел и знаки препинания (без повторений). Символы располагать в порядке возрастания их кодов.

Text56. Дан текстовый файл. Создать символьный файл, содержащий все символы, встретившиеся в тексте, включая пробел и знаки препинания (без повторений). Символы располагать в порядке убывания их кодов.

Text57. Дан текстовый файл. Подсчитать число появлений в нем каждой строчной (то есть маленькой) русской буквы и создать строковый файл, элементы которого имеют вид «<буква>–<число ее появлений>» (например, «а–25»). Буквы, отсутствующие в тексте, в файл не включать. Строки упорядочить по возрастанию кодов букв.

Text58. Дан текстовый файл. Подсчитать число появлений в нем каждой строчной (то есть маленькой) русской буквы и создать строковый файл, элементы которого имеют вид «<буква>–<число ее появлений>» (например, «а–25»). Буквы, отсутствующие в тексте, в файл не включать. Строки упорядочить по убыванию числа появлений букв, а при равном числе появлений — по возрастанию кодов букв.

Text59. Дана строка S, состоящая из 10 цифр, и файл с русским текстом. Зашифровать файл, выполнив циклическую замену каждой русской буквы, стоящей на K-й позиции строки, на букву того же регистра, расположенную в алфавите на SK-м месте после шифруемой буквы (для K = 11 снова используется смещение S1 и т. д.). Букву «ё» в алфавите не учитывать, знаки препинания и пробелы не изменять.

Text60. Дана строка и файл с русским текстом, зашифрованным по правилу, описанному в задании Text59. Данная строка представляет собой первую расшифрованную строку текста. Расшифровать остальные строки и заменить в файле зашифрованный текст на расшифрованный. Если информации для расшифровки недостаточно, то исходный файл не изменять.

Указания

Text1–3. См. решение Text1. Остальные задания решаются аналогично; для них, как и для Text1, можно реализовать два варианта решения: с построчным и посимвольным заполнением результирующего файла.

Text4. См. решение этого задания.

Text5–20. Для добавления новых данных в конец текстового файла (Text5–6) достаточно открыть текстовый поток StreamWriter в режиме дополнения, указав в его конструкторе в качестве второго параметра значение true. Все прочие действия по преобразованию строк файла, а именно, вставка строк в начало (Text7–8) или середину файла (Text9–11), замена строк (Text12), удаление строк (Text13–16), преобразование строк (Text17– 20), выполняются с помощью вспомогательного текстового файла (ср. со вторым вариантом решения File25). В Text14 для того чтобы проверить, является ли только что прочитанная (методом ReadLine) строка последней строкой файла, используйте функцию Peek. В Text19–20 удобно использовать посимвольное чтение и запись файловых данных (как во вторых вариантах решений Text1 и Text4). По поводу преобразования прописных русских букв в строчные и обратно (в Text19) см. указание к String16–18. При открытии всех текстовых файлов необходимо указывать формат кодирования Encoding.Default.

Text21–23. См. решение Text21. Остальные задания решаются аналогично.

Text24–25. Непустая строка является первой строкой абзаца, если ей предшествует пустая строка (или если она является первой строкой файла). Для хранения предшествующей строки используйте вспомогательную переменную. В Text25 используйте вспомогательный текстовый файл.

Text26–28. Для определения начала абзаца достаточно выделить из текущей строки первые пять символов (функцией Substring) и сравнить их со строкой из пяти пробелов. В Text27–28 используйте вспомогательный текстовый файл.

Text29–30. Для выделения слов удобно использовать посимвольное считывание данных из исходного файла (как во втором варианте решения Text4), при котором анализируется текущий и предшествующий ему символ (ср. с последним вариантом решения String41). Предшествующий символ должен храниться во вспомогательной переменной. Для того чтобы избежать специальной обработки начального и конечного слова в строке, удобно считать разделителем не только пробел, но и символы с кодами 13 и 10 (составляющие маркер конца строки EOLN); проверять, является ли символ подобным разделителем, проще всего с помощью метода IsWhiteSpace. Алгоритм нахождения минимумов/максимумов приведен в решении Minmax1; для определения длины слов используйте свойство Length класса string.

Text31–33. По поводу выделения слов из файла см. указание к Text29–30. В данном случае разделителями считаются не только пробельные символы, но и все знаки препинания, поэтому для проверки того, что символ является разделителем, надо использовать не только метод IsWhiteSpace, но и метод IsPunctuation. По поводу преобразования прописных русских букв в строчные и строчных в прописные см. указание к String16–18.

Text34–37. Используйте вспомогательный текстовый файл. Для определения длины строки используйте свойство Length класса string. В Text34–35 для вставки в начало строки пробелов используйте метод PadLeft, в Text36 для удаления половины начальных пробелов используйте метод Remove, в Text37 для вставки пробелов в середину строки используйте метод Insert.

Text38–39. Можно создать вспомогательный файл, в котором каждый абзац из исходного файла будет располагаться в одной строке, после чего организовать посимвольное считывание данных из вспомогательного файла (выделяя из него слова и определяя, можно ли дописать очередное слово в конец текущей строки результирующего файла или необходимо перейти на новую строку). Более эффективным является решение, не использующее вспомогательный файл.

Text40–41. При формировании текстовых файлов с числовой информацией удобно организовать непосредственную запись данных из числовых переменных в результирующий файл, указывая формат выводимых данных с помощью вспомогательной форматной строки. Например, в Text40 запись в текстовый поток w типа StreamWriter отформатированной строки, содержащей целые числа a и b и снабженной начальным и конечным разделителем '|', будет осуществляться оператором

    w.WriteLine("|{0, 30}{1, 30}|", a, b);

Здесь форматная строка "|{0, 30}{1, 30}|" содержит обычные символы (в данном случае разделители '|') и форматные настройки, заключенные в фигурные скобки и содержащие номер форматируемого элемента данных (нумерация ведется от 0) и ширину поля вывода для этого элемента. Форматируемые элементы данных указываются в остальных параметрах метода WriteLine. Для выравнивания данных по левому краю (в Text41) перед шириной поля вывода следует указать знак '-'.

Text42–43. См. указание к Text40–41. При задании форматных настроек для вещественных чисел потребуется дополнительно указать спецификатор формата F и в нем — количество дробных знаков. Например, форматные настройки для аргумента x будут следующими: {0, 10:F4}. Далее, поскольку в задании требуется указывать в качестве десятичных разделителей точки, а не запятые, необходимо воспользоваться вариантом метода WriteLine с первым параметром, явно задающим региональные настройки. Подходящими являются, например, региональные настройки для США: "en-US" (пример использования параметра, задающего региональные настройки, см. в решении задания String19).

Text44–45. Поскольку в классе StreamReader не предусмотрены методы, позволяющие считывать из файла числовые данные, необходимо выполнять построчное считывание данных в строковую переменную (как в первом варианте решения Text4), после чего преобразовывать введенную строку в целое (в Text44) или в вещественное число (в Text45) с помощью метода Parse. При преобразовании строки в вещественное число следует использовать региональные настройки для США (см. решение String19). Для проверки того, что вещественное число x имеет ненулевую дробную часть, достаточно сравнить это число с его округленным значением Math.Round(x).

Text46. См. указание к Text44–45. В этом задании после считывания строки из файла необходимо выделять из нее строковые представления чисел, используя один из алгоритмов выделения слов из строки (см. решение String41).

Text47. См. указание к Text44–45. В данном случае удобно выполнять преобразование введенной строки в целое число в try-блоке; обрабатываться будут только те строки, для которых преобразование в целое число выполнено успешно (то есть без возбуждения исключения). Пример преобразования строки в число в try-блоке см. в решении String19.

Text48. См. указание к Text46 и указание к Text47.

Text49. Используйте вспомогательный текстовый файл; см. также указание к Text40–41. В этом задании при записи числовых данных в текстовый файл не следует указывать ширину поля вывода.

Text50. Организуйте построчное считывание данных их исходного файла. Используя метод Substring, разбивайте каждую строку на две части. Начальная часть (длиной 30 символов) дополняется завершающими пробелами до 80 символов и записывается в строковый файл (по поводу записи данных в строковый файл см. решение задания File63). Оставшаяся часть преобразуется методом Parse в число, которое записывается в файл вещественных чисел. В методе Parse необходимо указать региональные настройки для США (см. решение String19).

Text51–52. Ср. с Text46. Организуйте построчное считывание данных из исходного файла и выделение слов из каждой прочитанной строки. В Text51 выполняйте преобразование каждого слова в вещественное число с помощью метода Parse (используя региональные настройки для США — см. решение String19). В Text52 пропускайте нечетные слова (первое, третье и пятое), поскольку данные слова являются символами-разделителями, и выполняйте преобразование второго, четвертого и шестого слова в целые числа.

Text53. Организуйте посимвольное считывание данных из исходного файла (как во втором варианте решения задания Text4). Для того чтобы проверить, является ли символ знаком препинания, проще всего воспользоваться методом IsPunctuation, определенным для типа char.

Text54–56. Организуйте посимвольное считывание данных из исходного файла (как во втором варианте решения задания Text4). Для того чтобы при формировании результирующего файла избежать дублирования символов, удобно использовать вспомогательный динамический массив a (типа ArrayList), предназначенный для хранения всех символов, обнаруженных в исходном файле. В случае, когда символы требуется расположить в порядке их первого появления в тексте (Text54), для каждого прочитанного из текстового файла символа надо проверять, содержится ли он в массиве a (используя метод Contains), и если этот символ еще не содержится, то записывать его в результирующий файл, а также добавлять в массив a. В случае, когда символы требуется расположить по возрастанию/убыванию их кодов (Text55–56), надо вначале сформировать динамический массив a, записав в него все символы, встретившиеся в исходном файле (без повторений), а затем отсортировать массив (методом Sort) и записать его элементы в файл, перебирая их по возрастанию (Text55) или убыванию (Text56) индексов.

Text57–58. Организуйте посимвольное считывание данных из исходного файла (как во втором варианте решения задания Text4). Для хранения информации о количестве появлений русских букв удобно воспользоваться вспомогательным целочисленным массивом a из 32 элементов, в котором каждый элемент соответствует одной из строчных русских букв: a[0] — букве «а», a[1] — «б», …, a[31] — «я» (буква «ё», как и в прочих заданиях, не учитывается). Таким образом, строчной русской букве c будет соответствовать элемент массива a с индексом (int)c – (int)'a'. Массив a заполняется при считывании символов исходного файла таким образом, чтобы после завершения считывания данных в каждом элементе массива содержалось число появлений в файле буквы, соответствующей этому элементу (например, в элементе a[0] должно содержаться число появлений в файле буквы «а»). Теперь, если строки результирующего файла требуется упорядочить по возрастанию кодов букв (Text57), достаточно последовательно просмотреть все элементы массива a, записывая в результирующий файл строки, соответствующие ненулевым элементам этого массива. Если же требуется упорядочить строки по убыванию числа появлений букв (Text58), то необходимо просматривать массив несколько раз. При каждом просмотре надо найти элемент с наибольшим значением, записать информацию о нем в результирующий файл и после этого обнулить данный элемент (просмотр элементов массива осуществляется до тех пор, пока в нем остаются ненулевые элементы).

Text59. Ср. с String63. Организуйте посимвольное считывание данных из исходного файла (как во втором варианте решения задания Text4). При шифровании символов используйте выражения, аналогичные приведенным в указании к String3–5. Для нахождения величины смещения, задаваемой символом строки s с индексом k, достаточно использовать выражение (int)s[k] – (int)'0'.

Text60. Задание можно решить аналогично Text59, если предварительно найти ключ S (для этого достаточно сравнить символы исходной строки с символами первой строки исходного файла).


Модуль 5. Текстовые файлы

Справочные сведения

Текстовые файловые потоки: классы StreamReader и StreamWriter

Для работы с текстовыми файлами предусмотрены два основных класса: текстовый поток-оболочка для чтения StreamReader и текстовый поток-оболочка для записи StreamWriter. Эти классы определены в пространстве имен System.IO.

Создание текстовых потоков

В отличие от двоичных потоков-оболочек (BinaryReader и BinaryWriter) при создании текстовых потоков не требуется явно создавать «базовый» файловый поток типа FileStream; достаточно лишь указать имя требуемого текстового файла. Заметим, что базовый файловый поток все же создается, и к нему можно получить доступ с помощью свойства BaseStream, однако при работе с текстовыми потоками данное свойство, как правило, не используется. Имеется также возможность связать текстовый поток с уже существующим файловым потоком (типа FileStream); для этого предусмотрены соответствующие конструкторы. В настоящем пособии эти конструкторы (как и некоторые другие конструкторы со специальными возможностями) не рассматриваются.

StreamReader(string name[, Encoding encoding]);
StreamWriter(string name[, bool append[, Encoding encoding]]);

Создает объект-поток указанного типа и связывает его с файлом с именем name (дополнительные сведения о параметре name приводятся в описании конструктора FileStream).

В случае потока для чтения StreamReader указанный файл должен существовать (иначе возбуждается исключение FileNotFoundException). Файл открывается на чтение, и файловый указатель устанавливается на начало файла.

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

Если указан параметр encoding, то он определяет формат кодирования файловых данных; при отсутствии этого параметра используется формат UTF-8. Для установки формата кодирования, который соответствует ANSI-кодировке, используемой системой Windows по умолчанию, в качестве параметра encoding следует указать Encoding.Default.

Совместный доступ к файлу из нескольких текстовых потоков возможен только в случае, если все эти потоки являются потоками типа StreamReader.

Текстовые потоки можно также создать с помощью перечисленных ниже классовых методов класса File.

static StreamReader OpenText(string name);
static StreamWriter CreateText(string name);
static StreamWriter AppendText(string name);

Действие метода OpenText аналогично действию конструктора класса StreamReader с единственным параметром name; действие методов CreateText и AppendText аналогично действию конструктора класса StreamWriter с первым параметром name и вторым параметром, равным false и true соответственно. К сожалению, в данных методах не предусмотрен параметр, определяющий формат кодирования; при создании потока для него всегда устанавливается формат UTF-8.

Закрытие текстовых потоков. Пустые текстовые потоки

void Close();

Закрывает текстовый поток this (при этом закрывается и базовый поток BaseStream, связанный с потоком this) и освобождает неуправляемые ресурсы, выделенные для работы с данным потоком. Повторное выполнение метода Close игнорируется, не возбуждая исключения.

Перед закрытием потока StreamWriter выполняется его метод Flush, записывающий в файл текст, оставшийся в файловом буфере. Заметим, что класс StreamReader не имеет метода Flush.

Для текстовых потоков, как и для двоичных, следует всегда вызывать метод Close после завершения работы с ними.

static readonly StreamReader Null;
static readonly StreamWriter Null;

Данные классовые поля позволяют использовать в программе «пустые» текстовые потоки для чтения и записи (пустой поток StreamReader.Null обрабатывается как файл с пустым содержимым; текст, записываемый в пустой поток StreamWriter.Null, нигде не сохраняется). Эта возможность оказывается полезной при отладке, а также при реализации «молчаливых» режимов работы программы.

Чтение данных из текстового потока

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

int Read();
int Read(char[] chars, int start, int count);

Данные методы работают аналогично методам Read класса BinaryReader с теми же параметрами.

string ReadLine();

Считывает и возвращает очередную строку из текстового потока this. Признаком конца строки считается конец файла или наличие одного из двух вариантов маркеров конца строки: символ с кодом 10 ('\n') или пара символов с кодами 13 и 10 ('\r', '\n'); маркер конца строки в возвращаемую строку не включается. Если данный метод вызывается после достижения конца файла, то он возвращает значение null.

string ReadToEnd();

Считывает все оставшиеся символы из текстового потока this (начиная с текущего символа) и возвращает их в виде одной строки, содержащей как «обычные символы», так и маркеры конца строк (маркер конца файла в возвращаемую строку не включается). Если данный метод вызывается после достижения конца файла, то он возвращает пустую строку "".

При организации считывания данных из текстовых файлов могут оказаться полезными следующие метод и свойство класса StreamReader.

int Peek();

Данный метод работает так же, как метод PeekChar класса BinaryReader.

bool EndOfStream { get; }

Возвращает true, если достигнут конец файла, и false в противном случае. Это свойство доступно в .NET Framework, начиная с версии 2.0.

Запись данных в текстовый поток

Для записи данных в классе StreamWriter предусмотрены методы Write и WriteLine, которые перегружены для различных типов записываемых данных, в том числе для всех элементарных типов, включая класс object. Ниже приводятся только те варианты метода Write, которые связаны с элементарными типами, используемыми в настоящем пособии.

В любом из приведенных методов записи данные добавляются в конец файла.

Текст числовой_тип, указанный при описании параметра value, обозначает любой из рассматриваемых в пособии числовых типов.

При работе с символьными данными (типа char и string) следует учитывать, что при их записи в файл будет выполняться кодирование, формат которого определяется при создании текстового потока StreamWriter.

void Write(object value);
void Write(bool value);
void Write(числовой_тип value);
void Write(char value);
void Write(string value);

Каждый из методов данной группы записывает в поток текстовое представление параметра value (для получения текстового представления вызывается метод ToString указанного параметра; в частности, для символьных и строковых параметров в файл записываются сами эти параметры). Если параметр равен null, то для него метод ToString не вызывается и в файл ничего не записывается.

void Write(char[] array[, int start, int count]);

Оба варианта данного метода работают аналогично методам Write класса BinaryWriter с теми же параметрами, за исключением того, что в случае единственного параметра array, равного null, исключение не возбуждается и в файл ничего не записывается.

void Write(string fmt, params object[] args);

Данный метод обеспечивает форматированный вывод данных, использующий форматную строку fmt. Фактически он вызывает метод Format класса string с указанными параметрами, после чего записывает строку, возвращенную этим методом, в текстовый поток.

Для любого из перечисленных выше методов Write имеется «парный» к нему метод WriteLine с тем же набором параметров. Метод WriteLine записывает в текстовый поток те же данные, что и соответствующий ему метод Write, после чего дописывает в поток маркер конца строки. Имеется также вариант метода WriteLine без параметров, который записывает в файл только маркер конца строки:

void WriteLine();

Используемый во всех вариантах метода WriteLine маркер конца строки берется из свойства NewLine класса StreamWriter:

string NewLine { get; set; }

По умолчанию это свойство возвращает строку из двух символов с кодами 13 и 10 ("\r\n"). Значение свойства NewLine можно изменять (например, на строку "\n" или "\r\n\r\n"), но обычно необходимости в этом не возникает.

Стандартный текстовый поток для ввода-вывода: класс Console

В любых консольных приложениях платформы .NET доступен особый текстовый поток для ввода-вывода: консольное окно. Для управления этим окном предназначен класс Console, определенный в пространстве имен System. В оконных приложениях .NET консольное окно по умолчанию не создается, однако попытки ввода-вывода данных с использованием класса Console не приводят к возбуждению исключений, поскольку в этой ситуации класс Console связывается с «пустыми» текстовыми потоками для ввода и вывода. Для возможности использования консольного окна в оконных приложениях необходимо указать в качестве типа приложения вариант «Console Application».

В .NET Framework версии 2.0 класс Console был дополнен большим количеством свойств и методов, предназначенных для гибкого управления внешним видом консольного окна и обеспечения дополнительных возможностей при консольном вводе данных. В настоящем пособии эти свойства и методы не рассматриваются.

Объект типа Console создавать не требуется; все действия с консольным окном выполняются с помощью классовых свойств и методов класса Console.

static TextReader In { get; }
static TextWriter Out { get; }
static TextWriter Error { get; }

Каждое из данных свойств обеспечивает доступ к соответствующему текстовому потоку, связанному с консольным окном: In — стандартный поток для ввода, Out — стандартный поток для вывода, Error — поток для вывода сообщений об ошибках. Данные свойства предназначены только для чтения; в качестве их типа указаны классы TextReader и TextWriter — базовые предки любых текстовых потоков ввода-вывода (в том числе файловых). Заметим, что классы TextReader и TextWriter имеют те же методы для ввода и вывода, что и описанные ранее классы StreamReader и StreamWriter.

В стандартных консольных потоках используется формат кодирования, принятый по умолчанию для консольных окон в текущей версии Windows; в частности, в русской версии Windows используется кодовая страница 866 «Cyrillic (DOS)» ANSI-кодировки.

static void SetIn(TextReader newIn);
static void SetOut(TextWriter newOut);
static void SetError(TextWriter newError);

Данные методы предназначены для перенаправления стандартных консольных потоков ввода-вывода; в качестве их новых значений можно указать, например, текстовые файлы или пустые потоки.

static int Read();
static string ReadLine();

Данные методы предназначены для ввода символов и строк из стандартного потока ввода In. Они работают аналогично соответствующим методам класса StreamReader, однако следует учитывать, что текст, набранный в консольном окне, передается программе (и, следовательно, обрабатывается методами ввода) только после нажатия клавиши [Enter]. Если же поток ввода пуст, то выполнение программы приостанавливается до тех пор, пока с клавиатуры не будут введены данные и не будет нажата клавиша [Enter].

Следует также иметь в виду, что нажатие клавиши [Enter] записывает во входной поток два символа с кодами 13 и 10, которые могут считываться методом Read как обычные символы.

static void Write(object value);
static void Write(bool value);
static void Write(числовой_тип value);
static void Write(char value);
static void Write(string value);
static void Write(char[] array[, int start, int count]);
static void Write(string fmt, params object[] args);
Эти методы предназначены для вывода данных различных типов в стандартный поток вывода Out. Кроме того, у класса Console имеются «парные» к Write методы WriteLine с тем же набором параметров и метод WriteLine без параметров:
static void WriteLine();

Все эти методы работают аналогично одноименным методам класса StreamWriter, поскольку вызов любого варианта классового метода Write или WriteLine класса Console приводит к вызову соответствующего варианта одноименного экземплярного метода для объекта Console.Out.

Для вывода данных в поток сообщений об ошибках Error специальных методов не предусмотрено, однако для этих целей можно использовать соответствующие методы потока Error, например, Console.Error.WriteLine().

Вспомогательные классы для работы с файлами

Класс File

Класс File, также определенный в пространстве имен System.IO, предназначен для обработки файлов как элементов файловой системы, без доступа к их содержимому. Он содержит только классовые методы; при этом первым параметром любого метода является имя name обрабатываемого файла.

В разделах, посвященных файловым потокам, уже рассматривались методы класса File, позволяющие открыть файл с нужным именем: Create, OpenRead, OpenWrite, Open и OpenText, CreateText, AppendText. В данном разделе описываются другие часто используемые методы класса File. Для всех этих методов в качестве параметра name можно указывать как абсолютные, так и относительные имена файлов; в последнем случае файлы ищутся в текущем каталоге (то есть в рабочем каталоге приложения).

static void Copy(string name, string newName[, bool overwrite]);

Создает копию файла name с именем newName. Файл name и путь, указанный в имени newName, должны существовать; имя newName не должно быть именем существующего каталога; если параметр overwrite не указан или равен false, то имя newName не может быть именем существующего файла (при нарушении любого из этих условий возбуждается исключение). Если параметр overwrite равен true и файл newName существует, то его содержимое заменяется на содержимое файла name (если же файл newName закрыт на запись, то возбуждается исключение).

static void Move(string name, string newName);

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

static void Delete(string name);

Удаляет файл с именем name. Если файл не существует, то метод не выполняет никаких действий. Если в имени файла указан несуществующий диск и/или каталог или если файл существует, но удален быть не может (например, если он в данный момент используется другим приложением), а также если в качестве name указано имя существующего каталога, то возбуждается исключение.

static bool Exists(string name);

Возвращает true, если файл с именем name существует, и false в противном случае. В частности, метод возвращает false, если параметр name равен null, является пустой строкой или является именем каталога (пусть даже и существующего). Данный метод никогда не возбуждает исключения.

Класс Path

Класс Path предназначен для манипулирования именами файлов. Он не требует создания экземпляров; все его поля и методы являются классовыми.

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

Основная часть методов предназначена для выделения требуемого элемента из имени файла или каталога. Перечислим эти методы, описав для каждого возвращаемое значение (все методы имеют единственный параметр name типа string и возвращают объект типа string):

  • GetPathRoot — имя корневого каталога (вида C:\, C: или \). Особые ситуации: если name равно null, то возвращается null; если name не содержит имени корневого каталога в какой-либо из указанных выше форм, то возвращается пустая строка;
  • GetDirectoryName — путь к файлу, включающий имя диска, но не содержащий завершающий символ \ (если name завершается символом \, то возвращается строка name без завершающего символа \). Особые ситуации: если name представляет собой имя корневого каталога вида C:\, C: или \, то возвращается null; если имя файла name не содержит имени диска и пути, то возвращается пустая строка; если файл находится в корневом каталоге (например, C:\data или \data), то возвращаемая строка содержит завершающий символ \ (для приведенных примеров будет возвращено "C:\" или "\" соответственно); если параметр name равен null, является пустой строкой или состоит только из пробелов, то возбуждается исключение;
  • GetFileName — имя файла вместе с расширением. Особые ситуации: если name является пустой строкой или оканчивается символом-разделителем для диска или каталога, то возвращается пустая строка; если name равно null, то возвращается null;
  • GetFileNameWithoutExtension — имя файла без расширения, то есть строка, возвращенная методом GetFileName, у которой дополнительно удален последний из имеющихся символов '.' (точка) и все следующие за ним символы;
  • GetExtension — расширение файла, включая предшествующую точку. Особые ситуации: если name не содержит расширения или оканчивается символом '.', то возвращается пустая строка; если name равно null, то возвращается null;
  • GetFullPath — полное имя файла (если name содержит относительное имя, то к нему добавляется имя текущего каталога вместе с именем диска; если name начинается с символа \, то к нему добавляется имя текущего диска). Особые ситуации: если name равно null, является пустой строкой или состоит только из пробелов, то возбуждается исключение.
static bool HasExtension(string name);

Возвращает true, если имя файла name содержит непустое расширение, и false в противном случае.

static bool IsPathRooted(string name);

Возвращает true, если имя файла name содержит корневой каталог (то есть является абсолютным), и false, если имя является относительным.

static string ChangeExtension(string name, string ext);

Возвращает имя name, расширение которого заменено на расширение ext. Параметр ext может либо содержать, либо не содержать начальный символ '.'. Особые ситуации: если параметр name равен null или является пустой строкой, то он возвращается без изменений; если параметр ext равен null, то возвращается строка name без расширения (и без предшествующей расширению точки); если параметр ext равен пустой строке или строке ".", то возвращается строка name с пустым расширением — точкой.

Все вышеперечисленные методы класса Path возбуждают исключение, если имя name содержит недопустимые символы (например, < > | "); при этом символы * и ?, используемые в масках файлов, считаются допустимыми.

В заключение опишем методы класса Path, связанные с созданием временных файлов.

static string GetTempPath();

Возвращает имя системного каталога, предназначенного для хранения временных файлов.

static string GetTempFileName();

Создает пустой файл с уникальным именем и расширением .tmp в системном каталоге, предназначенном для хранения временных файлов, и возвращает полное имя созданного файла.

static string GetRandomFileName();

Возвращает случайную строку, которую можно использовать в качестве имени файла или каталога. Строка состоит из цифр и строчных латинских букв и включает собственно имя из 8 символов и расширение из 3 символов. Данный метод доступен в .NET Framework, начиная с версии 2.0.


Модуль 5. Текстовые файлы

Контрольные вопросы

Для каждого вопроса щелкните мышью на радиокнопке с номером варианта ответа, который, по вашему мнению, является правильным. Во всех фрагментах кода предполагается, что в программе указаны операторы using со всеми требуемыми пространствами имен. После того как вы ответите на все вопросы, проверьте правильность ответов, нажав на кнопку «Проверка».

1. Дан текстовый файл в формате UTF-8, содержащий 10 строк. Каждая строка состоит из 20 символов '*' (звездочка) и завершается маркером конца строки. Каков размер данного файла в байтах?

(1) | 200 | (2) | 210
(3) | 220 | (4) | 420

2. Укажите верное утверждение.

(1) | Текстовый файл можно открыть на дополнение | (2) | Текстовый файл можно открыть одновременно на чтение и запись
(3) | Для текстовых файлов возможен прямой доступ к их элементам-строкам | (4) | После каждой строки в текстовом файле присутствует маркер конца строки

3. Укажите синтаксически ошибочный оператор.

(1) | StreamReader r = new StreamReader("a.dat"); | (2) | StreamReader r = new StreamReader("a.dat", true, Encoding.Default);
(3) | StreamWriter w = new StreamWriter("a.dat"); | (4) | StreamWriter w = new StreamWriter("a.dat", true, Encoding.Default);

4. Укажите ошибочное утверждение.

(1) | С помощью методов класса File можно удалить файл с указанным именем | (2) | С помощью методов класса File можно проверить, существует ли файл с указанным именем
(3) | С помощью методов класса File можно открыть двоичный файл на чтение | (4) | С помощью методов класса File можно открыть текстовый файл на запись в требуемой кодировке

5. Переменные s1 и s2 типа string равны соответственно "abc.txt" и null. Укажите, что произойдет при выполнении следующего оператора:

Console.WriteLine(Path.ChangeExtension(s1, s2));
(1) | Будет возбуждено исключение | (2) | Будет напечатана строка abc.txt
(3) | Будет напечатана строка abc. | (4) | Будет напечатана строка abc

6. Текстовый поток r типа StreamReader связан с файлом, содержащим 20 пустых строк. Укажите, что произойдет при выполнении следующего фрагмента:

int n = 0;
for (int i = 0; i < 50; i++)
    if (r.ReadLine() == "")
        n++;
Console.WriteLine(n);
(1) | Будет возбуждено исключение | (2) | Будет напечатано число 50
(3) | Будет напечатано число 20 | (4) | Будет напечатано число 0


Модуль 5. Текстовые файлы

Итоговое задание

Ввести с клавиатуры строку Name — допустимое имя файла. Используя датчик случайных чисел, создать текстовый файл с именем Name и расширением .txt, содержащий 20 строк, каждая из которых содержит пробелы, цифры от «1» до «5» и строчные (маленькие) латинские буквы от «a» до «e» (длина каждой строки также определяется случайным образом и может лежать в диапазоне от 40 до 70).

Описать вспомогательную функцию TextOutput(name), которая выводит на экран имя текстового файла name и содержимое этого файла, располагая на каждой экранной строке одну строку исходного файла, заключенную в двойные кавычки. После вывода последней строки файла функция выводит в следующей экранной строке текст EOF — признак окончания содержимого файла. Используя функцию TextOutput, вывести на экран содержимое созданного файла.

На основе созданного файла сформировать новый текстовый файл (файл А), который имеет имя, полученное из имени файла Name добавлением в его начало буквы «A», и расширение .txt. Файл A должен содержать все строки исходного файла, которые начинаются с буквы (начальные пробелы в строках не учитываются). Вывести содержимое файла A на экран, используя функцию TextOutput.

Преобразовать текстовый файл А, удалив из каждой его строки начальные и конечные пробелы. Вывести содержимое преобразованного файла A на экран, используя функцию TextOutput.

Кроме того, сформировать еще один текстовый файл (файл B), который имеет имя, полученное из имени файла Name добавлением в его начало буквы «B», и расширение .txt. Каждая строка файла B должна содержать последнее слово из соответствующей строки файла A (без начальных и конечных пробелов) и после него — в скобках — количество слов в соответствующей строке файла A. Вывести содержимое файла B на экран, используя функцию TextOutput.

Приведем образец текста, который должен выводиться программой, выполняющей проектное задание:

Name = qwerty
qwerty.txt:
"134d2b2a2ced2dd33e1ecb3aa21d5cc c 1dabac4ea43d1b5dd535d5ec"
" a3 4 d 2cecb345c e2c 44ae2c32aec352 e244cee5121d "
"4eaa 1e45c1c 1  3d24ddade 5e5b1d212 2c4 1"
"bb3 dc51e2aa254bb a eda35ee d551342eae1c53be2beba51451a"
" e5ec434c43d351cdcaa1b ce24341b1d4da42   31c414144abaeb3ece5 e bc"
" ea53ac5ad524dd2e 5 adb5ba25e 35 2 4ad ee 3d"
"b3 bc  1ca13 dc43aa5e21c4b ea 5acbe31d1e242be15e35a5c3d41221e 5b 5"
"3e4254a35e5d31 b 4 d4d51da  2eaa4a1 d31 1 35b4eed342eb15"
"33d3a a4d4b  cc2 b35 45a11d54bc4aa5e3ca b1c 3ed5beeed"
"4dd21411e2b2443ce212cea43233c4ccaa2a  a1"
"a1a3 542debda2434de1c3a5eac1bbdb 32d 13bd d4 d 4c3a2be12a144c3a11 d"
" a23a23b414 a4331be 2e2dbeea5b3  c31 3b4b4c5b3"
"3e3 4 de 44ab3b2cc4 c 22cea3d2c141aa1ad 2d3cbb 1 1 b33 3c135551d"
"225c 11d4cec45 e43b1 c 13de34abc524554e ece5 a3a25 24b5514"
"21c5d11321a5 3b23da c531e544cbe3 ebd1313d315a  db52aa"
"bbcb53bc55124 bc eb2143c5d55b253cb2 15dca1cb44be2e53"
"3324e1ad212 d4bdab51da bb41252cc1454ea142e4e5b5ee1a2 2 c3ab5e43e45d4"
"3d a dd1 1ed234a5ce4c14da4cb4a d1ea3abe31a24cac d4dcd 2251eb12c411c2c"
"edcb32de1eacc c2e c3d5b32ddc3ada3c4ee2c 45"
"  34bbd2c442425544 ba1 5ebc2c3ce3aa2d51144 2d5ec5c3a3d3eed4eea44a4ed"
EOF
Aqwerty.txt:
" a3 4 d 2cecb345c e2c 44ae2c32aec352 e244cee5121d "
"bb3 dc51e2aa254bb a eda35ee d551342eae1c53be2beba51451a"
" e5ec434c43d351cdcaa1b ce24341b1d4da42   31c414144abaeb3ece5 e bc"
" ea53ac5ad524dd2e 5 adb5ba25e 35 2 4ad ee 3d"
"b3 bc  1ca13 dc43aa5e21c4b ea 5acbe31d1e242be15e35a5c3d41221e 5b 5"
"a1a3 542debda2434de1c3a5eac1bbdb 32d 13bd d4 d 4c3a2be12a144c3a11 d"
" a23a23b414 a4331be 2e2dbeea5b3  c31 3b4b4c5b3"
"bbcb53bc55124 bc eb2143c5d55b253cb2 15dca1cb44be2e53"
"edcb32de1eacc c2e c3d5b32ddc3ada3c4ee2c 45"
EOF
Aqwerty.txt:
"a3 4 d 2cecb345c e2c 44ae2c32aec352 e244cee5121d"
"bb3 dc51e2aa254bb a eda35ee d551342eae1c53be2beba51451a"
"e5ec434c43d351cdcaa1b ce24341b1d4da42   31c414144abaeb3ece5 e bc"
"ea53ac5ad524dd2e 5 adb5ba25e 35 2 4ad ee 3d"
"b3 bc  1ca13 dc43aa5e21c4b ea 5acbe31d1e242be15e35a5c3d41221e 5b 5"
"a1a3 542debda2434de1c3a5eac1bbdb 32d 13bd d4 d 4c3a2be12a144c3a11 d"
"a23a23b414 a4331be 2e2dbeea5b3  c31 3b4b4c5b3"
"bbcb53bc55124 bc eb2143c5d55b253cb2 15dca1cb44be2e53"
"edcb32de1eacc c2e c3d5b32ddc3ada3c4ee2c 45"
EOF
Bqwerty.txt:
"e244cee5121d(7)"
"d551342eae1c53be2beba51451a(5)"
"bc(5)"
"3d(8)"
"5(8)"
"d(8)"
"3b4b4c5b3(5)"
"15dca1cb44be2e53(4)"
"45(4)"
EOF


Приложение. Вспомогательные методы
задачника Programming Taskbook

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

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

Класс PT предоставляет средства для инициализации требуемого задания и организации ввода-вывода данных. В программах на языке C# решение задания оформляется в виде функции Solve, в которой, как правило, и вызываются методы класса PT, обеспечивающие инициализацию задания и ввод-вывод данных. В программе не требуется создавать объекты класса PT, поскольку все его методы являются классовыми.

Опишем особенности, связанные с различными методами класса PT.

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

Если метод Task вызывается в функции Solve несколько раз, то все последующие его вызовы игнорируются.

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

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

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

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

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

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

Благодаря использованию параметра-массива, снабженного атрибутом params, при вызове метода Put можно указывать произвольное число параметров. Параметры могут иметь тип bool, int, long, double, char, string (приведены только те типы, которые могут потребоваться при выполнении заданий из настоящего пособия). В качестве параметров метода Put можно указывать не только переменные, но и выражения (в частности, константы соответствующего типа. Если в списке параметров указываются параметры недопустимого типа, то при выполнении программы выводится сообщение об ошибке «В методе Put указан параметр недопустимого типа».

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

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


Литература

  1. Ахо А. В., Хопкрофт Дж. Э., Ульман Дж. Д. Структуры данных и алгоритмы. — М.: «Вильямс», 2000. — 384 с.
  2. Балена Ф., Димауро Дж. Современная практика программирования на Microsoft Visual Basic и Visual C#. — М.: «Русская редакция», 2006. — 640 с.
  3. Вирт Н. Алгоритмы и структуры данных. М.: Мир, 1989. 360 с.
  4. Зубов В. С. Справочник программиста. Базовые методы решения графовых задач и сортировки. — М.: «Филинъ», 1999. — 256 с.
  5. Майо Дж. C#: Искусство программирования. Энциклопедия программиста. — СПб.: «ДиаСофтЮП», 2002. — 656 с.
  6. Рихтер Дж. Программирование на платформе Microsoft .NET Framework. Мастер-класс. — 3-е изд. — М.: «Русская Редакция»; СПб.: Питер, 2005. — 512 с.
  7. Шилдт Г. Полный справочник по C#. — М.: «Вильямс», 2004. 752 с.