August 2016 challenge
The Rainfall Challenge
Problem description is copied verbatim from the linked Code Review question:
Problem Statement
A group of farmers has some elevation data, and we're going to help them understand how rainfall flows over their farmland.
We'll represent the land as a two-dimensional array of altitudes and use the following model, based on the idea that water flows downhill:
If a cell’s four neighboring cells all have higher altitudes, we call this cell a sink; water collects in sinks.
Otherwise, water will flow to the neighboring cell with the lowest altitude. If a cell is not a sink, you may assume it has a unique lowest neighbor and that this neighbor will be lower than the cell.
Cells that drain into the same sink – directly or indirectly – are said to be part of the same basin.
Your challenge is to partition the map into basins. In particular, given a map of elevations, your code should partition the map into basins and output the sizes of the basins, in descending order.
Assume the elevation maps are square. Input will begin with a line with one integer, S, the height (and width) of the map. The next S lines will each contain a row of the map, each with S integers – the elevations of the S cells in the row. Some farmers have small land plots such as the examples below, while some have larger plots. However, in no case will a farmer have a plot of land larger than S = 5000.
Your code should output a space-separated list of the basin sizes, in descending order. (Trailing spaces are ignored.)
For examples see the the linked question :)
RainfallChallenge.java
public class RainfallChallenge {
public static void main(String[] args) {
try (Scanner stdin = new Scanner(System.in)) {
Grid grid = new Grid(stdin.nextInt());
grid.read(stdin);
StringBuilder output = new StringBuilder();
for (int basinSize : grid.solve()) {
output.append(basinSize);
output.append(' ');
}
System.out.println(output);
}
}
}
Grid.java
public class Grid {
Cell[][] grid;
public Grid(int size) {
grid = new Cell[size][size];
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
grid[row][column] = new Cell(grid, row, column);
}
}
}
public void read(Scanner stdin) {
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
grid[row][column].setHeight(stdin.nextInt());
}
}
}
public void test(int[][] heights) {
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
grid[row][column].setHeight(heights[row][column]);
}
}
}
public List<Integer> solve() {
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
grid[row][column].findSink();
}
}
Map<Cell, List<Cell>> basins = new HashMap<>();
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
Cell end = grid[row][column].getSink();
List<Cell> cells = basins.get(end);
if (cells == null) {
cells = new ArrayList<>();
basins.put(end, cells);
}
cells.add(grid[row][column]);
}
}
List<Integer> results = new ArrayList<>();
for (List<Cell> basin : basins.values()) {
results.add(basin.size());
}
Collections.sort(results, Collections.reverseOrder());
return results;
}
}
Cell.java
public class Cell {
private final int row;
private final int column;
private int height;
private Cell next = this;
private Cell sink = next;
private Cell[][] grid;
public Cell(Cell[][] grid, int row, int column) {
this.grid = grid;
this.row = row;
this.column = column;
next = this;
sink = next;
}
public Cell getNext() {
return next;
}
private void setNextIfLower(Cell candidate) {
if (candidate.getHeight() < next.getHeight()) {
next = candidate;
}
}
public Cell getSink() {
return sink;
}
public void setHeight(int height) {
this.height = height;
}
public int getHeight() {
return height;
}
public int getRow() {
return row;
}
public int getColumn() {
return column;
}
// package visibility is intentional
void findSink() {
_findSink();
}
private Cell _findSink() {
// if we've already updated the sink, just return it
if (sink != this) {
return sink;
}
if (row > 0) {
setNextIfLower(grid[row - 1][column]);
}
if (column > 0) {
setNextIfLower(grid[row][column - 1]);
}
if (row + 1 < grid.length) {
setNextIfLower(grid[row + 1][column]);
}
if (column + 1 < grid[row].length) {
setNextIfLower(grid[row][column + 1]);
}
if (next != this) {
sink = next._findSink();
}
return sink;
}
}
RainfallTest.java
public class RainfallTest {
@Test
public void test3() {
int[][] heights = {{1, 5, 2},
{2, 4, 7},
{3, 6, 9}};
Grid grid = new Grid(heights.length);
grid.test(heights);
List<Integer> basinSizes = grid.solve();
assert(basinSizes.size() == 2);
assert(basinSizes.get(0) == 7);
assert(basinSizes.get(1) == 2);
}
@Test
public void test1() {
int[][] heights = {{10}};
Grid grid = new Grid(heights.length);
grid.test(heights);
List<Integer> basinSizes = grid.solve();
assert(basinSizes.size() == 1);
assert(basinSizes.get(0) == 1);
}
@Test
public void test5() {
int[][] heights = {{1, 0, 2, 5, 8},
{2, 3, 4, 7, 9},
{3, 5, 7, 8, 9},
{1, 2, 5, 4, 3},
{3, 3, 5, 2, 1}};
Grid grid = new Grid(heights.length);
grid.test(heights);
List<Integer> basinSizes = grid.solve();
assert(basinSizes.size() == 3);
assert(basinSizes.get(0) == 11);
assert(basinSizes.get(1) == 7);
assert(basinSizes.get(2) == 7);
}
@Test
public void test4() {
int[][] heights = {{0, 2, 1, 3},
{2, 1, 0, 4},
{3, 3, 3, 3},
{5, 5, 2, 1}};
Grid grid = new Grid(heights.length);
grid.test(heights);
List<Integer> basinSizes = grid.solve();
assert(basinSizes.size() == 3);
assert(basinSizes.get(0) == 7);
assert(basinSizes.get(1) == 5);
assert(basinSizes.get(2) == 4);
}
}
The jUnit
tests are based on the examples from the linked question.
assert(...)
method which only is activated if you pass-ea
on program start. I guess you mean something likeAssert.assertTrue(condition)
orAssert.assertEquals(expected, actual)
. Also, name your test method something more expressive then testN where N is something between 1 and 99999 \$\endgroup\$