Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,21 @@ FakesAssemblies/
*.opt

*Solved.cs

!TagsCloudApp_Tests/[Bb]in/
TagsCloudApp_Tests/[Bb]in/*
!TagsCloudApp_Tests/[Bb]in/[Dd]ebug/
TagsCloudApp_Tests/[Bb]in/[Dd]ebug/*
!TagsCloudApp_Tests/[Bb]in/[Dd]ebug/net8.0-windows/
TagsCloudApp_Tests/[Bb]in/[Dd]ebug/net8.0-windows/*
!TagsCloudApp_Tests/[Bb]in/[Dd]ebug/net8.0-windows/expected/


!TagsCloudConsoleInterface/[Bb]in/
TagsCloudConsoleInterface/[Bb]in/*
!TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/
TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/*
!TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/net8.0-windows/
TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/net8.0-windows/*
!TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/net8.0-windows/in.txt
!TagsCloudConsoleInterface/[Bb]in/[Dd]ebug/net8.0-windows/exclude.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System.Drawing;

namespace RectanglesCloudPositioning.Configs;

public record RectanglesPositioningConfig(Point Center, int RaysCount, Func<double, double> RadiusEquation);
14 changes: 14 additions & 0 deletions RectanglesCloudPositioning/Direction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace RectanglesCloudPositioning;

internal enum Direction
{
None = 0,

Left = 1,

Right = 2,

Up = 3,

Down = 4,
}
21 changes: 21 additions & 0 deletions RectanglesCloudPositioning/ICloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Drawing;
using FluentResults;

namespace RectanglesCloudPositioning;

public interface ICloudLayouter
{
public Result<Rectangle> PutNextRectangle(Size rectangleSize)
{
return Result
.FailIf(
rectangleSize.Width <= 0 || rectangleSize.Height <= 0,
new Error("Width and height must be greater than zero."))
.Bind(() => Result
.Try(
() => PutNextRectangleOrThrow(rectangleSize),
ex => new Error($"Cannot put a rectangle of size {rectangleSize}. {ex.Message}")));
}

protected Rectangle PutNextRectangleOrThrow(Size rectangleSize);
}
18 changes: 18 additions & 0 deletions RectanglesCloudPositioning/NoEqualityComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace RectanglesCloudPositioning;

internal class NoEqualityComparer<T> : IComparer<T>
where T : IComparable<T>
{
public int Compare(T? x, T? y)
{
if (x == null)
{
return -1;
}

var comparison = x.CompareTo(y);
return comparison == 0
? -1
: comparison;
}
}
21 changes: 21 additions & 0 deletions RectanglesCloudPositioning/RectanglesCloudPositioning.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

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

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentResults" Version="3.16.0" />
</ItemGroup>

</Project>
88 changes: 88 additions & 0 deletions RectanglesCloudPositioning/ShapedCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using FluentResults;
using RectanglesCloudPositioning.Configs;
using System.Drawing;

namespace RectanglesCloudPositioning;

public class ShapedCloudLayouter : ICloudLayouter
{
private readonly List<Rectangle> rectangles = [];

private readonly Func<double, double> radiusEquation;
private readonly Point center;
private readonly int raysCount;

private IEnumerator<Point> pointsEnumerator;
private int radius;

public ShapedCloudLayouter(RectanglesPositioningConfig config)
: this(config.Center, config.RaysCount, config.RadiusEquation)
{
}

public ShapedCloudLayouter(Point center, int raysCount, Func<double, double> radiusEquation)
{
ArgumentNullException.ThrowIfNull(radiusEquation);

this.center = center;
this.raysCount = raysCount;
this.radiusEquation = radiusEquation;

pointsEnumerator = new[] { center }.AsEnumerable().GetEnumerator();
}

Rectangle ICloudLayouter.PutNextRectangleOrThrow(Size rectangleSize)
{
var rectangle = GetRectangleToPut(rectangleSize);
rectangles.Add(rectangle);
return rectangle;
}

private Rectangle GetRectangleToPut(Size rectangleSize)
{
while (pointsEnumerator.MoveNext())
{
var point = pointsEnumerator.Current - rectangleSize / 2;
var rectangle = new Rectangle(point, rectangleSize);

if (CanPut(rectangle))
{
return rectangle;
}
}

radius++;
pointsEnumerator = GetPoints().GetEnumerator();
return GetRectangleToPut(rectangleSize);
}

private bool CanPut(Rectangle rectangle)
{
return rectangles
.All(otherRectangle => !otherRectangle.IntersectsWith(rectangle));
}

private IEnumerable<Point> GetPoints()
{
if (radius == 0)
{
yield return center;
yield break;
}

var step = 2 * Math.PI / raysCount;

for (var angle = .0; angle < 2 * Math.PI; angle += step)
{
var shapeRadius = radius * radiusEquation(angle);
yield return GetPoint(shapeRadius, angle);
}
}

private Point GetPoint(double radius, double angle)
{
var x = (int)(radius * Math.Cos(angle));
var y = (int)(radius * Math.Sin(angle));
return new Point(x + center.X, y + center.Y);
}
}
81 changes: 81 additions & 0 deletions RectanglesCloudPositioning/SortedRectanglesList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Drawing;

namespace RectanglesCloudPositioning;

/// <summary>
/// Вспомогательная структура для хранения прямоугольников, отсортированных по координатам сторон,
/// с возможностью получения прямоугольника по индексу в отсортированном списке.
/// </summary>
internal class SortedRectanglesList
{
private readonly Dictionary<Direction, SortedList<int, Rectangle>> sortedRectangles;

public SortedRectanglesList()
{
var noEqualityComparer = new NoEqualityComparer<int>();

sortedRectangles = new Dictionary<Direction, SortedList<int, Rectangle>>(4)
{
{ Direction.Left, new(noEqualityComparer) },
{ Direction.Right, new(noEqualityComparer) },
{ Direction.Up, new(noEqualityComparer) },
{ Direction.Down, new(noEqualityComparer) },
};
}

public int Count { get; private set; }

public void Add(Rectangle rectangle)
{
sortedRectangles[Direction.Left].Add(-rectangle.Right, rectangle);
sortedRectangles[Direction.Right].Add(rectangle.Left, rectangle);
sortedRectangles[Direction.Up].Add(-rectangle.Bottom, rectangle);
sortedRectangles[Direction.Down].Add(rectangle.Top, rectangle);

Count++;
}

public Rectangle Get(Direction sortingDirection, int index)
{
if (!sortedRectangles.TryGetValue(sortingDirection, out var rectangles))
{
throw new ArgumentException($"Unsupported sorting direction: {sortingDirection}.");
}

if (index < 0 || index >= Count)
{
throw new IndexOutOfRangeException($"Index was out of range: {index}.");
}

return rectangles.Values[index];
}

public bool HasIntersection(
Rectangle rectangle,
Direction sortingDirection,
int startIndex,
out int intersectedRectangleIndex)
{
if (!sortedRectangles.TryGetValue(sortingDirection, out var rectangles))
{
throw new ArgumentException($"Unsupported sorting direction: {sortingDirection}.");
}

if (startIndex < 0)
{
throw new IndexOutOfRangeException($"Index was out of range: {startIndex}.");
}

for (var i = startIndex; i < rectangles.Count; i++)
{
if (rectangle.IntersectsWith(rectangles.Values[i]))
{
intersectedRectangleIndex = i;
return true;
}
}

intersectedRectangleIndex = -1;
return false;
}
}
104 changes: 104 additions & 0 deletions RectanglesCloudPositioning/SpiralCircularCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using RectanglesCloudPositioning.Configs;
using System.Drawing;

namespace RectanglesCloudPositioning;

public class SpiralCircularCloudLayouter : ICloudLayouter
{
private readonly SortedRectanglesList rectangles = new();
private readonly Dictionary<Direction, int> directionIndices = [];

private readonly Point center;
private readonly int raysCount;

private IEnumerator<(Point Point, Direction Direction)> pointsEnumerator;
private int radius;

public SpiralCircularCloudLayouter(RectanglesPositioningConfig config)
: this(config.Center, config.RaysCount)
{
}

public SpiralCircularCloudLayouter(Point center, int raysCount)
{
this.center = center;
this.raysCount = raysCount;
pointsEnumerator = Enumerable
.Empty<(Point, Direction)>()
.GetEnumerator();
}

Rectangle ICloudLayouter.PutNextRectangleOrThrow(Size rectangleSize)
{
var rectangle = GetRectangleToPut(rectangleSize);
rectangles.Add(rectangle);
return rectangle;
}

private Rectangle GetRectangleToPut(Size rectangleSize)
{
while (pointsEnumerator.MoveNext())
{
var direction = pointsEnumerator.Current.Direction;
var point = pointsEnumerator.Current.Point - rectangleSize / 2;
var rectangle = new Rectangle(point, rectangleSize);

if (CanPut(rectangle, direction))
{
ResetDirectionIndices();
return rectangle;
}
}

radius++;
ResetDirectionIndices();
pointsEnumerator = GetPoints().GetEnumerator();
return GetRectangleToPut(rectangleSize);
}

private bool CanPut(Rectangle rectangle, Direction direction)
{
var wideRectangle = direction is Direction.Left or Direction.Right
? new Rectangle(new Point(rectangle.X, int.MinValue / 2), new Size(rectangle.Width, int.MaxValue))
: new Rectangle(new Point(int.MinValue / 2, rectangle.Y), new Size(int.MaxValue, rectangle.Height));

if (rectangles.HasIntersection(wideRectangle, direction, directionIndices[direction], out var intersectionIndex))
{
directionIndices[direction] = intersectionIndex;

return !rectangles.HasIntersection(rectangle, direction, intersectionIndex, out _);
}

return true;
}

private IEnumerable<(Point, Direction)> GetPoints()
{
if (radius == 0)
{
yield return (center, Direction.None);
yield break;
}

var step = 2 * Math.PI / raysCount;

for (var angle = Math.PI / 4; angle < 3 * Math.PI / 4; angle += step)
{
var x = (int)(radius * Math.Cos(angle));
var y = (int)(radius * Math.Sin(angle));

yield return (new Point(x, y), Direction.Left);
yield return (new Point(-y, x), Direction.Up);
yield return (new Point(-x, -y), Direction.Right);
yield return (new Point(y, -x), Direction.Down);
}
}

private void ResetDirectionIndices()
{
directionIndices[Direction.Left] = 0;
directionIndices[Direction.Right] = 0;
directionIndices[Direction.Up] = 0;
directionIndices[Direction.Down] = 0;
}
}
Loading