Лабораторная работа по C#. Создание и реализация класса «Матрица MxN»

Задача: Создать класс «Матрица MxN». Реализовать инициализацию элементов матрицы случайными числами, вывод транспонированной матрицы, нахождение среднего арифметического всех элементов, а также вывод информации об объекте.

Теоретическая часть

Для выполнения этой лабораторной работы нам потребуются основные теоретические знания о том, что из себя представляют классы и объекты в C#, методы C# и, возможно, информация о том, как переопределить метод.

Также нам потребуется информация о том, что из себя представляет транспонированная матрица.  Самое простое определение транспонированной матрицы — это матрица у которой столбцы и строки поменяны местами или, говоря языком математики: А[i, j]T = A[j, i] Этим выражением мы воспользуемся при выполнении лабораторной работы.

Итак, в нашем классе должно быть объявлено минимум четыре метода:

  1. Инициализация матрицы
  2. Транспонирование матрицы
  3. Нахождение среднего арифметического всех элементов
  4. Вывод информации об объекте

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

Практическая часть

Создаем «скелет» класса

Для начала, создадим «скелет» класса, чтобы в дальнейшем можно было сэкономить немного места в статье. Исходя из информации в теоретической части, можно объявить вот такой класс Matrix:

class Matrix
{
    int[,] data; //здесь будет хранится матрица

    //метод генерации матрицы
    public void Generate(int m, int n) 
    { }
            
    //метод транспонирования матрицы
    public int[,] Transpose()
    { }

    //метод вычисления среднего значения
    public float Average()
    { }

    //метод получения информации об объекте
    public string GetInfo()
    {
        return "";
    }
}

Это минимум, что должно быть в нашем классе. При желании, этот класс можно сделать более функциональным, а вместо метода GetInfo() переопределить в классе метод ToString() (о том, как это сделать можно почитать здесь). Теперь начнем разрабатывать непосредственно методы класса.

Методы класса Matrix

Начнем с метода Generate — генерация матрицы:

public void Generate(int m, int n)
{
    data = new int[m, n]; //создаем пустой массив
    Random random = new Random();
    //заполняем массив данными
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
        {
            data[i, j] = random.Next(10, 100);
        }
}

на входе метод получает размерности матрицы: m — количество строк, n — количество столбцов. Вначале, мы создаем массив data с необходимыми размерами. Далее, мы используем двойной цикл for:

  • внешний цикл — проходит по массиву построчно
  • внутренний цикл — проходит по массиву по столбцам

на каждой итерации внутреннего цикла мы генерируем случайное число в диапазоне от 10 до 99 и заполняем массив data случайными числами. Теперь напишем метод транспонирования полученной матрицы. В отличие от предыдущего метода, метод Transpose не принимает никаких параметров, но возвращает значение — транспонированную матрицу:

public int[,] Transpose()
{
    int[,] transpose = new int[data.GetLength(1), data.GetLength(0)];
    for (int i = 0; i < data.GetLength(0); i++)
    {
        for (int j = 0; j < data.GetLength(1); j++)
        {
            transpose[j,i] = data[i,j];
        }
    }
    return transpose;
}

Здесь опять же, используется двойной цикл for, но с некоторыми изменениями, а именно:

  1. для получения количества строк и столбцов в исходной матрице мы используем метод массива data.GetLength(), который возвращает размер заданного в параметрах измерения массива, то есть data.GetLength(0) — возвращает количество строк, а data.GetLength(1) — количество столбцов.
  2. на каждой итерации внутреннего цикла мы заполняем новый массив (transpose), используя свойство транспонированной матрицы, описанное в теоретической части.

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

Третий метод — Average (вычисление среднего арифметического значения всех элементов матрицы):

public float Average()
{
    float sum = 0;
    for (int i = 0; i < data.GetLength(0); i++)
    {
        for (int j = 0; j < data.GetLength(1); j++)
        {
            sum+= data[i,j];
        }
    }
    return sum/(data.GetLength(0)* data.GetLength(1));
}

Снова двойной цикл в котором рассчитывается сумма всех элементов и в конце эта сумма делится на количество элементов в матрице. Чтобы лишний раз не приводить значение суммы к типу float мы сразу объявили переменную необходимого нам типа.

И последний метод — вывод информации об объекте:

public string GetInfo()
{
    string matrix = $"Размеры: {data.GetLength(0)}x{data.GetLength(1)}\n";
    matrix += $"Среднее значение {Average()} \n";
    matrix += "----Матрица------\n";
    for (int i = 0; i < data.GetLength(0); i++)
    {
        for (int j = 0; j < data.GetLength(1); j++)
        {
            matrix += data[i, j].ToString() + "\t";
        }
        matrix += "\n";
    }
    return matrix;
}

