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
10 changes: 10 additions & 0 deletions Result/FPTool.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

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

</Project>
8 changes: 8 additions & 0 deletions Result/None.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace FPTool;

public class None
{
private None()
{
}
}
2 changes: 2 additions & 0 deletions Result/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
110 changes: 110 additions & 0 deletions Result/Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
namespace FPTool;

public struct Result<T>
{
public Result(string? error, T? value = default)
{
Error = error;
Value = value;
}
public static implicit operator Result<T>(T v)
{
return Result.Ok(v);
}

public string? Error { get; }
internal T? Value { get; }
public T GetValueOrThrow()
{
if (IsSuccess) return Value!;
throw new InvalidOperationException($"No value. Only Error {Error}");
}
public bool IsSuccess => Error == null;
}

public static class Result
{
public static Result<T> AsResult<T>(this T value)
{
return Ok(value);
}

public static Result<T> Ok<T>(T value)
{
return new Result<T>(null, value);
}
public static Result<None> Ok()
{
return new Result<None>(null);
}

public static Result<T> Fail<T>(string e)
{
return new Result<T>(e);
}

public static Result<T> Of<T>(Func<T> f, string? error = null)
{
try
{
return Ok(f());
}
catch (Exception e)
{
return Fail<T>(error ?? e.Message);
}
}

public static Result<None> OfAction(Action f, string? error = null)
{
try
{
f();
return Ok();
}
catch (Exception e)
{
return Fail<None>(error ?? e.Message);
}
}

public static Result<TOutput> Then<TInput, TOutput>(
this Result<TInput> input,
Func<TInput, TOutput> continuation)
=> input.Then(inp => Of(() => continuation(inp)));

public static Result<None> Then<TInput, TOutput>(
this Result<TInput> input,
Action<TInput> continuation)
=> input.Then(inp => OfAction(() => continuation(inp)));

public static Result<None> Then<TInput>(
this Result<TInput> input,
Action<TInput> continuation)
=> input.Then(inp => OfAction(() => continuation(inp)));

public static Result<TOutput> Then<TInput, TOutput>(
this Result<TInput> input,
Func<TInput, Result<TOutput>> continuation)
=> input.IsSuccess
? continuation(input.Value!)
: Fail<TOutput>(input.Error!);

public static Result<TInput> OnFail<TInput>(
this Result<TInput> input,
Action<string> handleError)
{
if (!input.IsSuccess) handleError(input.Error!);
return input;
}

public static Result<TInput> ReplaceError<TInput>(
this Result<TInput> input,
Func<string, string> replaceError)
=> input.IsSuccess ? input : Fail<TInput>(replaceError(input.Error!));

public static Result<TInput> RefineError<TInput>(
this Result<TInput> input,
string errorMessage)
=> input.ReplaceError(err => errorMessage + ". " + err);
}
44 changes: 44 additions & 0 deletions TagCloud/CloudGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using FPTool;
using TagCloud.ImageGenerator;
using TagCloud.ImageSaver;
using TagCloud.WordsFilter;
using TagCloud.WordsReader;

namespace TagCloud;

public class CloudGenerator(
IImageSaver saver,
IWordsReader reader,
BitmapGenerator imageGenerator,
IEnumerable<IWordsFilter> filters)
{
private const int MIN_FONT_SIZE = 10;
private const int MAX_FONT_SIZE = 80;

public Result<string> GenerateTagCloud()
=> reader
.ReadWords()
.Then(BuildFreqDict)
.Then(ToWordTagList)
.Then(imageGenerator.GenerateWindowsBitmap)
.Then(saver.Save);

private static Result<List<WordTag>> ToWordTagList(Dictionary<string, int> freqDict)
=> freqDict.Values.Max().AsResult().Then(
m => freqDict.Select(p => ToWordTag(p, m)).ToList());

private Result<Dictionary<string, int>> BuildFreqDict(List<string> words)
=> ApplyFilters(words).Then(wl => wl
.GroupBy(w => w)
.OrderByDescending(g => g.Count())
.ToDictionary(g => g.Key, g => g.Count()));

private Result<List<string>> ApplyFilters(List<string> words)
=> filters.Aggregate(words.AsResult(), (c, f) => c.Then(f.ApplyFilter));

private static int TransformFreqToSize(int freq, int maxFreq)
=> (int)(MIN_FONT_SIZE + (float)freq / maxFreq * (MAX_FONT_SIZE - MIN_FONT_SIZE));

private static WordTag ToWordTag(KeyValuePair<string, int> pair, int maxFreq)
=> new(pair.Key, TransformFreqToSize(pair.Value, maxFreq));
}
9 changes: 9 additions & 0 deletions TagCloud/CloudLayouter/ICloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Drawing;
using FPTool;

namespace TagCloud.CloudLayouter;

public interface ICloudLayouter
{
public Result<Rectangle> PutNextRectangle(Size rectangleSize);
}
44 changes: 44 additions & 0 deletions TagCloud/CloudLayouter/PointLayouter/PointCloudLayouter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Drawing;
using FPTool;
using TagCloud.CloudLayouter.PointLayouter.PointGenerator;
using TagCloud.CloudLayouter.Settings;

namespace TagCloud.CloudLayouter.PointLayouter;

