Содержание
Задача: во входном файле содержится две строки: первая содержит одно целое число N (количество чисел во второй строке), вторая строка содержит N вещественных чисел (каждое от 0 до 105). В результате работы программы должен быть сформирован выходной файл, который содержит следующие значения: 1) количество чисел, больших среднего арифметического исходных чисел; 2) сумма всех чисел, меньших среднего арифметического; 3) максимальное число. Для инициализации исходной матрицы необходимо использовать программу-генератор.
Теоретическая часть
Для выполнения этой лабораторной работы нам потребуется либо перенаправить потоки ввода-вывода нашего консольного приложения, либо использовать наши знания о работе с файлами в C# (если, конечно, таковые знания имеются). Что касается перенаправления потоков ввода-вывода, то можно изучить эту статью или же воспользоваться первой частью выполнения этой лабораторной работы по C#. Ниже вы найдете решение лабораторной работы с использованием обоих вариантов.
Второй момент касается генерации случайных чисел для составления исходной матрицы и, непосредственно, проведение необходимых расчётов. Для генерации исходной матрицы будем использовать класс Random
. Для выполнения всех необходимых вычислений нам потребуются знания о циклах в C# и логических операторах.
Третий момент, касающийся условия задачи, которое говорит нам о том, что необходимо использовать программу-генератор. Если у вас имеются знания об использовании методов в C#, то эту часть лабораторной работы можно и не выносить в отдельную программу, а создать метод, который будет генерировать исходный файл. Опять же, в практической части лабораторной работы я покажу оба варианта решения — с написанием отдельного приложения и с использованием метода.
Практическая часть
Генератор массива
Генератор массива в виде отдельного приложения
Начнем выполнение лабораторной работы с чёткого следования условию — написания программы-генератора.
Смотрим на условие задачи: во входном файле содержится две строки: первая содержит одно целое число N
(количество чисел во второй строке), вторая строка содержит N
вещественных чисел (каждое от 0 до 105). В условии ничего не говориться об ограничениях на размер массива, который будет сформирован, следовательно, нам, как минимум придётся учитывать, что пользователь может задать массив и на 1 элемент и на 2 147 483 647 и программа должна это условие выполнить. Так как целочисленных типов данных в C# несколько, то договоримся, что для значения N
будем использовать тип int
.
Логика работы программы должна быть следующая: после запуска, программа должна запросить значение N
. Если N
задано корректно (число больше нуля и не выходит за границы диапазона значений int
), то создается файл, содержащий в первой строке значение N
, а во второй — N
случайных чисел от 0 до 105. Чтобы файл сгенерированный нашей программой впоследствии можно было прочитать в другой программе, необходимо сразу договориться о формате записи строки с числами. Для разделения чисел в строке мы будем использовать пробел.
И, наконец, запись файла мы будем производить с использованием перенаправления потока вывода консольного приложения в файл.
Теперь приступим к созданию программы-генератора исходного массива. Ниже представлено код нашего консольного приложения:
namespace generator { internal class Program { static void Main(string[] args) { Console.WriteLine("Добро пожаловать в генератор исходного массива!"); Console.WriteLine("Введите желаемое количество элементов в массиве"); string str; int val = 0; //пробуем преобразовать введенное пользователем значение в целое число while (val <= 0) { //просим пользователя ввести число Console.Write("N = "); str = Console.ReadLine(); //считываем введенное значение if (int.TryParse(str, out val)) { if (val>0) { TextWriter save_out = Console.Out; //сохраняем стандартный поток вывода //перенаправляем поток вывода StreamWriter writer = new StreamWriter(@"input.txt"); Console.SetOut(writer); //пишем значения в файл Console.WriteLine(val); Random random = new Random(); for (int i = 0; i<val; i++) Console.Write($"{random.Next(100001)} "); //возвращаем стандартный поток вывода Console.SetOut(save_out); writer.Close();//закрываем файл Console.WriteLine($"Массив из {val} элементов создан!"); } } } Console.WriteLine($"Ваш файл находится здесь: {Environment.CurrentDirectory}"); } } }
Рассмотрим алгоритм работы этого приложения. Первые две строки — это, в принципе, ничего не значащий текст — мы просто здороваемся с пользователем и говорим, что он запустил генератор. Далее, мы объявляем две переменные — строку и число:
string str; int val = 0;
Строка нам требуется, чтобы хранить введенное пользователем значение, а в переменную типа int
мы будем, соответственно, сохранять количество элементов массива. Далее, мы создаем цикл while
. Для чего он нам потребовался? Это своеобразная «защита от дурака». Тело цикла будет выполняться до тех пор, пока пользователь приложения не введет значение больше нуля и это значение не будет больше, чем максимальное значение типа int
. Если пользователь введет сразу верное значение, то тело цикла выполнится один раз. При этом, стоит обратить внимание, что переменную val
мы сразу же инициализируем значением 0, чтобы цикл смог выполнится хотя бы один раз. Теперь рассмотрим тело цикла.
Вначале мы просим пользователя ввести число и считываем введенное значение в переменную str
:
Console.Write("N = "); str = Console.ReadLine(); //считываем введенное значение
Далее, мы проверяем условие
if (int.TryParse(str, out val)) { ... }
Код в фигурных скобках будет выполнен только в том случае, если введенная строка будет представлять собой целое число. Далее, если пользователь действительно ввел целое число, то проверяется второе условие, что пользователь ввел число больше нуля:
if (val>0) { ... }
Если и это условие оказалось истинным, то начинается основная работа приложения — формирование файла с исходными данными. Первое, что мы делаем — это перенаправляем поток вывода на файл:
TextWriter save_out = Console.Out; //сохраняем стандартный поток вывода //перенаправляем поток вывода StreamWriter writer = new StreamWriter(@"input.txt"); Console.SetOut(writer);
Далее мы записываем в файл значение N и запускаем цикл, в котором генерируются случайные числа, которые также пишутся в файл, но уже во вторую строку:
//пишем значения в файл Console.WriteLine(val); Random random = new Random(); for (int i = 0; i<val; i++) Console.Write($"{random.Next(100001)} ");
здесь стоит обратить внимание на тело цикла for
. Во-первых, как мы договорились выше, для разделения чисел мы используем пробел, для чего формируем очередную строку с использованием интерполяции. Во-вторых, обратите внимание на вызов метода Next
у объекта random
:
random.Next(100001)
по условию задачи, максимальное число, которое может быть выведено в файл — это 100 000. Метод Next
возвращает случайное число, меньшее, чем задано в параметрах. То есть, если бы мы сделали такой вызов:
random.Next(100000)
то мы бы нарушили условие задачи и максимальное число в файле было бы не 100 000, а 99 999. После того, как файл сформирован, мы возвращаем значение потока вывода обратно, закрываем файл и сообщаем пользователю, что массив сформирован:
//возвращаем стандартный поток вывода Console.SetOut(save_out); writer.Close();//закрываем файл Console.WriteLine($"Массив из {val} элементов создан!");
на этом тело цикла while
заканчивается и выводится последняя строка — мы показываем пользователю где лежит файл с исходными данными. Конечно, как и первые две строки в приложении, это делать не обязательно, но предпочтительно — чтобы пользователь, в случае чего, мог точно знать куда мы записали файл. Ниже показан вывод программы при ситуации, когда пользователь несколько раз вводит неверные значения, а затем — верное:
Введите желаемое количество элементов в массиве
N = ываыа
N = 0
N = -1
N = 10
Массив из 10 элементов создан!
Ваш файл находится здесь: %тут будет пусть к вашему файлу%
Значение в последней строке будет зависеть от того где будет располагаться ваше приложение на компьютере. На этом разработка программы-генератора закончена. Можно, конечно, улучшить приложение и дать пользователю возможность указывать имя файла, путь, куда этот файл сохранить и т.д., но это мы сделаем далее — когда превратим наше приложение в метод внутри нового приложения.
Генератор массива в виде метода C#
Для того, чтобы сгенерировать массив и записать его в файл, в принципе, отдельное приложение и не нужно. Более того, использование отдельного приложения может привести к лишним манипуляциям с этим файлом, учитывая то, что файл формируется рядом с exe-файлом приложения — может потребоваться открыть папку с файлом, скопировать его в папку к другому приложению и т.д. Зачем эти лишние манипуляции? Просто перенесем (и немного модифицируем) код нашего приложения-генератора непосредственно в код лабораторной работы.
Создадим новый проект консольного приложения C# и перенесем в него код метода Main
из предыдущего приложения, сделав попутно несколько изменений. Ниже, для наглядности, представлен весь исходный код приложения, который написан на текущий момент:
namespace generator { internal class Program { static int Generator(string fileName) { string str; int val = 0; //пробуем преобразовать введенное пользователем значение в целое число while (val <= 0) { //просим пользователя ввести число Console.Write("N = "); str = Console.ReadLine(); //считываем введенное значение if (int.TryParse(str, out val)) { if (val > 0) { using StreamWriter writer = new StreamWriter(fileName); { writer.WriteLine(val); Random random = new Random(); for (int i = 0; i < val; i++) writer.Write($"{random.Next(100001)} "); } } } } return val; } static void Main(string[] args) { const string fileName = "input.txt"; Console.WriteLine($"Введите количество элементов массива"); int count = Generator(fileName); Console.WriteLine($"Массив из {count} элементов сгенерирован и сохранен"); } } }
Первое на что обратим внимание — это на метод Generator
:
static int Generator(string fileName)
Этот метод принимает в качестве входного параметра строку (fileName
), содержащую имя файла, в который будет записан массив и возвращает целое число (int
) — количество элементов в сгенерированном массиве. Код этого метода отличается от кода разработанного выше приложения в следующих моментах: во-первых, в методе мы не перенаправляем поток вывода на файл, а используем методы объекта writer
для записи текста в файл. Во-вторых, для работы с объектом writer мы использовали конструкцию using
, которая позволяет нам автоматически освободить все ресурсы, занимаемые объектом после того, как надобность в использовании метода отпадает. В итоге, мы немного сократили код и избавились от вызова writer.Close()
.
Теперь перейдем к методу Main
. Здесь мы объявили константу (fileName
), в которой храним имя файла и ничего не значащую строку «Введите количество элементов массива». На следующей строке мы осуществляем вызов метода C#, указав в параметрах метода константу. Значение, возвращаемое методом присваивается переменной count
. После того, как массив будет сгенерирован, выведется последняя строка, в которой будет указано из скольки элементов будет состоять наш массив.
Основная часть лабораторной работы
Прежде, чем начинать писать код приложения, внимательно смотрим и анализируем, какие задачи перед нами стоят. Нам необходимо:
1) количество чисел, больших среднего арифметического исходных чисел;
2) сумма всех чисел, меньших среднего арифметического;
3) максимальное число.
Первое на что обращаем внимание — это на то, что в двух пунктах из трех от нас требуется сравнение очередного элемента массива со средним арифметическим значением. Следовательно, первое, что нам необходимо сделать — это рассчитать это значение. Второй момент, скорее касается внимательности — если число больше среднего арифметического, то мы должны считать количество (1, 2, 3 и т.д.), а если меньше — суммировать такие числа, например, 10+4+3 и т.д. В итоге получается следующий алгоритм работы:
- Читаем из сформированного файла элементы массива и создаем этот массив
- Считаем среднее арифметическое всех элементов массива
- В цикле проходим по всем элементам массива и считаем значения, необходимые для выполнения п.1 и п.2 условий задачи.
Вначале, решение лабораторной работы с использованием перенаправлений потоков ввода-вывода консоли. Код метода Main:
static void Main(string[] args) { const string fileName = "input.txt"; Console.WriteLine($"Введите количество элементов массива"); int count = Generator(fileName); Console.WriteLine($"Массив из {count} элементов сгенерирован и сохранен"); TextReader save_in = Console.In; string strElements; //строка, содержащая элементы массива string N;//количество элементов в массиве (которое ДОЛЖНО быть) using StreamReader reader = new StreamReader(fileName); { Console.SetIn(reader); N = reader.ReadLine(); strElements = reader.ReadLine(); } //разделяем строку на элементы, используя в качестве разделителя пробел Console.SetIn(save_in); int summa = 0; string[] strArray = strElements.Split(' '); int[] data = new int[Convert.ToInt32(N)];//создаем массив целых чисел for (int i = 0; i < strArray.Length; i++) { if (int.TryParse(strArray[i], out int value)) { data[i] = value; summa += value; } } double avg = (float)summa / data.Length;//считаем среднее арифметическое всех элементов массива int max = 0; //максимальное значение в массиве int sumMinValue = 0; //сумма всех элементов, меньших, чем avg int countMax = 0; //количество элементов, больших, чем avg foreach (int value in data) { if (value > max) max = value; if (value > avg) countMax++; else if (value < avg) sumMinValue += value; } //выводим в консоль полученные значения Console.WriteLine($"Среднее арифметическое значение: {avg:F3}"); Console.WriteLine($"Максимальное значение: {max}"); Console.WriteLine($"Количество элементов больших, чем среднее арифметическое: {countMax}"); Console.WriteLine($"Сумма элементов меньших, чем среднее арифметическое: {sumMinValue}"); //пишем выходной файл const string outputFile = "output.txt"; TextWriter save_out = Console.Out; using StreamWriter writer = new StreamWriter(outputFile); { Console.SetOut(writer); Console.WriteLine(countMax); Console.WriteLine(sumMinValue); Console.WriteLine(max); } Console.SetOut(save_out); }
После того, как файл с исходным массивом сгенерирован мы перенаправляем поток ввода на этот файл и считываем из него значения:
TextReader save_in = Console.In; string strElements; //строка, содержащая элементы массива string N;//количество элементов в массиве (которое ДОЛЖНО быть) using StreamReader reader = new StreamReader(fileName); { Console.SetIn(reader); N = reader.ReadLine(); strElements = reader.ReadLine(); } Console.SetIn(save_in);
После того, как значения из файла прочитаны — не забываем вернуть входной поток в исходное состояние (последняя строка кода). Далее идут следующие три строки кода:
int summa = 0; string[] strArray = strElements.Split(' '); int[] data = new int[Convert.ToInt32(N)];//создаем массив целых чисел
В первой строке мы объявляем переменную, в которой будем хранить сумму элементов массива. Во второй строке мы объявляем переменную-массив и присваиваем этой переменной значение, которое вернет нам метод Split
, который разделяет строку на элементы по определенному разделителю. Так как мы использовали в качестве разделителя пробел, то и в метод Split
мы передали в качестве разделителя тоже пробел. В третьей строке мы создаем массив целых значений на N
элементов.
Далее идет первый цикл — for
:
for (int i = 0; i < strArray.Length; i++) { if (int.TryParse(strArray[i], out int value)) { data[i] = value; summa += value; } }
Этот цикл выполняет сразу три задачи:
- если очередной элемент массива вдруг окажется не числом (а такое может вполне быть, если доступ к файлу получит кто-то кроме вас и захочет «пошутить»), то элемент с тем же номером в массиве
data
останется равным нулю (значением по умолчанию для типаint
) - массив
data
заполняется значениями - рассчитывается сумма всех элементов массива (переменная
summa
).
Этот цикл также мог решать и четвертую задачу — поиск максимального элемента массива, но оставим этот момент для второго цикла.
После того, как цикл пройден, считаем среднее арифметическое значение:
double avg = (float)summa / data.Length;
здесь мы приводим значение summa
к типу float
(для чего это делается — смотри подробное описание здесь) и делим сумму элементов на значение свойства data.Length
— количество элементов в массиве data
. После того, как среднее арифметическое значение определено, можно приступать к непосредственному решению задач, поставленных в лабораторной работе. Для этого был написан цикл foreach
:
int max = 0; //максимальное значение в массиве int sumMinValue = 0; //сумма всех элементов, меньших, чем avg int countMax = 0; //количество элементов, больших, чем avg foreach (int value in data) { if (value > max) max = value; if (value > avg) countMax++; else if (value < avg) sumMinValue += value; }
здесь каждый элемент массива проверяется на следующие условия:
- если очередной элемент больше, чем значение переменной
max
, то переменнаяmax
получает это значение. Тем самым мы ищем наибольший элемент в массиве. - если очередной элемент больше среднего значения, то наращивается на 1 значение переменной
countMax
- если очередной элемент меньше среднего значения, то считается сумма таких элементов (переменная
sumMinValue
увеличивается на величину очередного элемента массива)
Таким образом, после выполнения двух циклов мы полностью решаем поставленные в лабораторной работе задачи и всё, что нам остается — это перенести результаты в файл. Перед тем, как это сделать, мы информируем пользователя о результатах вычислений:
//выводим в консоль полученные значения Console.WriteLine($"Среднее арифметическое значение: {avg:F3}"); Console.WriteLine($"Максимальное значение: {max}"); Console.WriteLine($"Количество элементов больших, чем среднее арифметическое: {countMax}"); Console.WriteLine($"Сумма элементов меньших, чем среднее арифметическое: {sumMinValue}");
и, используя перенаправление потока вывода, пишем результаты в файл:
//пишем выходной файл const string outputFile = "output.txt"; TextWriter save_out = Console.Out; using StreamWriter writer = new StreamWriter(outputFile); { Console.SetOut(writer); Console.WriteLine(countMax); Console.WriteLine(sumMinValue); Console.WriteLine(max); } Console.SetOut(save_out);
Лабораторная работа полностью выполнена.
Весь исходный код лабораторной работы
Ниже представлен весь исходный код лабораторной работы:
namespace generator { internal class Program { static int Generator(string fileName) { string str; int val = 0; //пробуем преобразовать введенное пользователем значение в целое число while (val <= 0) { //просим пользователя ввести число Console.Write("N = "); str = Console.ReadLine(); //считываем введенное значение if (int.TryParse(str, out val)) { if (val > 0) { using StreamWriter writer = new StreamWriter(fileName); { writer.WriteLine(val); Random random = new Random(); for (int i = 0; i < val; i++) writer.Write($"{random.Next(100001)} "); } } } } return val; } static void Main(string[] args) { const string fileName = "input.txt"; Console.WriteLine($"Введите количество элементов массива"); int count = Generator(fileName); Console.WriteLine($"Массив из {count} элементов сгенерирован и сохранен"); TextReader save_in = Console.In; string strElements; //строка, содержащая элементы массива string N;//количество элементов в массиве (которое ДОЛЖНО быть) using StreamReader reader = new StreamReader(fileName); { Console.SetIn(reader); N = reader.ReadLine(); strElements = reader.ReadLine(); } Console.SetIn(save_in); int summa = 0; string[] strArray = strElements.Split(' '); int[] data = new int[Convert.ToInt32(N)];//создаем массив целых чисел for (int i = 0; i < strArray.Length; i++) { if (int.TryParse(strArray[i], out int value)) { data[i] = value; summa += value; } } double avg = (float)summa / data.Length;//считаем среднее арифметическое всех элементов массива int max = 0; //максимальное значение в массиве int sumMinValue = 0; //сумма всех элементов, меньших, чем avg int countMax = 0; //количество элементов, больших, чем avg foreach (int value in data) { if (value > max) max = value; if (value > avg) countMax++; else if (value < avg) sumMinValue += value; } //выводим в консоль полученные значения Console.WriteLine($"Среднее арифметическое значение: {avg:F3}"); Console.WriteLine($"Максимальное значение: {max}"); Console.WriteLine($"Количество элементов больших, чем среднее арифметическое: {countMax}"); Console.WriteLine($"Сумма элементов меньших, чем среднее арифметическое: {sumMinValue}"); //пишем выходной файл const string outputFile = "output.txt"; TextWriter save_out = Console.Out; using StreamWriter writer = new StreamWriter(outputFile); { Console.SetOut(writer); Console.WriteLine(countMax); Console.WriteLine(sumMinValue); Console.WriteLine(max); } Console.SetOut(save_out); } } }
Итого
В этой лабораторной работе мы использовали навыки работы с логическими операторами, массивами, файлами. Как и любая другая хорошая лабораторная работа по C# — эта лабораторная работа содержит в себе достаточно много путей улучшения приложения, например, можно подумать как улучшить приложение в плане обработки исключительных ситуаций или сделать работу приложения не с текстовым, а с бинарным файлом определенной структуры, содержащим необходимые значения и так далее. В общем, тут есть ещё над чем подумать.
Литература
- Технологии программирования: учебное пособие (лабораторный практикум) в двух частях (часть 1) для студентов направления 09.03.02 «Информационные системы и технологии» / Николаев Е.И. – Ставрополь: Изд-во СКФУ, 2020. – 184 с.