Расстояние между точками в пространстве и на плоскости

mygedz

Администратор
Сообщения
588
Оценка реакций
62
C#:
using System;

namespace ConsoleAppXYandXY3
{
    class Program
    {
        static void Main(string[] args)
        {
            char selection;
            Double x1, x2, y1, y2, z1, z2, distance;
           
            Console.WriteLine("1. Расстояние между двумя точками на плоскости.");
            Console.WriteLine("2. Расстояние между двумя точками в пространстве.");
            Console.WriteLine("Выберите нужный пункт (1 или 2):");

            selection = Convert.ToChar(Console.ReadLine());

            switch (selection)
            {
                case '1':
                    Console.Write("Введите координаты x1: ");
                    x1 = Convert.ToDouble(Console.ReadLine());

                    Console.Write("Введите координаты x2: ");
                    x2 = Convert.ToDouble(Console.ReadLine());

                    Console.Write("Введите координаты y1: ");
                    y1 = Convert.ToDouble(Console.ReadLine());

                    Console.Write("Введите координаты y2: ");
                    y2 = Convert.ToDouble(Console.ReadLine());

                    distance = Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2));
                    Console.WriteLine($"Расстояние между двумя точками на плоскости равно {distance}");
                    Console.ReadKey();
                    break;
                case '2':
                    Console.Write("Введите координаты x1: ");
                    x1 = Convert.ToDouble(Console.ReadLine());

                    Console.Write("Введите координаты x2: ");
                    x2 = Convert.ToDouble(Console.ReadLine());

                    Console.Write("Введите координаты y1: ");
                    y1 = Convert.ToDouble(Console.ReadLine());

                    Console.Write("Введите координаты y2: ");
                    y2 = Convert.ToDouble(Console.ReadLine());

                    Console.Write("Введите координаты z1: ");
                    z1 = Convert.ToDouble(Console.ReadLine());

                    Console.Write("Введите координаты z2: ");
                    z2 = Convert.ToDouble(Console.ReadLine());

                    distance = Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2) + Math.Pow(z2 - z1, 2));
                    Console.WriteLine($"Расстояние между двумя точками в пространстве равно {distance}");
                    Console.ReadKey();
                    break;
                default:
                    Console.WriteLine("Вы ввели неверный символ");
                    Console.ReadKey();
                    break;
            }

        }
    }
}
 

Вложения

Последнее редактирование:

mygedz

Администратор
Сообщения
588
Оценка реакций
62
А вот что написал знающий товарищ (tym32167, далее его текст):
Когда вы пишете программу, пишете код, то требования к качеству кода, стилю и структуре могут различаться, в зависимости от многих факторов. Это как писать художественное произведение - если вы пишете короткий рассказ, то вам можно не беспокоиться о вселенной, где обитают персонажи, о мотивации каждого из них и прочих вещах. Если же вы пишете большой труд, то вам придется предусмотреть всё, законы вымышленного мира, события, которые в этом мире будут пересекаться, разные части вашего произведения будут тесто переплетены и общая сложность произведения увеличится.

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

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

Итак, первое, что бросается в глаза, вы в своем коде оперируете точками - двухмерной и трехмерной. В текущем варианте, что у вас есть, точка представлена 2 или 3 переменными - координатами. И каждый раз, когда вам нужно хранить точку, вы храните её в виде набора переменных. Для простого приложения это не имеет значения. Для корпоративного - это плохо, так как:
  • если в каких то других участках программы понадобится использовать точки, то там также придется оперировать 2 переменными, а не одной, что в 2 раза сложнее.
  • вы, по большому счету, уже в самом задании оперируете термином "точка", то есть по заданию это целостный объект. Вы рассматриваете ввод точки юзером, расстояние между точками, но у вас в коде нет сущности "точка", что заставляет читателя вашего кода (а код чаще читают, чем пишут, потому читаемость кода очень важна) прилагать умственные усилия, чтобы увидеть ваши точки в коде, чтобы собрать в голове точку из ваши переменных.
Поэтому, первое, что я бы сделал, будь это корпоративным приложением, это выделил бы 2 сущности - 2д и 3д точки. Выглядеть это может вот так:
C#:
public class Point2D
{
    public double X { get; }
    public double Y { get; }

    public Point2D(double x, double y)
    {
        X = x;
        Y = y;
    }
}