Тестирование класса

Теперь осталось протестировать наше приложение и посмотреть на результат. Напишем следующий метод Main нашего приложения:

static void Main(string[] args)
{
    Matrix matrix = new Matrix();
    matrix.Generate(5, 10);	
    Console.WriteLine(matrix.GetInfo());
    Console.WriteLine("TRANSPOSE");
    int[,] data = matrix.Transpose();
    string m = "";
    for (int i = 0; i < data.GetLength(0); i++)
    {
        for (int j = 0; j < data.GetLength(1); j++)
        {
            m += data[i, j].ToString() + "\t";
        }
        m += "\n";
    }
    Console.WriteLine(m);
}

Здесь мы создали объект класса Matrix, сгенерировали матрицу 5х10, вывели информацию об исходной матрице, транспонировали матрицу и вывели её элементы в консоль. В запущенном приложении это выглядит следующим образом:

Транспонированная матрица
Транспонированная матрица

Как можно видеть по рисунку — приложение работает корректно. Основная часть работы, можно сказать, закончена. Но, если внимательно посмотреть на код, то мы можем увидеть следующие его недостатки:

  1. в методе Main мы фактически продублировали метод GetInfo() для вывода информации о матрице. Это, как минимум, лишняя трата времени и, как максимум, проблемы с дальнейшей поддержкой приложения. Пример проблемы — вам скажут, что элементы матрицы надо выводить через запятую, а не через табуляцию, да ещё и раскрашивать каждую строку в другой цвет. В итоге, вам придётся дважды переписывать код вывода матрицы в консоль.
  2. Практически в каждом методе (кроме Generate) мы «дёргаем» метод массива data.GetLength(). Зачем нам лишние вызовы методов (читай — лишняя трата процессорного времени), если размерности исходной матрицы можно запоминать.
  3. Что произойдет, если мы сразу после создания объекта класса Matrix вызовем метод GetInfo? А получим мы сразу ошибку так как массив data на этот момент не будет создан.

Этот только то, что лежит на поверхности. Давайте немного улучшим наше приложение и сделаем его работу более стабильной и быстрой.

Улучшение приложения

Для начала, добавим в наш класс два поля, которые будут хранить значения размерностей исходной матрицы:

class Matrix
{
    int[,] data;
    int m, n;
    ....

Так как размерности у нас пока передаются только в методе Generate, то допишем его следующим образом:

public void Generate(int m, int n)
{
    this.m = m;
    this.n = n;
    //тут весь код метода Generate, написанный ранее
}

Теперь разберемся с методом вывода матрицы в консоль. Судя по тексту задания, от нас не требуется никаких манипуляций с транспонированной матрицей, кроме того, что её необходимо вывести в консоль. Следовательно, хранить её нам незачем. Исходя из этого условия, мы можем переписать наш метод GetInfo.

    public string GetInfo(bool isTranspose, bool needAlInfo)
    {
        string matrix = isTranspose ? "----Транспонированная матрица------\n" : "----Исходная матрица------\n"; 
        var array = isTranspose ? Transpose() : data;

        int row = isTranspose ? n : m;
        int col = isTranspose ? m : n;
        
        if (needAlInfo)
        {
            matrix += isTranspose ? $"Размеры: {n}x{m}\n" : $"Размеры: {m}x{n}\n";
            matrix += $"Среднее значение {Average()} \n";
        }
        
        for (int i = 0; i < row; i++)
        {
            for (int j = 0; j < col; j++)
            {
                matrix += array[i, j].ToString() + "\t";
            }
            matrix += "\n";
        }
        return matrix;
    }
}

Первое, что поменялось — это то, что метод стал принимать два обязательных параметра: isTranspose — если true, то необходимо вывести на экран транспонированную матрицу, если false — исходную. Параметр needAlInfo указывает на то, необходимо ли вывести всю информацию по объекту, то есть и среднее значение и размерность и саму матрицу.

Далее, в самом методе мы неоднократно используем тернарные операции (сокращенные проверки), например:

string matrix = isTranspose ? "----Транспонированная матрица------\n" : "----Исходная матрица------\n";

что дословно означает: если параметр isTranspose = true, то переменная matrix принимает значение ----Транспонированная матрица------\n, иначе — переменная matrix принимает значение ----Исходная матрица------\n. Более подробную информацию о тернарных операциях можно получить в этой статье.

