Шагов Владислав#272
Conversation
| EndProject | ||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples", "Samples\Samples.csproj", "{B5108E20-2ACF-4ED9-84FE-2A718050FC94}" | ||
| EndProject | ||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagsCloudVisualization", "TagsCloudVisualization\TagsCloudVisualization.csproj", "{2EDAFD2B-C793-47E2-941E-1B9111073AB7}" |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| [SetUp] | ||
| public void SetUp() | ||
| { | ||
| generatedRectSizes = []; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| var layouter = new CircularCloudLayouter(center); | ||
|
|
||
| var rect = layouter.PutNextRectangle(size); | ||
| generatedRectSizes.Add(size); |
There was a problem hiding this comment.
Лишняя строчка, тест без неё работает прекрасно.
|
|
||
| [SupportedOSPlatform("windows")] | ||
|
|
||
| public static class CloudVisualizer |
There was a problem hiding this comment.
Ответственность этого класса - создать изборажение по заданным параметрам и сохранить в файл. При этом за пользователя очень много чего выбирается по-умолчанию:
- расположение центра
- размер изображения
- цвет фона
- цвета прямоугольников
- и т.д.
Хорошенько подумай, что можно захотеть настраивать в изображении!
Далее - недавно вы, кажется, проходили fluent interface'ы, помнишь такие? Давай применим новые знания и навыки: во-первых, давай создадим класс-билдер для CloudVisualizer - пусть билдер хранит разнообразные настройки и для каждой имеет fluent-метод для их изменения (например, метод SetCenter мог бы принимать параметры настраиваемого центра); во-вторых, пусть билдер имеет метод Build или Create (или что-то в этом духе), который будет создавать экземпляр CloudVisualizer с указанными настройками. Подумай, что должен делать билдер, если пользователь определил не все настройки, но вызывает метод Build? Spoiler: здесь могут быть разные решения разной сложности.
| namespace TagsCloudVisualization; | ||
|
|
||
| [SupportedOSPlatform("windows")] | ||
|
|
This comment was marked as duplicate.
This comment was marked as duplicate.
Sorry, something went wrong.
| foreach (var size in sizes) | ||
| layouter.PutNextRectangle(size); | ||
|
|
||
| layouter.PlacedRectangles.Count.Should().Be(sizes.Count); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| var file = Path.Combine(outputDir, "many.png"); | ||
|
|
||
| var sizes = Enumerable | ||
| .Range(1, 200) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| } | ||
|
|
||
| [Test, Explicit] | ||
| public void VizualizeExamples() |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| } | ||
|
|
||
| [Test] | ||
| public void PutNextRectangle_ShouldNotCrossCenter() |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| public void PutNextRectangle_ShouldThrow_OnNegativeSize() | ||
| { | ||
| var layouter = new CircularCloudLayouter(new Point(0, 0)); | ||
| var size = new Size(-10, 20); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| { | ||
| while (true) | ||
| { | ||
| var point = spiral.GetNextPoint(); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| { | ||
| for (var dy = -radiusCells; dy <= radiusCells && yielded < MaxTouchNeighbors; dy++) | ||
| { | ||
| if (!grid.TryGetValue((cx + dx, cy + dy), out var list)) continue; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
|
||
| public sealed class CircularCloudLayouter | ||
| { | ||
| private readonly Point center; |
There was a problem hiding this comment.
Константны всё равно потом примешал в конец
| public CircularCloudLayouter(Point center) | ||
| { | ||
| this.center = center; | ||
| centerX = center.X; |
There was a problem hiding this comment.
в том плане что можно Point сделать?
There was a problem hiding this comment.
У тебя и center и X,Y имеют модификатор readonly, т.е. ты их не перепресваиваешь. Почему вместо X не использовать center.X?
| public Rectangle PutNextRectangle(Size rectangleSize) | ||
| { | ||
| if (rectangleSize.Width <= 0 || rectangleSize.Height <= 0) | ||
| throw new ArgumentException("Rectangle size must be positive"); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| var cx = centerX / CellSize; | ||
| var cy = centerY / CellSize; | ||
|
|
||
| for (var dx = -radiusCells; dx <= radiusCells && yielded < MaxTouchNeighbors; dx++) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| } | ||
| } | ||
|
|
||
| if (yielded >= MaxTouchNeighbors) yield break; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| if (yielded < MaxTouchNeighbors && placedRectangles.Count > 0) | ||
| { | ||
| var last = placedRectangles[^1]; | ||
| foreach (var r in GetRectanglesAround(last, maxCells: 4)) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
|
||
| private IEnumerable<Rectangle> GetRectanglesAround(Rectangle r, int maxCells) | ||
| { | ||
| var x1 = Math.Max((r.Left / CellSize) - maxCells, -1000000); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
|
||
| for (var x = x1; x <= x2; x++) | ||
| for (var y = y1; y <= y2; y++) | ||
| if (grid.TryGetValue((x, y), out var list)) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| for (var x = x1; x <= x2; x++) | ||
| for (var y = y1; y <= y2; y++) | ||
| if (grid.TryGetValue((x, y), out var list)) | ||
| foreach (var rr in list) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| return Math.Abs(bestDist - double.MaxValue) < 0.00000001 ? FindBySpiralFallback(size) : best; | ||
| } | ||
|
|
||
| private void AddInHashAndCandidates(Rectangle c) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| return div; | ||
| } | ||
|
|
||
| private static int HashRect(Rectangle r) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| } | ||
| } | ||
|
|
||
| private double DistanceToCenterInt(Rectangle r) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| { | ||
| for (var i = 0; i < 20000; i++) | ||
| { | ||
| var p = spiral.GetNextPoint(); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
|
||
| private Rectangle FindBySpiralFallback(Size size) | ||
| { | ||
| for (var i = 0; i < 20000; i++) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| return r; | ||
| } | ||
|
|
||
| return new Rectangle(centerX + 50000, centerY + 50000, size.Width, size.Height); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
|
||
| private double DistanceToCenterInt(Rectangle r) | ||
| { | ||
| var dx = r.X + (r.Width >> 1) - centerX; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
|
||
| private static IEnumerable<(int, int)> GetCells(Rectangle r) | ||
| { | ||
| var x1 = FloorDiv(r.Left, CellSize); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| cellBuffer.AddRange(list); | ||
| } | ||
|
|
||
| return cellBuffer.Any(t => rect.IntersectsWith(t)); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
скорость сильно падает
на 500 прямоугольниках 150мс против 250мс
There was a problem hiding this comment.
Сомнительно - IL код один и тот же должен получаться. Проверишь в качестве факультатива?
There was a problem hiding this comment.
Ок, признаю, был не прав :) Похоже действительно делегат под лямбду компилятор один раз создаёт, а вот под метод групп конвершн - на каждой итерации.
| } | ||
| } | ||
|
|
||
| private bool IntersectsGrid(Rectangle rect) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| var dx = rect.X + (rect.Width >> 1) < centerX ? -1 : 1; | ||
| var dy = rect.Y + (rect.Height >> 1) < centerY ? -1 : 1; | ||
|
|
||
| var leftRight = rect with { X = rect.X - dx }; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| while (moved && iter++ < PullMaxIterations) | ||
| { | ||
| moved = false; | ||
| var dx = rect.X + (rect.Width >> 1) < centerX ? -1 : 1; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| { | ||
| if (dx == 0 && dy == 0) return rect; | ||
|
|
||
| for (var i = 0; i < 1000; i++) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| rect = shifted; | ||
| } | ||
|
|
||
| for (var i = 0; i < 3000; i++) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| // left | ||
| yield return new Rectangle( | ||
| o.Left - size.Width, | ||
| o.Top + ((o.Height - size.Height) >> 1), |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
|
|
||
| if (candidatesBuffer.Count > MaxCandidatesToEvaluate) | ||
| { | ||
| candidatesBuffer.Sort((a, b) => DistanceToCenterInt(a).CompareTo(DistanceToCenterInt(b))); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
There was a problem hiding this comment.
candidatesBuffer.Count - MaxCandidatesToEvaluate - как это лаконично обозвать?
|
|
||
| if (candidatesBuffer.Count == 0) | ||
| { | ||
| foreach (var c in GetSpiralCandidates(size, 40)) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| Color? rectangleBorderColor = null, | ||
| string? outputDirectory = null) | ||
| { | ||
| try |
There was a problem hiding this comment.
Сейчас внутри try точно можно поимать ранее пойманные (и залоггированные!) исключения - оберни в try только то, что нужно.
| throw new ArgumentException("File name cannot be null or empty", nameof(fileName)); | ||
|
|
||
| var extension = Path.GetExtension(fileName); | ||
| if (string.IsNullOrEmpty(extension)) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| nameof(fileName)); | ||
| } | ||
|
|
||
| if (string.IsNullOrWhiteSpace(outputDirectory)) |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
| var fullPath = Path.GetFullPath(outputDirectory); | ||
| Console.WriteLine($"Output directory resolved to: {fullPath}"); | ||
| } | ||
| catch (Exception ex) |
There was a problem hiding this comment.
А зачем перевыбрасывать-то его? И вообще - у тебя снаружи есть try-catch. Если ты здесь записал, то прерви выполнение, или наверху оставь большой try-catch и перехватывай всё там. Строго говоря, директория-то может быть синтаксически правильной, а ошибка связана с, например, правами доступа.
| Pen? pen = null, | ||
| int padding = 50) | ||
| { | ||
| try |
There was a problem hiding this comment.
Во, опять огромный внутренний try-catch - давай в одном месте наверху ошибки записывать и прекращать выполнение, а тут не пытаться всё обернуть?
| } | ||
|
|
||
| [Test] | ||
| public void PutNextRectangle_ShouldBeFast_For500Rectangles() |
There was a problem hiding this comment.
Периодически падает на этих тестах :) Но там рядом с границей, да
| { | ||
| var sizes = new List<Size> { new(10, 10) }; | ||
|
|
||
| var act = () => |


@Dimques