Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Юдин Павел #217

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
90 changes: 90 additions & 0 deletions cs/TagsCloudTests/ CircularCloudLayouterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Drawing;
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
using System.Drawing.Imaging;
using FluentAssertions;
using NUnit.Framework.Interfaces;
using tagsCloud;

namespace TagsCloudTests;

[TestFixture]
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
public class CircularCloudLayouterTests
{
private Point _start;
private CircularCloudLayouter circularCloudLayouter;
PavelUd marked this conversation as resolved.
Show resolved Hide resolved

[SetUp]
public void Setup()
{
_start = new Point(10, 10);
circularCloudLayouter = new CircularCloudLayouter(_start);
}

[TearDown]
public void TearDown()
{
if (TestContext.CurrentContext.Result.Outcome == ResultState.Failure)
{
var visualizer = new RectanglesVisualizer(circularCloudLayouter.Rectangles);
var workingDirectory = Environment.CurrentDirectory;
var projectDirectory = Directory.GetParent(workingDirectory).Parent.Parent.FullName;
var path = projectDirectory + @"\Images\";
var imageName = TestContext.CurrentContext.Test.Name;
var image = visualizer.DrawTagCloud();
image.Save($"{path}{imageName}.png", ImageFormat.Png);
Console.WriteLine($"Tag cloud visualization saved to file {path}{imageName}");
}
}


private readonly Func<List<Rectangle>, bool> isRectanglesIntersect = rectangles =>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

почему опять func
почему это не метод?

Copy link
Author

@PavelUd PavelUd Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Потому что в теле функции только return
Поэтому, я подумал, что можно и записать её как лямбда-выражение.

rectangles.Any(rectangle => rectangles.Any(nextRectangle =>
nextRectangle.IntersectsWith(rectangle) && !rectangle.Equals(nextRectangle)));


[Test]
public void CircularCloudLayouter_GetLocationAfterInitialization_ShouldBeEmpty()
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
{
var location = circularCloudLayouter.GetRectanglesLocation();
location.Should().BeEmpty();
}

[TestCase(-1, 10, TestName = "width is negative")]
[TestCase(1, -10, TestName = "height is negative")]
[TestCase(1, 0, TestName = "Zero height, correct width")]
[TestCase(0, 10, TestName = "Zero width, correct height")]
public void CircularCloudLayouter_PutRectangleWithNegativeParams_ShouldBeThrowException(int width, int height)
{
var size = new Size(width, height);
Action action = () => circularCloudLayouter?.PutNextRectangle(size);
action.Should().Throw<ArgumentException>()
.WithMessage("Sides of the rectangle should not be non-positive");
}

[Test]
public void CircularCloudLayouter_PutOneRectangle_IsNotEmpty()
{
var rect = circularCloudLayouter.PutNextRectangle(new Size(10, 10));
var location = circularCloudLayouter.GetRectanglesLocation();
location.Should().NotBeEmpty();
}

[Test]
public void CircularCloudLayouter_CreateSecondLayouterAfterAddingRectangle_IsNotEmpty()
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
{
var rect = circularCloudLayouter.PutNextRectangle(new Size(10, 10));
var circularCloudLayouter2 = new CircularCloudLayouter(new Point(12, 12));
circularCloudLayouter.Rectangles.Should().NotBeEmpty();
}

[TestCase(1000, TestName = "check intersection of 1000 rectangles")]
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
public void CircularCloudLayouter_PutNextRectangle_RectanglesShouldNotIntersect(int count)
{
for (var i = 0; i < count; i++)
{
var size = Utils.GetRandomSize();
var rect = circularCloudLayouter.PutNextRectangle(size);
}

isRectanglesIntersect(circularCloudLayouter.Rectangles).Should().BeFalse();
}
}
1 change: 1 addition & 0 deletions cs/TagsCloudTests/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using NUnit.Framework;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

что это и зачем?

