Skip to content
Merged
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@
### Collections

- Bag : A List with fast removing but no consistency indexing
- Array2D : A generic 2d array
- Grid2 : A generic 2d array
- NamedBag : A dictionary with a string key associated to a generic value

### Types

- Size : A struct to represent a size with width and height
22 changes: 22 additions & 0 deletions src/Ugtk.Foster/Types/SizeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Foster.Framework;
using System.Drawing;

namespace Ugtk.Foster.Types;

/// <summary>
/// Provides extension methods for the <see cref="Size"/> struct.
/// </summary>
public static class SizeExtensions
{
/// <summary>
/// Converts a <see cref="Size"/> object to a <see cref="Point2"/> object.
/// </summary>
/// <param name="size">The <see cref="Size"/> instance to convert.</param>
/// <returns>
/// A <see cref="Point2"/> object with the same width and height as the <see cref="Size"/> instance.
/// </returns>
public static Point2 ToPoint2(this Size size)
{
return new Point2(size.Width, size.Height);
}
}
18 changes: 13 additions & 5 deletions src/Ugtk/Collections/Array2d.cs → src/Ugtk/Collections/Grid2.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;

namespace Ugtk.Collections;

/// <summary>
/// Store objects in a 2 dimensional array
/// </summary>
/// <typeparam name="T">The type of object to store in the array</typeparam>
public sealed class Array2d<T> : IEnumerable<T>
public sealed class Grid2<T> : IEnumerable<T>
{
private readonly T[] _items;

Expand Down Expand Up @@ -39,17 +40,24 @@ public sealed class Array2d<T> : IEnumerable<T>
/// </summary>
/// <param name="columnsCount">The number of columns</param>
/// <param name="rowsCount">The number of rows</param>
/// <exception cref="ArgumentException"></exception>
public Array2d(int columnsCount, int rowsCount)
/// <exception cref="ArgumentOutOfRangeException"></exception>
public Grid2(int columnsCount, int rowsCount)
{
if (columnsCount <= 0) throw new ArgumentException("The number of columns must be greater than zero", nameof(columnsCount));
if (rowsCount <= 0) throw new ArgumentException("The number of rows must be greater than zero", nameof(rowsCount));
if (columnsCount <= 0) throw new ArgumentOutOfRangeException("The number of columns must be greater than zero", nameof(columnsCount));
if (rowsCount <= 0) throw new ArgumentOutOfRangeException("The number of rows must be greater than zero", nameof(rowsCount));

_items = new T[columnsCount * rowsCount];
ColumnsCount = columnsCount;
RowsCount = rowsCount;
}

/// <summary>
/// Contruct a new 2d array
/// </summary>
/// <param name="size">The size of the array</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public Grid2(Size size) : this(size.Width, size.Height) { }

/// <summary>
/// Get the array enumerator
/// </summary>
Expand Down
142 changes: 142 additions & 0 deletions src/Ugtk/Collections/NamedBag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;

namespace Ugtk.Collections;


/// <summary>
/// Represents a collection of named items that supports fast retrieval by name.
/// </summary>
/// <typeparam name="TItem">The type of items stored in the collection.</typeparam>
public sealed class NamedBag<TItem>
{
/// <summary>
/// Represents an item with a name and a value.
/// </summary>
private readonly struct NamedItem<T>(string name, T? item) : IComparable<NamedItem<T>>
{
public readonly string Name = name;
public readonly T Item = item!;

/// <summary>
/// Initializes a new instance of the <see cref="NamedItem{T}"/> struct with a name.
/// </summary>
/// <param name="name">The name of the item.</param>
public NamedItem(string name) : this(name, default) { }

/// <summary>
/// Compares the current instance with another <see cref="NamedItem{T}"/> based on the name.
/// </summary>
/// <param name="other">The other named item to compare.</param>
/// <returns>An integer indicating the relative order of the items.</returns>
public int CompareTo(NamedItem<T> other)
{
return Name.CompareTo(other.Name);
}
}

private readonly List<NamedItem<TItem>> _items = [];
private readonly List<string> _namesToBeAdded= [];
private readonly List<TItem> _itemsToBeAdded = [];

/// <summary>
/// Adds a new item to the collection.
/// </summary>
/// <param name="name">The name of the item.</param>
/// <param name="item">The item to add.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="name"/> or <paramref name="item"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="name"/> is empty or whitespace.</exception>
public void Add(string name, TItem item)
{
Add(name, item, true);
}

/// <summary>
/// Adds a new item to the collection.
/// </summary>
/// <param name="name">The name of the item.</param>
/// <param name="item">The item to add.</param>
/// <param name="mustSort">Indicates whether the collection should be sorted after adding the item.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="name"/> or <paramref name="item"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="name"/> is empty or whitespace.</exception>
private void Add(string name, TItem item, bool mustSort)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name cannot be empty or whitespace.", nameof(name));
}

ArgumentNullException.ThrowIfNull(item);

_items.Add(new NamedItem<TItem>(name, item));

if (mustSort)
{
_items.Sort();
}
}

/// <summary>
/// Begins a batch operation for adding items to the collection.
/// </summary>
public NamedBag<TItem> BatchBeginAdd()
{
_namesToBeAdded.Clear();
_itemsToBeAdded.Clear();

return this;
}

/// <summary>
/// Adds a new item to the batch.
/// </summary>
/// <param name="name">The name of the item.</param>
/// <param name="item">The item to add.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="name"/> or <paramref name="item"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="name"/> is empty or whitespace.</exception>
public NamedBag<TItem> BatchAdd(string name, TItem item)
{
_namesToBeAdded.Add(name);
_itemsToBeAdded.Add(item);

return this;
}