public class PointCloudLayouter(Point center, IPointGenerator pointGenerator) : ICloudLayouter
{
private readonly List<Point> placedPoints = [];
private readonly List<Rectangle> placedRectangles = [];

public PointCloudLayouter(PointLayouterSettings settings)
: this(settings.Center, settings.Generator)
{ }

public Point Center { get; } = center;

public Result<Rectangle> PutNextRectangle(Size rectangleSize)
=> TryPutNext(rectangleSize)
.Then(RememberRectangle)
.RefineError("There are no more points in generator");

private Rectangle RememberRectangle(Rectangle rect)
{
placedRectangles.Add(rect);
placedPoints.Add(rect.Location - rect.Size / 2);
return rect;
}

private Result<Rectangle> TryPutNext(Size rectangleSize)
=> pointGenerator.StartFrom(Center)
.Then(pointEnumerable => pointEnumerable
.Except(placedPoints)
.Select(p => CreateRectangle(p, rectangleSize))
.First(r => !placedRectangles.Any(r.IntersectsWith))
);

private static Rectangle CreateRectangle(Point center, Size rectangleSize)
{
var rectangleUpperLeft = center - rectangleSize / 2;
return new Rectangle(rectangleUpperLeft, rectangleSize);
}
}
24 changes: 24 additions & 0 deletions TagCloud/CloudLayouter/PointLayouter/PointGenerator/Direction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace TagCloud.CloudLayouter.PointLayouter.PointGenerator;

public enum Direction
{
Up,
Right,
Down,
Left,
}

internal static class DirectionExtensions
{
public static Direction AntiClockwiseRotate(this Direction direction)
{
return direction switch
{
Direction.Up => Direction.Left,
Direction.Left => Direction.Down,
Direction.Down => Direction.Right,
Direction.Right => Direction.Up,
_ => throw new ArgumentOutOfRangeException(nameof(direction), direction, null)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Drawing;
using FPTool;
using TagCloud.CloudLayouter.Settings.Generators;

namespace TagCloud.CloudLayouter.PointLayouter.PointGenerator.Generators;

public class PolarArchimedesSpiral(double radius, double angleOffset) : IPointGenerator
{
public double Radius { get; } = radius;
public double AngleOffset { get; } = angleOffset * Math.PI / 180;

public PolarArchimedesSpiral(PolarSpiralSettings settings)
: this(settings.Radius, settings.AngleOffset)
{ }

public Result<IEnumerable<Point>> StartFrom(Point startPoint)
{
if (Radius <= 0 || angleOffset <= 0)
{
var argName = Radius <= 0 ? nameof(radius) : nameof(angleOffset);
return Result.Fail<IEnumerable<Point>>($"Spiral params should be positive: {argName}");
}
return PointGenerator(startPoint).AsResult();
}

private IEnumerable<Point> PointGenerator(Point startPoint)
{
var currentAngle = 0.0;
while (true)
{
var polarCoordinate = Radius / (2 * Math.PI) * currentAngle;

var xOffset = (int)Math.Round(polarCoordinate * Math.Cos(currentAngle));
var yOffset = (int)Math.Round(polarCoordinate * Math.Sin(currentAngle));

yield return startPoint + new Size(xOffset, yOffset);

currentAngle += AngleOffset;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Drawing;
using FPTool;
using TagCloud.CloudLayouter.Settings.Generators;

namespace TagCloud.CloudLayouter.PointLayouter.PointGenerator.Generators;

public class SquareArchimedesSpiral(int step) : IPointGenerator
{
public int Step { get; } = step;

public SquareArchimedesSpiral(SquareSpiralSettings settings)
: this(settings.Step)
{ }

public Result<IEnumerable<Point>> StartFrom(Point startPoint)
=> Step > 0
? PointGenerator(startPoint).AsResult()
: Result.Fail<IEnumerable<Point>>("Step should be positive number");

private IEnumerable<Point> PointGenerator(Point startPoint)
{
var neededPoints = 1;
var pointsToPlace = 1;
var direction = Direction.Up;
var currentPoint = startPoint;

while (true)
{
yield return currentPoint;

pointsToPlace--;
currentPoint += GetOffsetSize(direction);

if (pointsToPlace == 0)
{
direction = direction.AntiClockwiseRotate();
if (direction is Direction.Up or Direction.Down) neededPoints++;
pointsToPlace = neededPoints;
}
}
}

private Size GetOffsetSize(Direction direction) => direction switch
{
Direction.Up => new Size(0, Step),
Direction.Right => new Size(Step, 0),
Direction.Down => new Size(0, -Step),
Direction.Left => new Size(-Step, 0),
_ => throw new ArgumentOutOfRangeException(nameof(direction))
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Drawing;
using FPTool;

namespace TagCloud.CloudLayouter.PointLayouter.PointGenerator;

public interface IPointGenerator
{
public Result<IEnumerable<Point>> StartFrom(Point startPoint);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TagCloud.CloudLayouter.Settings.Generators;

public record PolarSpiralSettings(double Radius, double AngleOffset);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TagCloud.CloudLayouter.Settings.Generators;

public enum PossibleGenerators
{
POLAR_SPIRAL,
SQUARE_SPIRAL,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace TagCloud.CloudLayouter.Settings.Generators;

public record SquareSpiralSettings(int Step);
6 changes: 6 additions & 0 deletions TagCloud/CloudLayouter/Settings/PointLayouterSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System.Drawing;
using TagCloud.CloudLayouter.PointLayouter.PointGenerator;

namespace TagCloud.CloudLayouter.Settings;

public record PointLayouterSettings(Point Center, IPointGenerator Generator);
Loading