Copy link
Author

@PavelUd PavelUd Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

в Задачи 3 написано сохранять файлы, если тест провалился

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 46 additions & 0 deletions cs/TagsCloudTests/RectanglesVisualizerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Drawing;
using FluentAssertions;
using tagsCloud;

namespace TagsCloudTests;

[TestFixture]
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
public class RectanglesVisualizerTests
{
[Test]
public void RectanglesVisualizer_ShouldNotBeException()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

этот тест ничего не проверяет

Copy link
Author

@PavelUd PavelUd Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

{
var rectangles = new List<Rectangle>();
var action = () =>
{
var visualizer = new RectanglesVisualizer(rectangles);
visualizer.DrawTagCloud();
};
action.Should().NotThrow();
}


[Test]
public void RectanglesVisualizer_DrawSomeRectangles_AllRectanglesInImage()
{
var count = 10;
var rectangles = new List<Rectangle>();
for (var i = 0; i < count; i++)
{
var locate = Utils.GetRandomLocation();
var size = Utils.GetRandomSize();
var rect = new Rectangle(locate, size);
rectangles.Add(rect);
}

var visualizer = new RectanglesVisualizer(rectangles);
var image = visualizer.DrawTagCloud();
CheckImageBorders(rectangles, image).Should().BeTrue();
}

private bool CheckImageBorders(List<Rectangle> rectangles, Bitmap image)
{
return rectangles.Max(rectangle => rectangle.Bottom) < image.Height &&
rectangles.Max(rectangle => rectangle.Right) < image.Width;
}
}
57 changes: 57 additions & 0 deletions cs/TagsCloudTests/SpiralTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Drawing;
using FluentAssertions;
using tagsCloud;

namespace TagsCloudTests;

[TestFixture]
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
public class SpiralTests
{
private Point center;

[SetUp]
public void Setup()
{
center = new Point(10, 10);
}

[Test]
public void Spiral_StepAngleEquals0_ShouldBeThrowException()
{
Action action = () => new Spiral(center, 0);
action.Should().Throw<ArgumentException>()
.WithMessage("angle step non be 0");
}

[Test]
public void Spiral_IsNotStaticParams_ShouldBeTrue()
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
{
var spiral = new Spiral(center);
var start = spiral.GetPoint();
var spiral2 = new Spiral(new Point(0, 0));
var start2 = spiral2.GetPoint();
start.Should().NotBe(start2);
}

[TestCase(0.5f,
new[] { 10, 10, 10, 10, 10, 9 },
new[] { 10, 10, 10, 11, 12, 13 },
TestName = "AngleStep is positive")]
[TestCase(-0.5f,
new[] { 10, 10, 10, 10, 10, 9 },
new[] { 10, 10, 10, 9, 8, 7 },
TestName = "AngleStep is negative")]
public void Spiral_GetNextPoint_CreatePointsWithCustomAngle_ReturnsCorrectPoints(float angleStep, int[] x, int[] y)
Copy link

@razor2651 razor2651 Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

во-первых: что мешает передать в тест сразу массив Point а не отдельно x и y координаты?
во-вторых: зачем тут такая проверка по 6 точек? поведение изменится что ли на 7 точке? разве нельзя указать шаг угла побольше? тогда у тебя и точки будут сдвигаться куда быстрее и уже вторая будет разительно отличаться, покажет направление движение спирали по координатам
если даже указать шаг 2, то первая же точка будет (8, 14) при положительном angleStep и (8, 6) при отрицательном

Copy link
Author

@PavelUd PavelUd Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

передать массив Point компилятор выдаст ошибку: An attribute argument must be a constant expression, 'typeof()' expression or array creation expression of an attribute parameter type.

Всё остальное справедливо. Уже у себя поправил

{
var spiral = new Spiral(new Point(10, 10), angleStep);
var expectedPoints = new Point[x.Length];
var resultPoints = new Point[x.Length];
for (var i = 0; i < x.Length; i++)
{
expectedPoints[i] = new Point(x[i], y[i]);
resultPoints[i] = spiral.GetPoint();
}

resultPoints.Should().BeEquivalentTo(expectedPoints);
}
}
25 changes: 25 additions & 0 deletions cs/TagsCloudTests/TagsCloudTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.0.0-alpha.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

если я это уберу, то тесты не запускаются

<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"/>
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\tagsCloud\tagsCloud.csproj" />
</ItemGroup>

</Project>
50 changes: 50 additions & 0 deletions cs/tagsCloud/CircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Drawing;

namespace tagsCloud;

public class CircularCloudLayouter(Point center) : ICircularCloudLayouter
{
public List<Rectangle> Rectangles { get; } = new();
private readonly Spiral _spiral = new(center);
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
private readonly Size _sizeZone = new(500, 500);
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
private Rectangle _zonePossibleIntersections;
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
private List<Rectangle> _nearestRectangles;

public Rectangle PutNextRectangle(Size rectangleSize)
{
if (rectangleSize.Height <= 0 || rectangleSize.Width <= 0)
throw new ArgumentException("Sides of the rectangle should not be non-positive");
var rect = CreateNextRectangle(rectangleSize);
Rectangles.Add(rect);
return rect;
}

private Rectangle CreateNextRectangle(Size rectangleSize)
{
var point = _spiral.GetPoint();
var rect = new Rectangle(point, rectangleSize);
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
GetNearestRectangles(point);
while (_nearestRectangles.Any(x => x.IntersectsWith(rect)))
{
point = _spiral.GetPoint();
rect = new Rectangle(point, rectangleSize);
if (!Utils.IsInside(rect, _zonePossibleIntersections))
GetNearestRectangles(point);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

что делает эта строчка? зачем этот код?

Copy link
Author

@PavelUd PavelUd Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Я думал, что если пробегать не по всем прямоугольниками, а только по ближайшим, это сильно сократит время выполнения программы. Но сделав сегодня замеры, я пришёл к выводу, что такая логика только замедляет работу программы, так как для начальных это не нужно(они и так в самом начале и Any сразу проверяет их) , а для последних оказалось легче просто проверить по очереди все прямоугольники, чем фильтровать по дистанции, а потом уже проверять по ближайшим

Но как оказалось, если проверять пресечение текущего прямоугольника не сначала массива всех прямоугольников, а с конца, то это существенно ускоряет программу. Что позволяет обработать 100000 и более прямоугольников

}

return rect;
}

private void GetNearestRectangles(Point point)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

метод называется Get но ничего не возвращает, что он делает из названия не понятно, да и вообще бесполезен

Copy link
Author

@PavelUd PavelUd Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

этот метод обновляет зону ближайших прямоугольников.

{
_zonePossibleIntersections =
new Rectangle(new Point(point.X - _sizeZone.Width / 2, point.Y - _sizeZone.Height / 2), _sizeZone);
_nearestRectangles = Rectangles.Where(x =>
x.IntersectsWith(_zonePossibleIntersections) || Utils.IsInside(_zonePossibleIntersections, x)).ToList();
}

public List<Point> GetRectanglesLocation()
{
return Rectangles.Select(rectangle => rectangle.Location).ToList();
}
}
10 changes: 10 additions & 0 deletions cs/tagsCloud/ICircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Drawing;

namespace tagsCloud;

public interface ICircularCloudLayouter
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
{
List<Rectangle> Rectangles { get; }
Rectangle PutNextRectangle(Size rectangleSize);
List<Point> GetRectanglesLocation();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

опять штука которая не нужна для отрисовки, только для тестов, при том там где она используется можно использовать свойство

Copy link
Author

@PavelUd PavelUd Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

}
24 changes: 24 additions & 0 deletions cs/tagsCloud/MainProgram.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Drawing;
using System.Drawing.Imaging;

