Click here to Skip to main content
15,886,199 members
Articles / Programming Languages / C# 4.0

The List Trifecta, Part 2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
7 Sep 2013LGPL310 min read 28.6K   317   12  
The BDictionary is like a Dictionary mashed up with List<T>. BList and BMultiMap also say hello.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Loyc.Math
{
	public interface ISizeReader<T>
	{
		/// <summary>Gets the width of a rectangle (the difference between X coordinates).</summary>
		T Width { get; }
		/// <summary>Gets the height of a rectangle (the difference between Y coordinates).</summary>
		T Height { get; }
	}

	/// <summary>Represents a read-only 2D rectangle.</summary>
	/// <remarks>
	/// The relationship between X1, X2 and Width, and the relationship between 
	/// Y1, Y2 and Height, depends on whether the object represents a normal 
	/// (traditional Windows) rectangle, a bounding rectangle, or a pair of 
	/// points. A traditional rectangle defines its Height as Y2 - Y1, and its
	/// Width as X2 - X1. A bounding rectangle is defined slightly differently:
	/// the Width is X2 - X1 + e, and the Height is Y2 - Y1 + e, where e is an
	/// infitessimal value of type T (e=1 if T is an integer type).
	/// <para/>
	/// Also, a traditional rectangle is stored as a starting point (X1, Y1)
	/// and a size (Width, Height), while a bounding rectangle stores X2 and Y2 
	/// instead, not the Width and Height.
	/// <para/>
	/// Finally, this interface could simply represent a pair of points. In 
	/// that case, Width and Height return the absolute value of X2-X1 and 
	/// Y2-Y1 respectively.
	/// <para/>
	/// A rectangle may or may not require X2 >= X1 and Y2 >= Y1. If X1>X2 or
	/// Y1>Y2, the rectangle is said to be "not normalized" and the 
	/// <see cref="RectangleExt.IsNormal"/> extension method returns false.
	/// </remarks>
	public interface IRectangleReader<T> : ISizeReader<T>
	{
		T X1 { get; }
		T Y1 { get; }
		T X2 { get; }
		T Y2 { get; }
	}

	/// <summary>Represents a mutable 2D rectangle.</summary>
	/// <typeparam name="T">Type of each coordinate.</typeparam>
	/// <remarks>
	/// This interface can represent one of three types of rectangles: either 
	/// "normal" rectangles with fundamental variables X, Y, Width, Height, 
	/// bounding rectangles with two coordinate ranges (X1-X2) and (Y1-Y2), or
	/// or pairs of points (X1, Y1) and (X2, Y2).
	/// <para/>
	/// Because of this fact, it is not clear when you modify X1 whether this
	/// should affect X2 or Width. Similarly, when you modify X2, Y1 or Y2, it
	/// is not clear whether the size property or the opposite boundary should 
	/// change. To resolve this question, this interface does not allow you to
	/// modify the coordinates individually; instead you must change them in 
	/// pairs: you either set X and Width together, or X1 and X2 together; and
	/// similarly Y and Height together, or Y1 and Y2 together.
	/// <para/>
	/// You can also call the extension methods SetRect() or SetRange() to set
	/// all four coordinates at once.
	/// </remarks>
	/// <seealso cref="IBoundingBox{T}"/>
	public interface IRectangleBase<T> : IRectangleReader<T>
	{
		void SetXAndWidth(T x, T width);
		void SetYAndHeight(T y, T height);
		void SetXRange(T x1, T x2);
		void SetYRange(T x1, T x2);
	}
	
	/// <summary>This interface exists to work around a limitation of C#; see
	/// <see cref="IRectangle{T}"/>.</summary>
	public interface INewRectangle<Rect, T>
	{
		Rect NewRect(T x, T y, T width, T height);
		Rect NewRange(T x1, T y1, T x2, T y2);
	}
	
	/// <summary>Represents a mutable 2D rectangle.</summary>
	/// <remarks>
	/// This interface is separated into two bases, 
	/// <see cref="IRectangleBase{T}"/> and <see cref="INewRectangle{R,T}"/>, 
	/// for the same reason that <see cref="IPoint{T}"/>'s coordinates are 
	/// divided into <see cref="IPointBase{T}"/> and <see cref="INewPoint{P,T}"/>,
	/// as explained in the documentation of <see cref="IPoint{T}"/>. 
	/// </remarks>
	public interface IRectangle<T> : IRectangleBase<T>, INewRectangle<IRectangle<T>, T>
	{
	}

	/// <summary>Contains methods to manipulate rectangles.</summary>
	/// <remarks>Unfortunately, C# often can't infer the type parameters. Many of 
	/// these methods must be called with explicit type parameters.</remarks>
	public static class RectangleExt
	{
		public static bool IsNormal<Rect, T>(this Rect r)
			where Rect : IRectangleReader<T>
			where T : IComparable<T>
		{
			// Hey Microsoft, this would probably be faster if the built-in types 
			// implemented simple boolean methods: IsLess(), IsLessOrEqual().
			return r.X2.CompareTo(r.X1) >= 0 && r.Y2.CompareTo(r.Y1) >= 0;
		}
		public static void Normalize<Rect, T>(this Rect r)
			where Rect : IRectangleBase<T>
			where T : IComparable<T>
		{
			T x1 = r.X1, x2 = r.X2;
			if (r.X2.CompareTo(r.X1) < 0)
				r.SetXRange(x2, x1);
			T y1 = r.Y1, y2 = r.Y2;
			if (r.Y2.CompareTo(r.Y1) < 0)
				r.SetYRange(x2, x1);
		}

		/// <summary>Computes the union of two <i>normalized</i> rectangles,
		/// i.e. a rectangle large enough to cover both original rectangles.</summary>
		/// <remarks>The results may be incorrect if one or both rectangles 
		/// are not normalized.</remarks>
		public static Rect Union<Rect,T>(this Rect a, Rect b)
			where Rect : IRectangleBase<T>, INewRectangle<Rect, T>
			where T : IComparable<T>
		{
			return a.NewRange(MathEx.Min(a.X1, b.X1), MathEx.Min(a.Y1, b.Y1),
							   MathEx.Max(a.X2, b.X2), MathEx.Max(a.Y2, b.Y2));
		}

		/// <summary>Computes the intersection of two <i>normalized</i> rectangles,
		/// i.e. the region covered by both original rectangles.</summary>
		/// <remarks>The results may be incorrect if one or both rectangles are
		/// not normalized. If the rectangles do not overlap, a non-normalized 
		/// rectangle is returned.</remarks>
		public static Rect Intersect<Rect,T>(this Rect a, Rect b)
			where Rect : IRectangleBase<T>, INewRectangle<Rect, T>
			where T : IComparable<T>
		{
			return a.NewRange(MathEx.Max(a.X1, b.X1), MathEx.Max(a.Y1, b.Y1),
							   MathEx.Min(a.X2, b.X2), MathEx.Min(a.Y1, b.Y1));
		}

		/// <summary>Expands a rectangle's boundaries to include a given point.</summary>
		/// <returns>Returns true if the rectangle changed.</returns>
		public static bool ExpandToInclude<Rect,Point,T>(this Rect a, Point b)
			where Point : IPointReader<T>
			where Rect : IRectangleBase<T>, INewRectangle<Rect, T>
			where T : IComparable<T>
		{
			T x1 = a.X1, y1 = a.Y1;
			T x2 = a.X2, y2 = a.Y2;
			bool changed = false;
			if (x1.CompareTo(b.X) > 0) {
				x1 = b.X;
				changed = true;
			}
			if (x2.CompareTo(b.X) < 0) {
				x2 = b.X;
				changed = true;
			}
			if (y1.CompareTo(b.Y) > 0) {
				y1 = b.Y;
				changed = true;
			}
			if (y2.CompareTo(b.Y) < 0) {
				y2 = b.Y;
				changed = true;
			}
			if (changed)
				a.SetRange(x1, y1, x2, y2);
			return changed;
		}
		
		/// <summary>Expands a rectangle's boundaries to include a series of points.</summary>
		/// <returns>Returns true if the rectangle changed.</returns>
		public static bool ExpandToInclude<Rect, Point, T>(this Rect a, IEnumerable<Point> bs)
			where Point : IPointReader<T>
			where Rect : IRectangleBase<T>, INewRectangle<Rect, T>
			where T : IComparable<T>
		{
			bool changed = false;
			foreach (Point b in bs)
				changed |= ExpandToInclude<Rect, Point, T>(a, b);
			return changed;
		}

		/// <summary>Determines whether a rectangle contains a given point.</summary>
		/// <remarks>Returns false if the rectangle is not normalized.</remarks>
		public static bool Contains<Rect, Point, T>(this Rect a, Point b)
			where Point : IPointReader<T>
			where Rect : IRectangleBase<T>, INewRectangle<Rect, T>
			where T : IComparable<T>
		{
			return a.X1.CompareTo(b.X) <= 0 && a.X2.CompareTo(b.X) >= 0 &&
				   a.Y1.CompareTo(b.Y) <= 0 && a.Y2.CompareTo(b.Y) >= 0;
		}
		
		/// <summary>Determines whether a rectangle fully contains another rectangle.</summary>
		/// <remarks>May not work correctly if either of the rectangles is not normalized.</remarks>
		public static bool Contains<Rect, T>(this Rect a, Rect b)
			where Rect : IRectangleBase<T>, INewRectangle<Rect, T>
			where T : IComparable<T>
		{
			return a.X1.CompareTo(b.X1) <= 0 && a.X2.CompareTo(b.X2) >= 0 &&
				   a.Y1.CompareTo(b.Y1) <= 0 && a.Y2.CompareTo(b.Y2) >= 0;
		}
		
		/// <summary>Determines whether a rectangle overlaps another rectangle.</summary>
		/// <remarks>May not work correctly if either of the rectangles is not normalized.</remarks>
		public static bool Overlaps<Rect, T>(this Rect a, Rect b)
			where Rect : IRectangleBase<T>, INewRectangle<Rect, T>
			where T : IComparable<T>
		{
			return IsNormal<Rect, T>(Intersect<Rect, T>(a, b));
		}

		public static void SetRect<Rect, T>(this Rect r, T x, T y, T width, T height)
			where Rect : IRectangleBase<T>
		{
			r.SetXAndWidth(x, width);
			r.SetYAndHeight(y, height);
		}
		public static void SetRange<Rect, T>(this Rect r, T x1, T y1, T x2, T y2)
			where Rect : IRectangleBase<T>
		{
			r.SetXRange(x1, x2);
			r.SetYRange(y1, y2);
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer None
Canada Canada
Since I started programming when I was 11, I wrote the SNES emulator "SNEqr", the FastNav mapping component, the Enhanced C# programming language (in progress), the parser generator LLLPG, and LES, a syntax to help you start building programming languages, DSLs or build systems.

My overall focus is on the Language of your choice (Loyc) initiative, which is about investigating ways to improve interoperability between programming languages and putting more power in the hands of developers. I'm also seeking employment.

Comments and Discussions