Помимо тернарной операции мы также воспользовались такой возможностью C#, как использование неявно типизированных переменных вот в этой строке:

var array = isTranspose ? Transpose() : data;

в зависимости от того, что необходимо вывести на экран, неявно типизированная переменная array будет содержать либо транспонированную матрицу, либо исходную. Про неявно типизированные переменные можно почитать здесь. Дальше всё должно быть знакомо — проверяем значение параметра needAlInfo и формируем строку с информацией об объекте.

Таким образом, мы избавились сразу от двух проблем. Во-первых, хранение в полях класса информации о размерности исходной матрицы позволит нам избавиться в других методах от использования метода массива GetLength(), а, во-вторых, теперь мы можем, используя метод GetInfo выводить в консоль или исходную матрицу, или транспонированную и, при этом, управлять выводимой информацией — выводить всё или только матрицу. Переписать все методы на использование полей m и n можно самостоятельно (если нет — в конце статьи готовый исходник), мы же перейдем к последней, наиболее серьезной проблеме — избавимся от ситуации, когда матрица ещё не сформирована, а пользователь пробует вывести её на экран.

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

public float Average()
{
    if (data == null)
    {
        Console.WriteLine("Матрица не существует!");
        return -1;
    }
        
    //тут код метода
}

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

Второй вариант — инициализировать массив data нулями, например, так:

class Matrix
{
    int[,] data = new int[1,1];

    int m = 1;
    int n = 1;
    //тут весь остальной код класса

здесь мы создаем массив размерностью 1×1, который по умолчанию будет заполнен нулями, а точнее — одним нулем, так как матрица будет содержать всего один элемент. Таким образом, даже если пользователь вызовет любой из методов класса — ошибки не произойдет.

Третий вариант — определить свой конструктор класса, который будет требовать от пользователя сразу ввести требуемую размерность матрицы и произведет её инициализацию. Какой из трех вариантов подойдет вам — решать только вам. Я же покажу только третий вариант — с созданием конструктора:

class Matrix
{
    int[,] data = new int[1,1];

    int m = 1;
    int n = 1;

    public Matrix(int m, int n)
    {
        Generate(m,n);
    }

    // здесь код класса

здесь метод Matrix — это конструктор класса. Этот метод не имеет возвращаемого значения (даже void). Внутри конструктора вызывается метод Generate, который инициализирует матрицу, что позволит избежать ошибок при вызове методов класса. Теперь остается только окончательно дописать метод Main приложения.

Окончательный вариант метода Main

     static void Main(string[] args)
     {
         int m = 0;
         int n = 0;
         int errors = 0;
         while ((m == 0) && (n == 0))
         {
             Console.WriteLine("Введите желаемую размерность матрицы в формате MхN, где M и N - целые числа");
             Console.WriteLine("Например, 10x5");
             Console.Write("Размерность матрицы: ");
             string[] strings = Console.ReadLine().Split('x');
             if ((strings.Length < 2) || (int.TryParse(strings[0], out m) == false) || (int.TryParse(strings[1], out n) == false))
             {
                 errors++;
                 Console.WriteLine($"Допущено ошибок ввода: {errors}");
                 if ((errors > 5)&&(errors<10))
                 {
                     Console.Beep();
                     Console.ForegroundColor= ConsoleColor.Blue;
                     Console.WriteLine("Не издевайся надо мной! Пиши так: целое число, затем - маленький ИКС (английская раскладка!), затем - опять целое число. Пробелы ставить не надо");
                     Console.ForegroundColor = ConsoleColor.White;
                     continue;
                 }	
                 else
                     if (errors>=10)
                     {
                     Console.Beep();
                     Console.Beep();
                     Console.ForegroundColor = ConsoleColor.Red;
                     Console.WriteLine("Ты безнадежен, человек. Закрой программу и иди домой");
                     Console.ForegroundColor = ConsoleColor.White;
                     break;
					}
             }
         }
             
         if (errors<10)
         {
             Matrix matrix = new Matrix(m, n);
             Console.WriteLine(matrix.GetInfo(false, true));
             Console.WriteLine(matrix.GetInfo(true, false));
         }
     }

Здесь, помимо всего прочего, продемонстрирован один из вариантов того, как обработать неверный ввод пользователем данных и прервать выполнение программы, если пользователь вводит N-ое количество раз ошибочные данные. Я намеренно прошу пользователя ввести размерность матрицы в заданном формате. Рассмотрим подробнее цикл while. Этот цикл будет повторяться не более десяти раз. Количество ошибок ввода подсчитывается в переменной errors. Как только пользователь введет шестой раз подряд ошибочные данные, программа выдаст звук «бип» и напишет в консоли синим цветом строку «Не издевайся надо мной! Пиши так: целое число, затем — маленький ИКС (английская раскладка!), затем — опять целое число. Пробелы ставить не надо». Звук «бип» и эта строка повториться ещё 4 раза, после чего у программы «лопнет терпение» — она издаст двойной «бип» и попросит пользователя выйти, написав строку красным цветом.

Если же пользователь введет верные данные то сработает вторая часть метода — будет создан класс и выведена информация по исходной матрице и транспонированной:

if (errors<10)
{
    Matrix matrix = new Matrix(m, n);
    Console.WriteLine(matrix.GetInfo(false, true));
    Console.WriteLine(matrix.GetInfo(true, false));
}

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

Обработка ошибок в консольном приложении
Обработка ошибок в консольном приложении

А так, когда пользователь вводит верное значение:

Корректная работа приложения
Корректная работа приложения

Весь исходный код лабораторной работы

Ниже представлен весь исходный код к лабораторной работе

using System;

namespace MatrixMxN
{

