My solution for the Rainfall Challenge (August 2016 Community Challenge).
Error handling was intentionally ignored.
Cell.cs
public class Cell
{
private readonly int myRowIndex;
private readonly int myColIndex;
private readonly int myValue;
public Cell(int value, int rowIndex, int colIndex)
{
myRowIndex = rowIndex;
myColIndex = colIndex;
myValue = value;
}
public int RowIndex => myRowIndex;
public int ColIndex => myColIndex;
public int Value => myValue;
public bool IsSink { get; set; }
public Cell Top { get; set; }
public Cell Right { get; set; }
public Cell Bottom { get; set; }
public Cell Left { get; set; }
public Cell Next { get; set; }
}
Program.cs
Main
static void Main(string[] args)
{
var data = ReadData();
var cells = CreateCells(data).ToArray();
ConnectCells(cells, data.GetLength(0));
SetIsSink(cells);
SetNext(cells);
var basins = GetBasins(cells);
Console.WriteLine(string.Join(" ", basins));
}
ReadData
private static int[,] ReadData()
{
var size = int.Parse(Console.ReadLine());
var result = new int[size, size];
for (int row = 0; row < size; row++)
{
var rowData = Console.ReadLine().Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries).ToArray();
for (int col = 0; col < size; col++)
result[row, col] = int.Parse(rowData[col]);
}
return result;
}
CreateCells
private static IEnumerable<Cell> CreateCells(int[,] data)
{
for (int row = 0; row < data.GetLength(0); row++)
for (int col = 0; col < data.GetLength(1); col++)
yield return new Cell(data[row, col], row, col);
}
ConnectCells
private static void ConnectCells(Cell[] cells, int size)
{
foreach (var cell in cells)
{
var indexLeft = GetIndexLeft(cell, size);
var indexRight = GetIndexRight(cell, size);
var indexTop = GetIndexTop(cell, size);
var indexBottom = GetIndexBottom(cell, size);
if (indexLeft > -1) cell.Left = cells[indexLeft];
if (indexRight > -1) cell.Right = cells[indexRight];
if (indexTop > -1) cell.Top = cells[indexTop];
if (indexBottom > -1) cell.Bottom = cells[indexBottom];
}
}
private static int GetIndexTop(Cell cell, int size)
{
if (cell.RowIndex == 0)
return -1;
return (cell.RowIndex - 1) * size + cell.ColIndex;
}
private static int GetIndexRight(Cell cell, int size)
{
if (cell.ColIndex + 1 == size)
return -1;
return cell.RowIndex * size + cell.ColIndex + 1;
}
private static int GetIndexLeft(Cell cell, int size)
{
if (cell.ColIndex == 0)
return -1;
return cell.RowIndex * size + cell.ColIndex - 1;
}
private static int GetIndexBottom(Cell cell, int size)
{
if (cell.RowIndex + 1 == size)
return -1;
return (cell.RowIndex + 1) * size + cell.ColIndex;
}
SetIsSink
private static void SetIsSink(Cell[] cells)
{
foreach (var cell in cells)
cell.IsSink = (cell.Left == null || cell.Left.Value > cell.Value) &&
(cell.Right == null || cell.Right.Value > cell.Value) &&
(cell.Top == null || cell.Top.Value > cell.Value) &&
(cell.Bottom == null || cell.Bottom.Value > cell.Value);
}
SetNext
private static void SetNext(Cell[] cells)
{
foreach (var cell in cells.Where(c => !c.IsSink))
cell.Next = GetNext(cell);
}
private static Cell GetNext(Cell cell)
{
var cells = new[] {cell.Left, cell.Right, cell.Top, cell.Bottom};
return cells.Where(c => c != null)
.OrderBy(c => c.Value)
.First();
}
GetBasins
private static IEnumerable<int> GetBasins(Cell[] cells)
{
var visited = new HashSet<Cell>();
var basin = cells.Where(c => c.IsSink).ToDictionary(c => c, c => 1);
foreach (var cell in cells.Where(c => !c.IsSink))
{
var current = cell;
int count = 0;
while (!current.IsSink)
{
if (visited.Add(current)) count++;
current = current.Next;
}
basin[current] += count;
}
return basin.Values.OrderByDescending(v => v);
}
yield return
in CreateCells when the only caller's first action is to do a ToArray() on it. Just make CreateCells make an array \$\endgroup\$yield
has been introduced with C# 2.0 (2005)... so it's not fancy but a well-established language element ;). In general I prefer yield compared to introducing a local variable because it simplifies the code. However, the usage in methodCreateCells
is close to the edge... \$\endgroup\$