After stumbling upon this question on StackOverflow, I started playing with some extension methods to trim null objects from a bi-dimensional array.
This is what I've came with so far:
public static class TrimArray {
/// <summary>
/// Trims the outer layers of null objects;
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>An array with the outer layers of null objects trimmed.</returns>
public static TSource[,] Trim<TSource>( this TSource[,] sourceArray ) where TSource : class {
return sourceArray.CalculateTrim( true, true, true, true );
}
/// <summary>
/// Trims the outer top and bottom layers of null objects;
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>An array with the outer top and bottom layers of null objects trimmed.</returns>
public static TSource[,] TrimVertical<TSource>( this TSource[,] sourceArray ) where TSource : class {
return sourceArray.CalculateTrim( true, false, false, true );
}
/// <summary>
/// Trims the outer left and right layers of null objects;
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>An array with the outer left and right layers of null objects trimmed.</returns>
public static TSource[,] TrimHorizontal<TSource>( this TSource[,] sourceArray ) where TSource : class {
return sourceArray.CalculateTrim( false, true, true, false );
}
/// <summary>
/// Trims the outer top layers of null objects;
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>An array with the outer top layers of null objects trimmed.</returns>
public static TSource[,] TrimTop<TSource>( this TSource[,] sourceArray ) where TSource : class {
return sourceArray.CalculateTrim( true, false, false, false );
}
/// <summary>
/// Trims the outer left layers of null objects;
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>An array with the outer left layers of null objects trimmed.</returns>
public static TSource[,] TrimLeft<TSource>( this TSource[,] sourceArray ) where TSource : class {
return sourceArray.CalculateTrim( false, true, false, false );
}
/// <summary>
/// Trims the outer right layers of null objects;
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>An array with the outer right layers of null objects trimmed.</returns>
public static TSource[,] TrimRight<TSource>( this TSource[,] sourceArray ) where TSource : class {
return sourceArray.CalculateTrim( false, false, true, false );
}
/// <summary>
/// Trims the outer bottom layers of null objects;
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>An array with the outer bottom layers of null objects trimmed.</returns>
public static TSource[,] TrimBottom<TSource>( this TSource[,] sourceArray ) where TSource : class {
return sourceArray.CalculateTrim( false, false, false, true );
}
/// <summary>
/// Calculates the layers to be trimmed.
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <param name="trimTop">True to trim the top of the array, otherwise false.</param>
/// <param name="trimLeft">True to trim the left of the array, otherwise false.</param>
/// <param name="trimRight">True to trim the right of the array, otherwise false.</param>
/// <param name="trimBottom">True to trim the bottom of the array, otherwise false.</param>
/// <returns>An array with the outer layers of null objects trimmed.</returns>
private static TSource[,] CalculateTrim<TSource>( this TSource[,] sourceArray, Boolean trimTop, Boolean trimLeft, Boolean trimRight, Boolean trimBottom ) where TSource : class {
if( sourceArray == null
|| ( sourceArray.GetLength( 0 ) == 0 && sourceArray.GetLength( 1 ) == 0 )
|| ( !trimTop && !trimLeft && !trimRight && !trimBottom ) ) {
return sourceArray;
}
Int32
top = 0,
left = 0,
right = sourceArray.GetLength( 1 ) - 1,
bottom = sourceArray.GetLength( 0 ) - 1;
if( trimTop ) {
top = sourceArray.CalculateTrimTop();
}
if( trimLeft ) {
left = sourceArray.CalculateTrimLeft();
}
if( trimRight ) {
right = sourceArray.CalculateTrimRight();
}
if( trimBottom ) {
bottom = sourceArray.CalculateTrimBottom();
}
return sourceArray.Trim( top, left, right, bottom );
}
/// <summary>
/// Calculates the top limit to be trimmed.
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>The top limit to be trimmed.</returns>
private static Int32 CalculateTrimTop<TSource>( this TSource[,] sourceArray ) where TSource : class {
for( Int32 yIndex = 0, yIndexLimit = sourceArray.GetLength( 0 ); yIndex < yIndexLimit; yIndex++ ) {
for( Int32 xIndex = 0, xIndexLimit = sourceArray.GetLength( 1 ); xIndex < xIndexLimit; xIndex++ ) {
if( sourceArray[ yIndex, xIndex ] != null ) {
return yIndex;
}
}
}
return 0;
}
/// <summary>
/// Calculates the left limit to be trimmed.
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>The left limit to be trimmed.</returns>
private static Int32 CalculateTrimLeft<TSource>( this TSource[,] sourceArray ) where TSource : class {
for( Int32 xIndex = 0, xIndexLimit = sourceArray.GetLength( 1 ); xIndex < xIndexLimit; xIndex++ ) {
for( Int32 yIndex = 0, yIndexLimit = sourceArray.GetLength( 0 ); yIndex < yIndexLimit; yIndex++ ) {
if( sourceArray[ yIndex, xIndex ] != null ) {
return xIndex;
}
}
}
return 0;
}
/// <summary>
/// Calculates the right limit to be trimmed.
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>The right limit to be trimmed.</returns>
private static Int32 CalculateTrimRight<TSource>( this TSource[,] sourceArray ) where TSource : class {
for( Int32 xIndex = sourceArray.GetLength( 1 ) - 1; xIndex >= 0; xIndex-- ) {
for( Int32 yIndex = sourceArray.GetLength( 0 ) - 1; yIndex >= 0; yIndex-- ) {
if( sourceArray[ yIndex, xIndex ] != null ) {
return xIndex;
}
}
}
return sourceArray.GetLength( 1 ) - 1;
}
/// <summary>
/// Calculates the bottom limit to be trimmed.
/// </summary>
/// <typeparam name="TSource">A <see cref="Nullable"/> type object.</typeparam>
/// <param name="sourceArray">The source array to be trimmed</param>
/// <returns>The bottom limit to be trimmed.</returns>
private static Int32 CalculateTrimBottom<TSource>( this TSource[,] sourceArray ) where TSource : class {
for( Int32 yIndex = sourceArray.GetLength( 0 ) - 1; yIndex >= 0; yIndex-- ) {
for( Int32 xIndex = sourceArray.GetLength( 1 ) - 1; xIndex >= 0; xIndex-- ) {
if( sourceArray[ yIndex, xIndex ] != null ) {
return yIndex;
}
}
}
return sourceArray.GetLength( 0 ) - 1;
}
/// <summary>
/// Trims an array
/// </summary>
/// <typeparam name="TSource">The array object type.</typeparam>
/// <param name="sourceArray">The source array to be trimmed.</param>
/// <param name="top">The exclusive top limit to start the vertical trim.</param>
/// <param name="left">The exclusive left limit to start the horizontal trim.</param>
/// <param name="right">The exclusive right limit to end the horizontal trim.</param>
/// <param name="bottom">The exclusive bottom limit to end the vertical trim.</param>
/// <returns>The array trimmed.</returns>
public static TSource[,] Trim<TSource>( this TSource[,] sourceArray, Int32 top, Int32 left, Int32 right, Int32 bottom ) {
if( sourceArray == null || ( sourceArray.GetLength( 0 ) == 0 && sourceArray.GetLength( 1 ) == 0 ) ) {
return sourceArray;
}
if( top > bottom ) {
throw new ArgumentException( $"'{nameof( top )}' must be lower or equal to '{nameof( bottom )}'" );
}
if( left > right ) {
throw new ArgumentException( $"'{nameof( left )}' must be lower or equal to '{nameof( right )}'" );
}
if( top < 0 ) {
throw new ArgumentException( $"'{nameof( top )}' ({top}) must be greater or equal to 0", nameof( top ) );
}
if( left < 0 ) {
throw new ArgumentException( $"'{nameof( left )}' ({left}) must be greater or equal to 0", nameof( left ) );
}
if( right >= sourceArray.GetLength( 1 ) ) {
throw new ArgumentException( $"'{nameof( right )}' ({right}) must be lower than the {nameof( sourceArray )} 1-dimension ({sourceArray.GetLength( 1 )})", nameof( right ) );
}
if( bottom >= sourceArray.GetLength( 0 ) ) {
throw new ArgumentException( $"'{nameof( bottom )}' ({bottom}) must be lower than the {nameof( sourceArray )} 0-dimension ({sourceArray.GetLength( 0 )})", nameof( bottom ) );
}
TSource[,]
trimmedArray = new TSource[ bottom - top + 1, right - left + 1 ];
for( Int32 yIndex = 0; yIndex < trimmedArray.GetLength( 0 ); yIndex++ ) {
for( Int32 xIndex = 0; xIndex < trimmedArray.GetLength( 1 ); xIndex++ ) {
trimmedArray[ yIndex, xIndex ] = sourceArray[ top + yIndex, left + xIndex ];
}
}
return trimmedArray;
}
}
So far, these methods only perform the basic trimming -- it searches for boundaries where a non null objects are present, and limits to those limits to create a trimmed array.
Things that I'm absolutely sure I need to improve: Methods summaries -- I still need to learn better ways to explain the inputs and outputs plus limitations to it.
Thoughts on this?