    class Matrix
    {
        int[,] data = new int[1,1];

        int m = 1;
        int n = 1;

        public Matrix(int m, int n)
        {
            Generate(m,n);
        }

        public void Generate(int m, int n)
        {
            this.m = m;
            this.n = n;
            data = new int[m, n]; //создаем пустой массив
            Random random = new Random();
            //заполняем массив данными
            for (int i = 0; i < m; i++)
                for (int j = 0; j < n; j++)
                {
                    data[i, j] = random.Next(10, 100);
                }
        }

        public int[,] Transpose()
        {
            int[,] transpose = new int[n, m];
            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    transpose[j,i] = data[i,j];
                }
            }
            return transpose;
        }

        public float Average()
        {
            if (data == null)
            {
                Console.WriteLine("Матрица не существует!");
                return -1;
            }
                
            float sum = 0;
            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    sum+= data[i,j];
                }
            }
            return sum/(data.GetLength(0)* data.GetLength(1));
        }

        public string GetInfo(bool isTranspose, bool needAlInfo)
        {
            string matrix = isTranspose ? "----Транспонированная матрица------\n" : "----Исходная матрица------\n"; 
            var array = isTranspose ? Transpose() : data;

            int row = isTranspose ? n : m;
            int col = isTranspose ? m : n;
            
            if (needAlInfo)
            {
                matrix += isTranspose ? $"Размеры: {n}x{m}\n" : $"Размеры: {m}x{n}\n";
                matrix += $"Среднее значение {Average()} \n";
            }
            
            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < col; j++)
                {
                    matrix += array[i, j].ToString() + "\t";
                }
                matrix += "\n";
            }
            return matrix;
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            int m = 0;
            int n = 0;
            int errors = 0;
            while ((m == 0) && (n == 0))
            {
                Console.WriteLine("Введите желаемую размерность матрицы в формате MхN, где M и N - целые числа");
                Console.WriteLine("Например, 10x5");
                Console.Write("Размерность матрицы: ");
                string[] strings = Console.ReadLine().Split('x');
                if ((strings.Length < 2) || (int.TryParse(strings[0], out m) == false) || (int.TryParse(strings[1], out n) == false))
                {
                    errors++;
                    Console.WriteLine($"Допущено ошибок ввода: {errors}");
                    if ((errors > 1)&&(errors<4))
                    {
                        Console.Beep();
                        Console.ForegroundColor= ConsoleColor.Blue;
                        Console.WriteLine("Не издевайся надо мной! Пиши так: целое число, затем - маленький ИКС (английская раскладка!), затем - опять целое число. Пробелы ставить не надо");
                        Console.ForegroundColor = ConsoleColor.White;
                        continue;
                    }	
                    else
                        if (errors==4)
                        {
                        Console.Beep();
                        Console.Beep();
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine("Ты безнадежен, человек. Закрой программу и иди домой");
                        Console.ForegroundColor = ConsoleColor.White;
                        break;
   					}
                }
            }
                
            if (errors<4)
            {
                Matrix matrix = new Matrix(m, n);
                Console.WriteLine(matrix.GetInfo(false, true));
                Console.WriteLine(matrix.GetInfo(true, false));
            }
        }
    }
}

Итого

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

Литература

  1. Технологии программирования: учебное пособие (лабораторный практикум) в двух частях (часть 1) для студентов направления 09.03.02 «Информационные системы и технологии» / Николаев Е.И. – Ставрополь: Изд-во СКФУ, 2020. – 184 с.
Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии