# Основы языка C++ ## Цель работы 1. Владеть базовыми конструкциями и типами языка C++. 2. Уметь работать в среде программирования CodeBlocks. 3. Уметь автоматически проверять программы по эталонному вводу и выводу. ## Форма отчета Все результаты сдаются в электронном виде. Спецификации, блок-схемы, кода в DOCX и т. п. не нужно. 1. Проект с кодом, решающим общее задание. 2. Файлы эталонного ввода/вывода (даны) и BAT-файл для проверки пункта 1. 3. Проект с кодом, решающим задачу индивидуального варианта (на основе общего). 2. Файлы эталонного ввода/вывода и BAT-файл для проверки пункта 3. ## Задание 1. Написать программу для построения гистограммы массива чисел. 2. Доработать программу в соответствии с вариантом. Гистограмма — это наглядное графическое представление того, какие значения встречаются чаще или реже в исходных данных (как они распределены). Диапазон исходных данных делится на равные интервалы, для каждого интервала строится столбец. Высоты столбцов пропорциональны количеству значений, попавших в интервал. Таким образом сразу видно, какие значения встречаются чаще в целом (с точностью до ширины интервала) и насколько чаще по сравнению с другими (легко сравнить высоты визуально). Гистограмма строится так: диапазон значений на входе делится на несколько равных интервалов (корзин, bins), подсчитывается количество  чисел, попавших в каждый интервал, и для каждой корзины рисуется столбец, размер которого пропорционален количеству попавших в корзину чисел. Например, на вход поступают оценки 10 студентов: ``` text 4 4 3 5 3 4 5 5 4 4 ``` Пусть требуется построить гистограмму на три столбца. Диапазон значений на входе — от 3 до 5. Каждый из трех интервалов будет шириной *(5 − 3) / 3 = 0,67,* то есть интервалы будут *[3; 3.67], [3.67; 4.34], [4.34; 5].* В первую корзину попадут тройки (2 шт.), во вторую — четверки (5 шт.), в третью — пятерки (3 шт.). Результат: ``` text 2|** 5|***** 3|*** ``` В данном случае в каждый столбец попадает только одно значение (3, 4 или 5), но в принципе, в один столбец могут попадать разные значения. Например, если для тех же оценок использовать два интервала, 4 и 5 попадут в один интервал: ``` text 2|** 8|******** ``` **Требования:** * Числа в исходном массиве могут быть дробными. * Подписи к столбцам выровнены до трех знакомест (можно считать, что в корзину больше 999 чисел не попадет). * Ширина всей гистограммы (подписи и звездочек в каждом столбце) должна  укладываться в 80 символов. Если в корзину попало больше чисел, все столбцы нужно пропорционально сжать, чтобы выполнить условие. ## Указания к выполнению работы Алгоритм работы программы логично разделить на три этапа: 1. Ввод данных (считывание массива чисел и количества столбцов). 2. Обработка данных (расчет количества чисел, попавших в каждую корзину). 3. Вывод данных (отображение рассчитанных значений в виде гистограммы). Ввод данных составляет: 1. Ввод количества значений (целого числа). 2. Ввод значений (цикл, заполняющий массив). 2. Ввод количества корзин (целого числа). Обработка заключается в том, чтобы массив чисел преобразовать в массив их количеств в каждой корзине. Для этого нужно рассмотреть каждое число, узнать номер корзины, в которую оно попадает, и увеличить счетчик чисел для этой корзины. Вывод данных имеет смысл выполнить в несколько подходов: сначала элементарно, чтобы убедиться в правильности обработки, затем реализовать требования к отображению. 1. Отображать для каждой корзины количество чисел в ней, ось и такое же количество звездочек, сколько чисел в корзине. 2. Выровнять подписи до трех знакомест. 3. Масштабировать высоты столбцов, чтобы уложиться в 80 символов. ### Работа с CodeBlocks Писать код в онлайн-редакторах запрещается. Можно работать на своем ноутбуке. Запуск виртуальной машины: ярлык *VMWare Horizon Client* на рабочем столе → двойной щелчок по иконке *Win7 среды программирования* (не просто *Среды программирования*!). #### Создание проекта Из меню: *File → New → Project... → Console Application*. *Skip this page next time*: `☑`. *Please select the language you want to use*: `C++`. Настройки проекта: * *Project title:* `lab01` * *Folder to create project in:* `L:\I курс\<группа>\<ФИО>\РПОСУ` (создать нужные каталоги, если их нет). * *Project filename* и *Resulting filename* заполнятся автоматически. *Compiler*: `GNU GCC Compiler` (самый верхний). #### Работа с кодом На панели слева двойными щелчками раскрыть `lab01` и `Source`, двойным щелчком открыть `main.cpp`. Сборка и запуск проекта делаются кнопкой `F9`, из меню *Project → Build and run* или кнопкой на панели инструментов. На панели *Build messages* внизу показываются ошибки и предупреждения. ### Ввод данных Входные данные (переменные): количество чисел, числа, количество корзин. В C++ есть специальный тип `size_t`, подходящий для размеров массивов: целое неотрицательное число с широким диапазоном значений. Целесообразно  использовать его для количества чисел: ``` cpp size_t number_count; ``` Имя переменной сообщает, что в ней хранится: number count по-английски  «количество чисел». Всем переменным нужно давать осмысленные имена  (исключения: счетчики циклов `i`, `j`, ... и математические формулы). Программы чаще читаются, чем пишутся — их понятность важнее размера. Скорости набора текста помогает редактор, подсказывающий имена по мере ввода. Ввод стандартный: ``` cpp cout << "Enter number count: "; cin >> number_count; ``` Отображение кириллицы в консоли Windows требует ухищрений. Применять их не нужно, во всех ЛР достаточно англоязычного вывода. Массив значений состоит из действительных чисел, выберем для них тип `double`. Размер массива определяется во время работы программы переменной `number_count`, то есть это динамический массив. Его можно реализовать через `new[]`/`delete[]`, но в современном C++ принято использовать вектор, в данном случае — `vector`. Для использования `vector` нужно подключить часть стандартной библиотеки: ``` cpp #include ``` При объявлении переменной `numbers` (числа) типа `vector` можно сразу указать размер: ``` cpp vector numbers(number_count); ``` Очевидно, это нужно делать после ввода `number_count`. Если бы `numbers` была объявлена до этого, ей не нужно было бы передавать аргумент в скобках; вместо этого после ввода `number_count` нужно было бы изменить размер: ``` cpp numbers.resize(number_count); ``` Однако безопаснее объявлять переменные как можно ближе к месту первого использования: меньше риск случайно обратиться к ним до инициализации. Например, если объявить `numbers` в начале программы, ничто не помешает обратиться к его элементам до вызова `resize()`, что приведет к ошибке. Числа вводятся в `numbers` стандартным циклом `for` со счетчиком. Код нужно написать самостоятельно. Количество корзин `bin_count` целесообразно сделать того же типа, что и `number_count` и вводить так же. ### Обработка данных Необходимо для каждой корзины подсчитать количество попавших в нее чисел, то есть заполнить массив счетчиков. Тип счетчика — `size_t`, потому что  это по сути такое же количество, как количество чисел. Их массив имеет размер `bin_count`, а начальные значения в нем — нули: ``` cpp vector bins(bin_count); ``` Как по значению элемента определить номер корзины, куда он попадает? #### Определение индекса корзины по значению элемента  Нужно подсчитать, сколько чисел попало в каждую корзину. Для этого можно определить номер корзины, в которую попадает каждое число, и увеличить счетчик этой корзины. Каждая корзина представляет числа в одном из интервалов, на которые равномерно разбит диапазон исходных чисел. Например, для чисел `4 5 3 4 5 4 3 4 5 4` и трех корзин: ``` 4 4 4 5 3 4 5 3 4 5 |-----|-----|-----| 3.00 3.67 4.34 5.00 ``` Нижняя граница будет равна минимальному из чисел (обозначим его `min`), верхняя — максимальному `max`. Каждая следующая граница отстоит от предыдущей на размер корзины `bin_size`: ``` cpp double bin_size = (max - min) / bin_count; ``` В общем случае: ``` |------------|------------|------------|------------| min min+1*step min+2*step min+3*step max : : :<---------->: bin_size ``` Пусть `min` и `max` найдены, `bin_size` рассчитан по формуле выше. Остается для каждого `i`-го числа проверить каждую `j`-ю корзину. Если число попадает между границ этой корзины, то счетчик попавших в корзину чисел увеличивается. Прочие корзины можно уже не просматривать. Пример 1. При *i = 0* рассматривается число 4 из списка выше. При *j = 0* рассматривается интервал *[3.00; 3.67),* в который 4 не входит. При *j = 1* рассматривается интервал *[3.67; 4.34),* куда 4 попадает. Следовательно, счетчик `bins[1]` нужно увеличить, а цикл по `j` можно прекратить. Пример 2. При *i = 1* рассматривается число 5. Оно не входит ни в один из интервалов, даже *[4.34; 5.00),* потому что правая граница не учитывается. Этот особый случай нужно опознать и увеличить счетчик последней корзины. ``` cpp for (size_t i = 0; i < number_count; i++) { bool found = false; for (size_t j = 0; (j < bin_count - 1) && !found; j++) { auto lo = min + j * bin_size; auto hi = min + (j + 1) * bin_size; if ((lo <= numbers[i]) && (numbers[i] < hi)) { bins[j]++; found = true; } } // цикл по numbers не закончился! ``` Особый случай `number == max` из примера 2 можно опознать по `found == false` после цикла: ``` cpp if (!found) { bins[bin_count - 1]++; } } // конец цикла по numbers ``` Отметим оформление кода: * вокруг операторов (`=`, `<`, `>`), после ключевых слов (`for`, `if`) и перед фигурными скобками (`{`) стоят пробелы; * блоки кода (тело цикла, инструкции под условиями) выделены отступами. Нижняя и верхняя границы интервала обозначены `lo` (low) и `hi` (high), это типовые имена переменных, как `i`, `j` для счетчиков. Как и имена переменных, форматирование кода (code style) помогает понимать смысл программы (ее структуру) и страхует от ошибок. Чтобы автоматически  отформатировать код в CodeBlocks, нужно щелкнуть по тексту правой кнопкой мыши и выбрать *Format (use AStyle)* (если выделить фрагмент, отформатирован будет только он). #### Определение диапазона чисел в массиве Для расчета индексов столбцов нужно найти наибольший и наименьший элементы в массиве. Разберем эту простую задачу подробно, чтобы изучить диапазонный цикл `for` (range-based for loop). Формулировка решения: *для каждого* массива  чисел сравнить его с максимумом и минимумом, при необходимости скорректировать максимум или минимум. Заметим, что решение не оперирует индексами  в массиве — только значением очередного элемента. Для выражение этой идеи  в C++ есть диапазонный цикл `for`: ``` cpp double min = numbers[0]; double max = numbers[0]; for (double x : numbers) { if (x < min) { min = x; } else if (x > max) { max = x; } } ``` Запись `for (double x : numbers) { ... }` означает: выполнить тело цикла  *для каждого* элемента `x` (типа `double`) из массива `numbers`, то есть в точности соответствует логике решения. ### Вывод данных. Этап 1: минимальный работающий вариант Для каждого элемента `bin` массива `bins` нужно вывести значение `bin`, символ `|` и *bin* звездочек (внутренним циклом со счетчиком); после звездочек нужен перевод строки. Код нужно написать самостоятельно. На этом этапе можно проверить работу программы: ввести данные из примера  (десять чисел, три корзины) и визуально сопоставить гистограммы. На последующих этапах нужно будет проверять работу программы на десятках чисел, вводить которые долго и чревато ошибками. Можно записать числа в файл и вставлять в консоль, но сравнивать результаты (считать звездочки) не легче. Проблема усугубляется, если нужно проводить много тестов. ### Автоматическая проверка по эталонному вводу и выводу Можно полностью автоматически вводить данные в программу, сохранять ее вывод и сравнивать с эталоном, получая простой ответ: пройден ли тест. Эталонный ввод при этом читается из файла, вывод записывается в файл, который затем сравнивается с файлом эталонного вывода. При этом не требуется добавлять в программу работу с файлами и логику проверки, если знать, как устроен ввод и вывод, и уметь пользоваться стандартными утилитами. Дальнейшая работа ведется в консоли из каталога с файлом `*.exe`, в CodeBlocks это может быть `bin\Debug`. #### Командная строка Windows Командная строка (терминал) запускается через *Win+R*, `cmd` или путем ввода `cmd` в строку адреса в «Проводнике» и нажатия *Enter.* Текст `C:\>` слева от курсора называется *приглашением (prompt).* Приглашение показывает текущий каталог — корень диска C. Перейти в другой каталог можно командой `cd`, например, `cd lab01`. Если нужно перейти на другой диск, добавляется *ключ (опция)* `/d`, например: `cd /d L:\A-01-18\username`. Чтобы не вводить путь вручную, можно нажимать *Tab* после ввода первых символов имени каталога, и Windows дополнит путь. Если нужно повторить одну из предыдущих команд, стрелки вверх и вниз проматывают историю. Если нужно отредактировать команду, стрелками можно перемещать курсор влево-вправо. Скопировать и вставить текст в Windows 10 и выше можно `Ctrl+C`/`Ctrl+V`, а в более старых системах нужно вызвать контекстное меню заголовка окна и выбрать там *Изменить... → Копировать.* При затруднениях в работе с консолью можно воспользоваться [руководством][cmd]. [cmd]: http://cmd.readthedocs.io/cmd.html #### Командная строка Unix (Linux, OS X) Терминал обычно запускается через лаунчер поиском по слову `Terminal`. Конкретная программа зависит от операционной системы и дистрибутива Linux. Подойдет любая. Текст вида `<имя пользователя>@<имя машины>:~$` слева от курсора называется *приглашением (prompt).* Приглашение показывает текущий каталог — домашнюю директорию `~`, что является сокращением для `/home/<имя пользователя>`. Перейти в другой каталог можно командой `cd`, например, `cd lab01`. Чтобы не вводить путь вручную, можно нажимать *Tab* после ввода первых символов имени каталога, и Windows дополнит путь. Если нужно повторить одну из предыдущих команд, стрелки вверх и вниз проматывают историю. Если нужно отредактировать команду, стрелками можно перемещать курсор влево-вправо. Скопировать и вставить текст можно сочетаниями `Ctrl+Shitf+C`/`Ctrl+Shift+V` или `Ctrl+Ins`/`Shift+Ins` (для OS X вместо `Ctrl` используется `Cmd`). #### Запуск программ без CodeBlocks Находясь в каталоге с `*.exe`, можно попробовать запустить его: ```cmd C:\lab01\bin\Debug> lab01 ``` В Unix у исполняемых файлов нет расширения `*.exe` и запуск делается иначе: ```sh user@host:~/lab01/bin/Debug$ ./lab01 ``` Однако это может привести к сообщению об ошибке (имя библиотеки может быть иным): ``` Программа не может быть выполнена, потому что библиотека libstd++.dll не найдена. ``` Подробнее тема библиотек будет рассмотрена позже в курсе лекций и ЛР. Чтобы сделать файл `lab01.exe` независимым от внешних библиотек, нужно в CodeBlocks: * Открыть диалог *Settings → Compiler → Compiler settings* и поставить галочки *Static libstdc++ [-static-libstdc++]* и *Static linking [-static]*. * Пересобрать проект *Ctrl+F11* или *Build → Rebuild* или нажать на иконку с голубыми стрелочками. При использовании CMake, например, в CLion, нужно в `CMakeLists.txt` после `executable(xxx ...)` добавить такую строку: ```CMakeLists.txt target_link_options(xxx PRIVATE -static -static-libstdc++) ``` #### Стандартные потоки и их перенаправление Обычно для простоты говорят, что ввод происходит с клавиатуры, а вывод — на  экран. На самом деле ввод происходит из особого устройства — *стандартного ввода (standard input, stdin),* а вывод поступает на устройство *стандартного вывода (standard output, stdout).* По умолчанию стандартный ввод связан с клавиатурой, а вывод — с терминалом (окном консоли в Windows). Однако можно при запуске программы указать, что стандартным вводом для нее будет не клавиатура, а файл *(перенаправление ввода, input redirection).* Windows: ```cmd C:\lab01\bin\Debug> lab01.exe < 01-example.input.txt Enter number count: Enter numbers: Enter bin count: 2|** 5|***** 3|*** ``` Unix: ```sh user@host:~/lb01/bin/Debug$ ./lab01 < 01-example.input.txt ``` **Внимание.** Здесь и далее примеры работы с командной строкой включают приглашение `C:\lab01>`, команду и ее вывод. Приглашение вводить не нужно, это просто стандартный формат записи. Таким образом, нужно ввести `lab01.exe < 01-example.input.txt`, в ответ ожидается текст на второй строке и далее. Готовые файлы, которые нужно скачать или создать: * эталонный ввод [01-example.input.txt](assets/01-example.input.txt) * эталонный вывод [01-example.expected.txt](assets/01-example.expected.txt) Видно, что гистограмма строится правильно, но картину портят приглашения ввода (`Enter number count` и прочие). Аналогично можно направить стандартный вывод в файл. Windows: ```cmd C:\lab01\bin\Debug> lab01.exe < 01-example.input.txt > 01-example.actual.txt ``` Unix: ```sh user@host:~/lab01/bin/Debug$ ./lab01 < 01-example.input.txt > 01-example.actual.txt ``` Вывода на терминал нет — он весь в `01-example.actual.txt`, и если его просмотреть, окажется, что он соответствует предшествующему выводу. Как избавиться от приглашений, которые не нужны в режиме автоматических тестов? Проблема заключается в том, что у программы есть значимый, информативный вывод (собственно гистограмма), а есть декоративный вывод (приглашения). Только самой программе «известно», где какой вывод — без ее модификации не обойтись. Помимо стандартного вывода существует *стандартный вывод ошибок (stderr).* Такое название сложилось исторически, а на практике принято писать в него декоративный вывод. Этот поток доступен в C++ как `cerr`: ``` cpp cerr << "Enter number count: "; ``` Нужно самостоятельно заменит вывод приглашений (но не гистограммы) в `cout` на их вывод в `cerr`. Если теперь запустить программу как обычно (без перенаправления), ее работа  внешне не изменится, потому что стандартный вывод ошибок по умолчанию тоже связан с терминалом. Если же перенаправить вывод в файл, записанное в `cerr` появится в терминале, но не в файле `01-example.actual.txt`: ``` cmd C:\lab01\bin\Debug> lab01.exe < 01-example.input.txt > 01-example.actual.txt Enter number count: Enter numbers: Enter bin count: ``` Чтобы убрать декоративный вывод при автоматических тестах, можно направить его в специальное устройство `NUL`, которое поглощает любой вывод в него. **Внимание.** Здесь между Windows и Unix есть отличие: `2>NUL` vs `2>/dev/null`. Windows: ```cmd C:\lab01\bin\Debug> lab01.exe < 01-example.input.txt > 01-example.actual.txt 2>NUL ``` Unix: ```cmd user@host:~/lab01/bin/Debug> ./lab01 <01-example.input.txt >01-example.actual.txt 2>/dev/null ``` Вывода в терминал при этом нет никакого, хотя программа успешно работает. #### Сравнение файлов (Windows) Программа `fc` (file compare) позволяет построчно сравнить файл вывода  программы `01-example.actual.txt` с файлом `01-example.expected.txt`, содержащим эталонный вывод: ``` cmd C:\lab01\bin\Debug> fc 01-example.actual.txt 01-example.expected.txt Сравнение файлов 01-example.actual.txt и 01-EXAMPLE.EXPECTED.TXT FC: различия не найдены ``` Если бы были отличия, `fc` могла бы показать отличающиеся строки, а с ключом `/N` также и их номера ([справка][fc]): [fc]: http://ab57.ru/cmdlist/fc.html ``` cmd C:\lab01\bin\Debug> fc /N 01-example.actual.txt 02-alignment.expected.txt Сравнение файлов 01-example.actual.txt и 02-ALIGNMENT.EXPECTED.TXT ***** 01-example.actual.txt 1: 2|** 2: 5|***** 3: 3|*** ***** 02-ALIGNMENT.EXPECTED.TXT 1: 2|** 2: 5|***** 3: 3|*** ***** ``` #### Сравнение файлов (Unix) Программа `diff` (difference) позволяет построчно сравнить файл вывода  программы `01-example.actual.txt` с файлом `01-example.expected.txt`, содержащим эталонный вывод: ``` sh user@host:~/lab01/bin/Debug$ diff -u 01-example.actual.txt 01-example.expected.txt ``` Если отличий нет, `diff` не печатает ничего, как в примере выше. Если бы были отличия, `diff` могла бы показать отличающиеся строки и их номера: ``` sh user@host:~/lab01/bin/Debug$ diff -u 01-example.actual.txt 02-alignment.expected.txt --- 01-example.actual.txt 2023-02-06 23:52:25.513825363 +0300 +++ 02-alignment.expected.txt 2023-02-06 23:52:25.514825395 +0300 @@ -1,3 +1,3 @@ - 2|** - 5|***** - 3|*** + 9|********* + 33|********************************* +100|**************************************************************************************************** ``` #### BAT-файлы Чтобы не вводить каждый раз команды вручную, их можно записать в файл с расширением `*.bat` и запускать как программу из командной строки: ``` lab01.exe < 01-example.input.txt > 01-example.actual.txt 2>NUL fc /N 01-example.actual.txt 01-example.expected.txt ``` Чтобы запускать файл из «Проводника» и при ошибках окно не закрывалось, можно к последней строки добавить `|| pause`. ### Вывод данных. Этап 2: выравнивание подписей столбцов  Требуется количество чисел, попавших в каждый столбец, дополнять при выводе пробелами так, чтобы суммарно подпись занимала четыре знакоместа (символа). Хотя в C++ и есть средства форматирования, реализуем выравнивание вручную: 1. Если число меньше 100, вывести пробел (он займет место разряда сотен). 2. Если число меньше 10, вывести пробел (он займет место разряда десятков). 3. Вывести число. Необходимо написать код самостоятельно и автоматически проверить его: * эталонный ввод [02-alignment.input.txt](assets/02-alignment.input.txt); * эталонный вывод [02-alignment.expected.txt](assets/02-alignment.expected.txt). ### Вывод данных. Этап 3: масштабирование столбцов Поскольку ограничена ширина всей гистограммы (включая 3 цифры подписи и ось шириной 1 символ), ограничение на длину столбца будет *80 − 3 − 1 = 76* символов. Если в корзине самым большим количеством чисел не больше 76, масштабирование не нужно. Для масштабирования из количества чисел `count` в каждой корзине нужно  получить ее высоту `height` (количество звездочек). Масштабирование должно  работать так, чтобы самый высокий столбец имел 76 звездочек, следовательно, для него `height = 76 * 1.0`. Для прочих корзин вместо 1,0 должен быть множитель-доля количества чисел в этой корзине от максимального количества: `height = 76 * count / max_count`. Однако напрямую эту формулу записать на C++ нельзя: деление целых чисел `count` и `max_count` даст целое же число. Необходимо указать компилятору, что `count` нужно рассматривать как дробное. Это называется приведением типов (type cast): ``` cpp size_t height = 76 * (static_cast(count) / max_count); ``` Выражение `static_cast(x)` означает: рассматривать выражение `x` как имеющее тип `T`. Можно встретить другую форму записи, так называемое приведение в стиле C (C-style cast): `((double)count)`. Почему в C++ более громоздкий синтаксис? Дело в том, что приведение типов — это место  в программе, где программист берет на себя ответственность, что преобразование имеет смысл, поэтому лучше, когда оно резко выделяется в тексте программы. Заметим, что в данном примере можно было бы обойтись вообще без приведения типов, если сделать `max_count` типа `double`, однако приведение типов  часто встречается на практике — необходимо уметь его делать. Константа 76 используется как минимум в двух местах, что плохо. во-первых, если потребуется поменять ее, придется искать и редактировать все эти места. Во-вторых, при чтении кода будет непонятен ее смысл. По последней причине  такие числа в коде называются магическими константами. Их нужно выносить в неизменяемые переменные с понятными именами или комментариями. Обычно  их размещают в самом начале программы: ``` cpp const size_t SCREEN_WIDTH = 80; const size_t MAX_ASTERISK = SCREEN_WIDTH - 3 - 1; ``` Итак, требуется самостоятельно реализовать: 1. Поиск наибольшего количества чисел в одной корзине (можно совместить в подсчетом чисел в корзинах). 2. Масштабирование столбцов. Результат необходимо автоматически проверить: * эталонный ввод [03-scaling.input.txt](assets/03-scaling.input.txt) * эталонный вывод [03-scaling.expected.txt](assets/03-scaling.expected.txt) ## Варианты индивидуальных заданий Решение должно включать: код программы, файлы эталонного ввода и вывода, BAT-файл для автоматической проверки. ### Вариант 1 Дайте пользователю возможность задавать произвольную ширину гистограммы вместо 80 символов. Ширину менее 7, более 80 или менее трети количества чисел считайте некорректной — предлагайте пользователю ввести ее заново в этом случае с указанием причины. ### Вариант 2 Если пользователь вводит 0 как число столбцов, рассчитывайте число столбцов автоматически по эмпирической формуле *K = √N*, а если получилось *K > 25*, пересчитайте по правилу Стёрджеса: для *N* чисел количество столбцов *K = 1 + ⌊log₂N⌋*. Печатайте, по какой формуле был сделан выбор и сколько столбцов выбрано. **Указание.** См. функции `sqrt()` и `log2` из ``. ### Вариант 3 Дайте пользователю возможность задавать высоту гистограммы *H* строк. Если количество столбцов *K* в *C = ⌊H/K⌋* раз меньше *H*, столбцы должны занимать по *C* строк. **Пример.** Выбрано *H = 6, K = 3 ⇒ C = 2,* гистограмма: 8|******** |******** 11|*********** |*********** 6|****** |****** ### Вариант 4 Вместо количества элементов сделайте подписью столбца процент элементов, попавших в столбец, как целое двузначное число с `%` в конце. ### Вариант 5 Отображайте гистограмму зеркально, например: ********| 8 ***********| 11 ******| 6 ### Вариант 6 Дайте пользователю возможность выбора символов для столбцов «рисунка», линии оси (`|` в примерах) и для выравнивания подписей. Например, при выборе соответственно `|`, пробела и `0`: 008 |||||||| 011 ||||||||||| 006 |||||| Не позволяйте вводить символы табуляции и перевода строк, печатайте любое сообщение со словом «ERROR» и завершайте программу при этом. ### Вариант 7 Вычислите среднюю высоту столбца. Если столбец ниже, доведите его высоту до средней символами `-`. Если столбец выше, выводите часть, превышающую среднюю высоту, символами `+`. Пример (средняя высота — 8 звездочек): 8|******** 11|********+++ 6|******-- ### Вариант 8 После подсчета количеств значений в столбцах, замените их нарастающим итогом, начиная с первого столбца. При отображении соблюдайте те же правила, что и ранее. Пример для исходного графика с высотами 1-3-7-11-6-4-1: 1|* 4|**** 11|*********** 22|********************** 28|**************************** 32|******************************** 33|********************************* ### Вариант 9 В каждом столбце, если предыдущий столбец ниже, вместо `*` используйте `^` на высоте предыдущего столбца. Аналогично для следующего столбца, но `v`. Если соседние столбцы оба ниже текущего и равны, используйте `N`. Пример: 1|* 3|^** 7|**^**** 11|*****v^**** 6|***v** 4|v*** 1|* ### Вариант 10 Отображайте гистограмму вертикально без подписей, например: ******* ***** ***** **** *** *** ** * **Указание.** Можно воспользоваться следующей логикой: проходить по всем столбцам и печатать `*`, если высота столбца больше номера строки, или пробел, если нет — и так до тех пор, пока на очередной строке печатается хотя бы одна звездочка. ### Вариант 11 Добавьте рамку вокруг гистограммы. Добавьте учет линий рамки, чтобы общая ширина «изображения» не превышала 80 символов. Иллюстрация результата: +----------------+ | 8|******** | | 11|*********** | | 6|****** | +----------------+ ### Вариант 12 Добавьте на ось подписей границы столбцов. Например, если в первый столбец отнесены элементы от наименьшего до 1,23, во второй — от 1,23 до 2,34 и т. д., желаемый результат: 8|******** 1.23 11|*********** 2.34 6|****** Ширину места для подписей столбцов нужно увеличить, как на иллюстрации. ### Вариант 13 После вывода гистограммы запрашивайте у пользователя, доволен ли он результатом. Если ответ отрицательный, позвольте ввести новое количество столбцов и перестройте гистограмму. Процесс может повторяться сколько угодно раз. ### Вариант 14 Сделайте подписи к столбцам текстовыми. После ввода количества столбцов *K* пользователь должен ввести *K* строк (возможно, с пробелами), которые будут подписями к соответствующим столбцам. При выводе гистограммы вместо высоты каждого столбца нужно печатать его подпись. Подписи должны быть выровнены по правому краю на ширину самой длинной из них. ### Вариант 15 Добавьте горизонтальную шкалу под гистограммой. Шкалу нужно разбить на интервалы, размер которых от вводит пользователь. Допустимы размеры от 4 до 9, при некорректном вводе печатайте сообщение со словом «ERROR» и завершайте работу программы. Под нулевой, первой и последней отметкой шкалы требуется напечатать соответствующие числа. Шкала должна быть во всю ширину гистограммы. Пример для интервала размером 6: 8|******** 14|************** 12|************ |-----|-----|-----| 0 6 18 ### Вариант 16 Перед построением гистограммы удалите из входного массива все повторяющиеся (не обязательно подряд) элементы и напечатайте результат. **Указание.** Удалить `xs[i]` можно так: `xs.erase(xs.begin() + i)`. ### Вариант 17 После ввода количества чисел предлагайте пользователю генерировать их. При положительном ответе заполните исходный массив при помощи функции `rand()`: каждый элемент должен быть суммой 12 ее результатов. **Указание.** В начале программы добавьте `srand(time(0))`, чтобы случайные числа отличались между запусками программы (аналог `Randomize()` в Pascal). Для составления эталонного вывода замените `time(0)` на 42. ### Вариант 18 Избавьте программу от предположения о наибольшем возможном количестве чисел в столбце. Находите наибольшее и используйте это значение, чтобы выровнять подписи по правому краю, не расходуя при этом лишних знакомест.