public class Point3D
{
    public double X { get; }
    public double Y { get; }
    public double Z { get; }

    public Point3D(double x, double y, double z)
    {
        X = x;
        Y = y;
        Z = z;
    }
}
Теперь, имея на руках точки, читаем задание дальше - нам надо иметь возможность находить расстояние между точками. Вы это делаете прямо в вашей основной функции, но мы же пишем корпоративную систему - а это значит, что ваше приложение будет развиваться годами, то есть уж точно где-то когда-то, возможно уже другому программисту, понадобится точно такой же функционал - вычисление расстояния между точками. Поэтому, целесообразно написать функцию для этого. Но куда эту функцию деть? Я бы создал отдельный класс для работы с точками или с геометрическими примитивами, для простейших операций. Класс и сам метод я бы сделал статическими, так как вычисление расстояния между точками - это алгоритм, что навряд ли будет меняться в будущем, не зависимо от изменений в логике нашего приложения.
C#:
public static class GeomertyMath
{
    public static double Distance(Point2D p1, Point2D p2)
    {
        return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X)
                         + (p1.Y - p2.Y) * (p1.Y - p2.Y));
    }

    public static double Distance(Point3D p1, Point3D p2)
    {
        return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X)
                         + (p1.Y - p2.Y) * (p1.Y - p2.Y)
                         + (p1.Z - p2.Z) * (p1.Z - p2.Z));
    }
}
Далее, вопрос - а как нам понять, что наши методы работают правильно? К тому же, программист, что будет работать над кодом после нас, может случайно или намеренно изменить наши формулы и привести программу в негодность. Как снизить риск подобного? Тут нам на помощь приходят автоматические тесты (ключевые слова для поиска в гугле - unit testing, юнит тестирование). Например, вот пара тестов с использованием фреймворка NUnit
C#:
[TestFixture]
public class GeomertyMath_Tests
{
    [Test]
    public void Distance2D_positive_test()
    {
        var p1 = new Point2D(10, 10);
        var p2 = new Point2D(13, 14);

        var expectedResult = 5;
        var actualResult = GeomertyMath.Distance(p1, p2);

        var eps = 0.000001;

        Assert.True(Math.Abs(expectedResult - actualResult) < eps);
    }

    [Test]
    public void Distance2D_zero_test()
    {
        var p1 = new Point2D(0, 0);
        var p2 = new Point2D(0, 0);

        var expectedResult = 0;
        var actualResult = GeomertyMath.Distance(p1, p2);

        var eps = 0.000001;

        Assert.True(Math.Abs(expectedResult - actualResult) < eps);
    }
}
Итак, что мы имеем? У нас есть точки и есть проверенный и протестированный метод для расчета расстояния. Далее, нам надо уметь эти точки считать от пользователя. То есть нам нужна функция для считывания точки (2д и 3д). По мере получения опыта в разработке, вы начнете понимать такие вещи, как ответсвенность кода за какие-то функции. Вот, например, считывание точки с консоли - это функционал, который, по идее, никак не пересекается ни с логикой работы самой точки, ни с логикой расчетов расстояний, ни вообще с геометрическими операциями. То есть считвание точки с консоли - это абсолютно обособленный функционал, поэтому я сделаю для этого отдельный класс. В принципе, я в эот класс добавлю все взаимодействие с консолью, что нам необходимо.
C#:
public interface IReaderWriter
{
    Point2D Read2DPoint();
    Point3D Read3DPoint();
    string ReadLine();
    void WriteLine(string line);
}

public class ConsoleReaderWriter : IReaderWriter
{
    public Point2D Read2DPoint()
    {
        Console.Write("Введите координаты x: ");
        var x = Convert.ToDouble(Console.ReadLine());
        Console.Write("Введите координаты y: ");
        var y = Convert.ToDouble(Console.ReadLine());
        return new Point2D(x, y);
    }

    public Point3D Read3DPoint()
    {
        Console.Write("Введите координаты x: ");
        var x = Convert.ToDouble(Console.ReadLine());
        Console.Write("Введите координаты y: ");
        var y = Convert.ToDouble(Console.ReadLine());
        Console.Write("Введите координаты z: ");
        var z = Convert.ToDouble(Console.ReadLine());
        return new Point3D(x, y, z);
    }

    public void WriteLine(string line)
    {
        Console.WriteLine(line);
    }

