using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Mapper
{
/// <summary>
/// Implements a two dimensional dynamic array with elements of type T.
/// </summary>
/// <typeparam name="T"></typeparam>
public class DynamicTwoDimensionalArray<T>
{
/// <summary>
/// Describes a row or column
/// </summary>
private struct Dimension
{
public short _size;
public short _index;
// The width of a column or the height of a row
public int Size
{
get { return (int)_size; }
set { _size = (short)value; }
}
// When a row or column is split, the new row is created at the end of the physical array rather than in the middle.
// That way, there is no need to copy lots of data. But it does mean you need indirection from the logical index
// to the physical index.
// This field provides the physical index.
public int Index
{
get { return (int)_index; }
set { _index = (short)value; }
}
}
// Describe the rows and columns
private Dimension[] _columns;
private Dimension[] _rows;
private T[,] _data;
// Number of logical columns in the 2 dimensional array
private int _nbrColumns = 0;
// Number of logical rows in the 2 dimensional array
private int _nbrRows = 0;
/// <summary>
/// Number of columns
/// </summary>
public int NbrColumns { get { return _nbrColumns; } }
/// <summary>
/// Number of rows
/// </summary>
public int NbrRows { get { return _nbrRows; } }
/// <summary>
/// Constructor
/// </summary>
public DynamicTwoDimensionalArray()
{
}
/// <summary>
/// After you've constructed the array, you need to initialize it.
///
/// This removes any content and creates the first cell - so the array
/// will have height is 1 and width is 1.
/// </summary>
/// <param name="capacityX">
/// The array will initially have capacity for at least this many columns.
/// Must be greater than 0.
/// Set to the expected maximum width of the array or greater.
/// The array will resize itself if you make this too small, but resizing is expensive.
/// </param>
/// <param name="capacityY">
/// The array will initially have capacity for at least this many rows.
/// Must be greater than 0.
/// Set to the expected maximum height of the array or greater.
/// The array will resize itself if you make this too small, but resizing is expensive.
/// </param>
/// <param name="firstColumnWidth">
/// Width of the first column.
/// </param>
/// <param name="firstRowHeight">
/// Width of the first column.
/// </param>
/// <param name="firstCellValue">
/// Width of the first column.
/// </param>
public void Initialize(int capacityX, int capacityY, int firstColumnWidth, int firstRowHeight, T firstCellValue)
{
if (capacityX <= 0) { throw new Exception("capacityX cannot be 0 or smaller"); }
if (capacityY == 0) { throw new Exception("capacityY cannot be 0 or smaller"); }
if ((_columns == null) || (_columns.GetLength(0) < capacityX))
{
_columns = new Dimension[capacityX];
}
if ((_rows == null) || (_rows.GetLength(0) < capacityY))
{
_rows = new Dimension[capacityY];
}
if ((_data == null) || (_data.GetLength(0) < capacityX) || (_data.GetLength(1) < capacityY))
{
_data = new T[capacityX, capacityY];
}
_nbrColumns = 1;
_nbrRows = 1;
_columns[0].Index = 0;
_columns[0].Size = firstColumnWidth;
_rows[0].Index = 0;
_rows[0].Size = firstRowHeight;
_data[0, 0] = firstCellValue;
}
/// <summary>
/// Returns the item at the given location.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public T Item(int x, int y)
{
return _data[_columns[x].Index, _rows[y].Index];
}
/// <summary>
/// Sets an item to the given value
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="value"></param>
public void SetItem(int x, int y, T value)
{
_data[_columns[x].Index, _rows[y].Index] = value;
}
/// <summary>
/// Inserts a row at location y.
/// If y equals 2, than all rows at y=3 and higher will now have y=4 and higher.
/// The new row will have y=3.
/// The contents of the row at y=2 will be copied to the row at y=3.
///
/// If there is not enough capacity in the array for the additional row,
/// than the internal data structure will be copied to a structure with twice the size
/// (this copying is expensive).
/// </summary>
/// <param name="y">
/// Identifies the row to be split.
/// </param>
/// <param name="heightNewRow">
/// The height of the new row (the one at y=3 in the example).
/// Must be smaller than the current height of the existing row.
///
/// The old row will have height = (old height of old row) - (height of new row).
/// </param>
public void InsertRow(int y, int heightNewRow)
{
if (y >= _nbrRows) { throw new Exception(string.Format("y is {0} but height is only {1}", y, _nbrRows)); }
// If there are as many phyiscal rows as there are logical rows, we need to get more physical rows before the number
// of logical rows can be increased.
if (_data.GetLength(1) == _nbrRows) { IncreaseCapacity(); }
// Copy the cells with the given y to a new row after the last used row. The y of the new row equals _nbrRows.
int physicalY = _rows[y].Index;
for (int x = 0; x < _nbrColumns; x++)
{
_data[x, _nbrRows] = _data[x, physicalY];
}
// Make room in the _rows array by shifting all items that come after the one indexed by y one position to the right.
// If y is at the end of the array, there is no need to shift anything.
if (y < (_nbrRows - 1)) { Array.Copy(_rows, y + 1, _rows, y + 2, (_nbrRows - y - 1)); }
// Let the freed up element point at the newly copied row
_rows[y + 1].Index = _nbrRows;
// Set the heights of the old and new rows.
int oldHeight = _rows[y].Size;
int newHeightExistingRow = oldHeight - heightNewRow;
Debug.Assert((heightNewRow > 0) && (newHeightExistingRow > 0));
_rows[y + 1].Size = heightNewRow;
_rows[y].Size = newHeightExistingRow;
// The logical height of the array has increased by 1.
_nbrRows++;
}
/// <summary>
/// Same as InsertRow, but than for columns.
/// </summary>
/// <param name="x"></param>
public void InsertColumn(int x, int widthNewColumn)
{
if (x >= _nbrColumns) { throw new Exception(string.Format("x is {0} but width is only {1}", x, _nbrColumns)); }
// If there are as many phyiscal columns as there are logical columns, we need to get more physical columns before the number
// of logical columns can be increased.
if (_data.GetLength(0) == _nbrColumns) { IncreaseCapacity(); }
// Copy the cells with the given x to a new column after the last used column. The x of the new column equals _nbrColumns.
int nbrPhysicalRows = _data.GetLength(1);
int physicalX = _columns[x].Index;
Array.Copy(_data, physicalX * nbrPhysicalRows, _data, _nbrColumns * nbrPhysicalRows, _nbrRows);
// Make room in the _columns array by shifting all items that come after the one indexed by x one position to the right.
// If x is at the end of the array, there is no need to shift anything.
if (x < (_nbrColumns - 1)) { Array.Copy(_columns, x + 1, _columns, x + 2, (_nbrColumns - x - 1)); }
// Let the freed up element point at the newly copied column
_columns[x + 1].Index = _nbrColumns;
// Set the widths of the old and new columns.
int oldWidth = _columns[x].Size;
int newWidthExistingColumn = oldWidth - widthNewColumn;
Debug.Assert((widthNewColumn > 0) && (newWidthExistingColumn > 0));
_columns[x + 1].Size = widthNewColumn;
_columns[x].Size = newWidthExistingColumn;
// The logical width of the array has increased by 1.
_nbrColumns++;
}
/// <summary>
/// Returns the width of the column at the given location
/// </summary>
/// <param name="y"></param>
/// <returns></returns>
public int ColumnWidth(int x)
{
return _columns[x].Size;
}
/// <summary>
/// Returns the height of the row at the given location
/// </summary>
/// <param name="y"></param>
/// <returns></returns>
public int RowHeight(int y)
{
return _rows[y].Size;
}
/// <summary>
/// Doubles the capacity of the internal data structures.
///
/// Creates a new array with double the width and height of the old array.
/// Copies the element of the old array to the new array.
/// Then replaces the old array with the new array.
/// </summary>
private void IncreaseCapacity()
{
int oldCapacityX = _data.GetLength(0);
int oldCapacityY = _data.GetLength(1);
int newCapacityX = oldCapacityX * 2;
int newCapacityY = oldCapacityY * 2;
int nbrItemsToCopy = oldCapacityX * oldCapacityY;
T[,] newData = new T[newCapacityX, newCapacityY];
Array.Copy(_data, newData, nbrItemsToCopy);
_data = newData;
}
/// <summary>
/// Represents the DynamicTowDimensionalArray as a string.
/// </summary>
/// <returns></returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.Append(" X ");
for (int x = 0; x < _nbrColumns + 1; x++) { sb.AppendFormat(" {0,2:G} ", x); }
sb.AppendLine();
sb.Append("Y ");
for (int x = 0; x < _nbrColumns + 1; x++) { sb.AppendFormat(" ({0,3:G})", ColumnWidth(x)); }
sb.AppendLine();
for (int y = 0; y < _nbrRows + 1; y++)
{
sb.AppendFormat("{0,2:G} ({1,3:G}) ", y, RowHeight(y));
for (int x = 0; x < _nbrColumns + 1; x++)
{
sb.AppendFormat(" {0} ", Item(x, y));
}
sb.AppendLine();
}
return sb.ToString();
}
}
}