namespace tagsCloud;

public class MainProgram
{
public static void Main(string[] args)
{
var layout = new CircularCloudLayouter(new Point(100, 100));
for (var i = 0; i < 10; i++)
{
var rectangle = layout.PutNextRectangle(Utils.GetRandomSize());
}

var visualizer = new RectanglesVisualizer(layout.Rectangles);
var workingDirectory = Environment.CurrentDirectory;
var projectDirectory = Directory.GetParent(workingDirectory).Parent.Parent.FullName;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Author

@PavelUd PavelUd Dec 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

12234

человек может сохранять изображение в любую свою папку.
Если же говориться про добавление фотографии в методе Main в MainProgram, то я так делаю, потому что результат программы(exe файл) находиться в tagsCloud\bin\Debug\net6.0.
Поэтому Directory.GetParent(workingDirectory).Parent.Parent.FullName;
никогда не вернет null, так как всегда при выполнении программы будут папки bin и Debug

var path = projectDirectory + @"\images\";
var imageName = "10rect";
var image = visualizer.DrawTagCloud();
image.Save($"{path}{imageName}.png", ImageFormat.Png);
}
}
4 changes: 4 additions & 0 deletions cs/tagsCloud/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
![10000 random rectangles](https://github.com/PavelUd/tdd/blob/master/cs/tagsCloud/images/10000rect.png)
![1000 random rectangles](https://github.com/PavelUd/tdd/blob/master/cs/tagsCloud/images/1000rect.png)
![100 random rectangles](https://github.com/PavelUd/tdd/blob/master/cs/tagsCloud/images/100rect.png)
![10 random rectangles](https://github.com/PavelUd/tdd/blob/master/cs/tagsCloud/images/10rect.png)
56 changes: 56 additions & 0 deletions cs/tagsCloud/RectanglesVisualizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Drawing;

namespace tagsCloud;

public class RectanglesVisualizer
{
private readonly List<Rectangle> rectangles;
private readonly Bitmap image;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

кто будет отпускать ресурсы? райдер/решарпер тебе должны прямо трубить об этом

private readonly Graphics graphics;
private Point shift;
private static readonly Size DefaultSize = new(100, 100);
private const int Border = 50;


public RectanglesVisualizer(List<Rectangle> rectangles)
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
{
this.rectangles = rectangles;
var sizeImage = PrepareImage();
image = new Bitmap(sizeImage.Width, sizeImage.Height);
graphics = Graphics.FromImage(image);
var background = new SolidBrush(Color.Black);
graphics.FillRectangle(background, new Rectangle(0, 0, image.Width, image.Height));
}

private Size PrepareImage()
{
if (!rectangles.Any())
return DefaultSize;

var leftmost = rectangles.Min(rectangle => rectangle.Left);
var rightmost = rectangles.Max(rectangle => rectangle.Right);
var topmost = rectangles.Min(rectangle => rectangle.Top);
var bottommost = rectangles.Max(rectangle => rectangle.Bottom);

var startX = topmost >= 0 ? 0 : topmost;
var startY = leftmost >= 0 ? 0 : leftmost;
shift = new Point(Math.Abs(startX) + Border, Math.Abs(startY) + Border);

var height = Math.Abs(bottommost) + Math.Abs(topmost) + 2 * Border;
var width = Math.Abs(rightmost) + Math.Abs(topmost) + 2 * Border;

return new Size(width, height);
}

public Bitmap DrawTagCloud()
{
foreach (var rectangle in rectangles)
{
var vizRect = new Rectangle(new Point(rectangle.X + shift.X, rectangle.Y + shift.Y), rectangle.Size);
PavelUd marked this conversation as resolved.
Show resolved Hide resolved
var pen = new Pen(Utils.GetRandomColor());
graphics.DrawRectangle(pen, vizRect);
}

return image;
}
}
Loading