    public string ReadLine()
    {
        return Console.ReadLine();
    }
}
Обратите внимание, что сам класс - не статический и реализует нами же написанный интерфейс. Это сделано специально, так как в будущем, если нам придется считывать точки не только с консоли, но и, например, из базы данных или веб сервиса или какого-либо другого источника данных, то будет легко написать несколько реализаций для интерфейса, где считывание с консоли будет только одним из вариантов. И тогда, мы сможем запускать наши сценарии, считывая при этом данные не только из консоли. Это нам также поможет при тестировании наших сценариев.

Итак, у нас есть считватель точек, что дальше? Дальше, у нас есть главный сценарий с выбором и 2 независимых сценария, в одном сценарии мы считываем 2д точки, в другом - 3д. Давайте это оформим в виде 3 функций. Не забываем, что эти сценарии - это отдельный функционал, который никак не связан с другими участками нашего кода, потому я его выделю в отдельный класс.
C#:
public class MainScenarios
{
    private readonly IReaderWriter _readerWriter;

    public MainScenarios(IReaderWriter readerWriter)
    {
        _readerWriter = readerWriter;
    }

    public void Start()
    {
        _readerWriter.WriteLine("1. Расстояние между двумя точками на плоскости.");
        _readerWriter.WriteLine("2. Расстояние между двумя точками в пространстве.");
        _readerWriter.WriteLine("Выберите нужный пункт (1 или 2):");

        var selection = Convert.ToChar(_readerWriter.ReadLine());

        switch (selection)
        {
            case '1':
                Read2DScenario();
                break;
            case '2':
                Read3DScenario();
                break;
            default:
                _readerWriter.WriteLine("Вы ввели неверный символ");
                break;
        }
    }

    private void Read2DScenario()
    {
        _readerWriter.WriteLine("Введите первую точку:");
        var p1 = _readerWriter.Read2DPoint();
        _readerWriter.WriteLine("Введите вторую точку:");
        var p2 = _readerWriter.Read2DPoint();
        var distance = GeomertyMath.Distance(p1, p2);
        _readerWriter.WriteLine($"Расстояние между двумя точками на плоскости равно {distance}");
    }

    private void Read3DScenario()
    {
        _readerWriter.WriteLine("Введите первую точку:");
        var p1 = _readerWriter.Read3DPoint();
        _readerWriter.WriteLine("Введите вторую точку:");
        var p2 = _readerWriter.Read3DPoint();
        var distance = GeomertyMath.Distance(p1, p2);
        _readerWriter.WriteLine($"Расстояние между двумя точками в пространстве равно {distance}");
    }
}
Обратите внимание, что сценарии не знают, откуда точно они считывают и куда пишут значения. Этому классу со сценариями был передан просто интерфейс. Как это использовать? Давайте уже напишем логику нашей точки входа:
C#:
class Program
{
    static void Main(string[] args)
    {
        var readerWriter = new ConsoleReaderWriter();
        var logic = new MainScenarios(readerWriter);
        logic.Start();
        Console.ReadKey();
    }
}
Мы видим, что тут, в самом начале исполнения нашей программы, мы просто подготавливаем нужные нам классы и запскаем их. Эта точка, когда мы просто готовим нашу систему к запуску и запускаем, называется корнем композиции. Тут нет никакой логики, только подготовка нужных классов и их запуск.

Казалось бы, что ещё можно добавить? Но это ещё не всё! Возникает вопрос - а как мы можем убедиться, что наша логика, наши сценарии, работают корректно? Ответ очевиден, нам нужен тест для сценариев. Тут то нам и поможет тот факт, что наши сцкнарии не знают, откуда считывают и куда пишут свои значения.

Напишем свою имитацию взаимодействия с юзером, например, такую
C#:
public class ReaderWriterMock : IReaderWriter
{
    private string[] _userInput;
    private int _index = 0;
    private StringBuilder _userOutput = new StringBuilder();

    public ReaderWriterMock(string userInput)
    {
        _userInput = userInput.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
    }

    public Point2D Read2DPoint()
    {
        return new Point2D(Double.Parse(ReadLine()), Double.Parse(ReadLine()));
    }

    public Point3D Read3DPoint()
    {
        return new Point3D(Double.Parse(ReadLine()), Double.Parse(ReadLine()), Double.Parse(ReadLine()));
    }