/// <summary>
/// Ends the batch operation and adds all items to the collection.
/// </summary>
public void BatchEndAdd()
{
for(int i = 0; i < _namesToBeAdded.Count; i++)
{
Add(_namesToBeAdded[i], _itemsToBeAdded[i], false);
}

_items.Sort();
}

/// <summary>
/// Removes all items from the collection.
/// </summary>
public void Clear()
{
_items.Clear();
}

/// <summary>
/// Retrieves an item by its name.
/// </summary>
/// <param name="name">The name of the item to retrieve.</param>
/// <returns>The item associated with the specified name.</returns>
/// <exception cref="KeyNotFoundException">Thrown if no item with the specified name exists.</exception>
public TItem GetValue(string name)
{
var index = _items.BinarySearch(new NamedItem<TItem>(name));
if (index < 0)
{
throw new KeyNotFoundException($"The named item `{name}` cannot be found.");
}

return _items[index].Item;
}
}
85 changes: 85 additions & 0 deletions src/Ugtk/Types/Size.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;

namespace Ugtk.Types;

/// <summary>
/// Represents a size with a width and a height.
/// </summary>
public readonly struct Size : IEquatable<Size>
{
/// <summary>
/// Gets the width of the size.
/// </summary>
public int Width { get; }

/// <summary>
/// Gets the height of the size.
/// </summary>
public int Height { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Size"/> struct.
/// </summary>
/// <param name="width">The width of the size.</param>
/// <param name="height">The height of the size.</param>
/// <exception cref="ArgumentOutOfRangeException">When width or height is not positive</exception>
public Size(int width, int height)
{
if (width < 0)
{
throw new ArgumentOutOfRangeException(nameof(width), "Width must be non-negative.");
}

if (height < 0)
{
throw new ArgumentOutOfRangeException(nameof(height), "Height must be non-negative.");
}

Width = width;
Height = height;
}

/// <summary>
/// Determines whether the current instance is equal to another <see cref="Size"/> instance.
/// </summary>
/// <param name="other">The other <see cref="Size"/> instance to compare.</param>
/// <returns>
/// <c>true</c> if the two instances have the same <see cref="Width"/> and <see cref="Height"/>; otherwise, <c>false</c>.
/// </returns>
public bool Equals(Size other) => (Width, Height) == (other.Width, other.Height);

/// <summary>
/// Determines whether the current instance is equal to a specified object.
/// </summary>
/// <param name="obj">The object to compare with the current instance.</param>
/// <returns>
/// <c>true</c> if <paramref name="obj"/> is a <see cref="Size"/> and is equal to the current instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object? obj) => obj is Size size && Equals(size);

/// <summary>
/// Returns a hash code for the current instance.
/// </summary>
/// <returns>A hash code for the current instance.</returns>
public override int GetHashCode() => HashCode.Combine(Width, Height);

/// <summary>
/// Determines whether two <see cref="Size"/> instances are equal.
/// </summary>
/// <param name="left">The first <see cref="Size"/> instance.</param>
/// <param name="right">The second <see cref="Size"/> instance.</param>
/// <returns>
/// <c>true</c> if the two instances are equal; otherwise, <c>false</c>.
/// </returns>
public static bool operator ==(Size left, Size right) => left.Equals(right);

/// <summary>
/// Determines whether two <see cref="Size"/> instances are not equal.
/// </summary>
/// <param name="left">The first <see cref="Size"/> instance.</param>
/// <param name="right">The second <see cref="Size"/> instance.</param>
/// <returns>
/// <c>true</c> if the two instances are not equal; otherwise, <c>false</c>.
/// </returns>
public static bool operator !=(Size left, Size right) => !(left == right);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace Ugtk.Tests.Collections
{
public sealed class Array2dTests
public sealed class Grid2Tests
{
[Theory]
[InlineData(3, 2)]
Expand All @@ -15,7 +15,7 @@ public sealed class Array2dTests
public void Constructor_ShouldInitializeArray_WithCorrectDimensions(int columns, int rows)
{
// Act
var array2d = new Array2d<int>(columns, rows);
var array2d = new Grid2<int>(columns, rows);

// Assert
Check.That(array2d.ColumnsCount).IsEqualTo(columns);
Expand All @@ -29,15 +29,15 @@ public void Constructor_ShouldInitializeArray_WithCorrectDimensions(int columns,
public void Constructor_ShouldThrowArgumentException_WhenDimensionsAreInvalid(int columns, int rows)
{
// Act & Assert
Check.ThatCode(() => new Array2d<int>(columns, rows))
Check.ThatCode(() => new Grid2<int>(columns, rows))
.Throws<ArgumentException>();
}

[Fact]
public void Indexer_ShouldSetAndGetValuesCorrectly()
{
// Arrange
var array2d = new Array2d<string>(2, 2);
var array2d = new Grid2<string>(2, 2);
var value = "TestValue";

// Act
Expand All @@ -51,7 +51,7 @@ public void Indexer_ShouldSetAndGetValuesCorrectly()
public void Indexer_ShouldThrowIndexOutOfRangeException_WhenAccessingInvalidIndex()
{
// Arrange
var array2d = new Array2d<int>(2, 2);
var array2d = new Grid2<int>(2, 2);

// Act & Assert
Check.ThatCode(() => array2d[2, 2] = 42)
Expand All @@ -63,7 +63,7 @@ public void GetEnumerator_ShouldEnumerateAllItems()
{
// Arrange

var array2d = new Array2d<int>(2, 2);
var array2d = new Grid2<int>(2, 2);
array2d[0, 0] = 1;
array2d[1, 0] = 2;
array2d[0, 1] = 3;
Expand Down
Loading