    public string ReadLine()
    {
        return _userInput[_index++];
    }

    public void WriteLine(string line)
    {
        _userOutput.AppendLine(line);
    }

    public string GetOutput()
    {
        return _userOutput.ToString();
    }
}
Как видно, с помощю этого класса, мы сможем запускать свою логику и тестировать без участия человека. То есть мы сможем написать тесты нашей логики и запускать их, тем самым показывая, что наша логика работает верно. Вот пример 2 тестов, первый тест имитирует неверный ввод юзера, второй - простой сценарий с 2мерными точками.
C#:
[TestFixture]
public class MainScenariosTests
{
    [Test]
    public void WrongInputTest()
    {
        var userInput = @"3";
        var readerWriter = new ReaderWriterMock(userInput);
        var scenarios = new MainScenarios(readerWriter);

        scenarios.Start();

        var userOutput = readerWriter.GetOutput();
        var expected = @"1. Расстояние между двумя точками на плоскости."
                       + Environment.NewLine + "2. Расстояние между двумя точками в пространстве."
                       + Environment.NewLine + "Выберите нужный пункт (1 или 2):"
                       + Environment.NewLine + "Вы ввели неверный символ"
                       + Environment.NewLine;

        Assert.AreEqual(expected, userOutput);
    }

    [Test]
    public void Scenario2DTest()
    {
        var userInput = @"
1
10
15
13
19
";
        var readerWriter = new ReaderWriterMock(userInput);
        var scenarios = new MainScenarios(readerWriter);

        scenarios.Start();

        var userOutput = readerWriter.GetOutput();
        var expected = @"1. Расстояние между двумя точками на плоскости."
                       + Environment.NewLine + "2. Расстояние между двумя точками в пространстве."
                       + Environment.NewLine + "Выберите нужный пункт (1 или 2):"
                       + Environment.NewLine + "Введите первую точку:"
                       + Environment.NewLine + "Введите вторую точку:"
                       + Environment.NewLine + "Расстояние между двумя точками на плоскости равно 5"
                       + Environment.NewLine;

        Assert.AreEqual(expected, userOutput);

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

mygedz

Администратор
Сообщения
588
Оценка реакций
62
Или вот еще пример:
C#:
static void Main(string[] args)
{
    char selection;

    while (selection != 3)
    {
        selection = MainMenu()

        switch (selection)
        {
            case '1':
                CalcFlatDistanceCooridinates();
                break;
            case '2':
                CalcDistance3dCooridinates();
                break;
            case '3':
                break;
            default:
                WrongCommand();
                break;
        }
    }
}

private void MainMenu()
{
    Console.WriteLine("1. Расстояние между двумя точками на плоскости;");
    Console.WriteLine("2. Расстояние между двумя точками в пространстве;");
    Console.WriteLine("3. Выход");

    return Convert.ToChar(Console.ReadLine());
}

private void CalcFlatDistanceCooridinates()
{
    double[] x = new double[2];
    double[] y = new double[2];

    Console.Write("Введите координаты(можно с плавающей точкой) x1|y1|x2|y2:");

    //считываешь и обрабатываешь строку и записываешь все в x, y в соответствующую ячейку;
    //обрабатывая в том числе неправильно введенное количество переменных (должно быть 3 знака "|" )

    var distance = Math.Sqrt(Math.Pow(x[1] - x[0], 2) + Math.Pow(y[1] - y[0], 2));

    Console.WriteLine($"Расстояние между двумя точками на плоскости: {distance}");
    Console.ReadKey();
}

private void CalcDistance3dCooridinates()
{
    double[] x = new double[2];
    double[] y = new double[2];
    double[] z = new double[2];


    Console.Write("Введите координаты(можно с плавающей точкой) x1|y1|z1|x2|y2|z2:");

    //считываешь и обрабатываешь строку и записываешь все в coord1, coord2;
    //обрабатывая в том числе неправильно введенное количество переменных

    distance = Math.Sqrt(Math.Pow(x[1] - x[0], 2) + Math.Pow(y[1] - y[0], 2) + Math.Pow(z[1] - z[0], 2));
    Console.WriteLine($"Расстояние между двумя точками в пространстве равно {distance}");
    Console.ReadKey();
    break;
}

private void WrongCommand()
{
    Console.WriteLine("Вы ввели неверный символ");
    Console.ReadKey();
}
 
Верх Низ