# Snail matrix in Java - version III (generalizing)

Once again, in the previous version, Alexander Ivanchenko helped me with his awesome answer.

Now, I have improved the toString() and, also, generalized the class to handle matrices of any dimensions, not only square matrices. Given an $m \times n$-matrix, we start setting its elements $1, 2, \ldots mn$ from the top left location clockwise at the outermost feasible non-occupied cycle.

## Code

package com.github.coderodde.fun.snailmatrix;

import java.util.Arrays;
import static java.util.stream.Collectors.joining;

public class SnailMatrix {

private static final String BASE_FORMAT = "%%%dd";

/**
* The actual matrix data.
*/
private final int[][] data;

/**
* Constructs this snail matrix.
*
* @param n the width and height of this matrix.
*/
private SnailMatrix(final int width, final int height) {
this.data = new int[height][width];
}

/**
* Creates a snail matrix of dimensions {@code n x n}.
*
* @param width the width of the matrix.
* @param height the height of the matrix.
*
* @return the snail matrix.
*/
public static SnailMatrix ofSize(final int width, final int height) {
checkDimensions(width, height);

var matrix = new SnailMatrix(width, height);
var initializer = new MatrixInitializer(matrix);

initialize(initializer);

return matrix;
}

/**
* Initializes the matrix.
*
* @param initializer the intializer object.
*/
private static void initialize(final MatrixInitializer initializer) {
int value = 1;

while (initializer.hasNext()) {
initializer.set(value++);
initializer.next();
}
}

/**
* Returns the value from column {@code x} and row {@code y}.
*
* @param x the {@code x}-coordinate of the target cell.
* @param y the {@code y}-coordinate of the target cell.
*
* @return the value of the target cell.
*/
public int get(final int x, final int y) {
return data[y][x];
}

public int getWidth() {
return data[0].length;
}

public int getHeight() {
return data.length;
}

public int getCapacity() {
return getWidth() * getHeight();
}

@Override
public String toString() {
return Arrays.stream(data)
.map(this::rowToString)
.collect(joining(System.lineSeparator()));
}

private String rowToString(final int[] row) {
final var cellFormat = getCellFormat();

return Arrays.stream(row)
.mapToObj(cellFormat::formatted)
.collect(joining(" "));
}

private String getCellFormat() {
return BASE_FORMAT.formatted(String.valueOf(getCapacity())
.length());
}

private void set(final int x, final int y, final int value) {
data[y][x] = value;
}

private static void checkDimensions(final int width, final int height) {
if (width < 1) {
throw new IllegalArgumentException(
String.format(
"Width too small: %d. Must be at least 1.",
width));
}

if (height < 1) {
throw new IllegalArgumentException(
String.format(
"Height too small: %d. Must be at least 1.",
height));
}
}

/**
* This class implements the snail matrix initializer.
*/
private static final class MatrixInitializer {
private static final int[][] DIRECTIONS =
new int[][] {
{1, 0},
{0, 1},
{-1, 0},
{0, -1},
};

private final SnailMatrix matrix;
private int direction = 0;
private int x;
private int y;

private MatrixInitializer(final SnailMatrix matrix) {
this.matrix = matrix;
}

private void set(int value) {
matrix.set(x, y, value);
}

private boolean hasNext() {
return isValid(x, y) && !isInitialized(x, y);
}

private boolean isValid(final int x, final int y) {
return 0 <= x && x < matrix.getWidth()
&& 0 <= y && y < matrix.getHeight();
}

private boolean isInitialized(final int x, final int y) {
return matrix.get(x, y) != 0;
}

private void next() {
checkDirection();
x += xDelta();
y += yDelta();
}

private void checkDirection() {
final int nextX = x + xDelta();
final int nextY = y + yDelta();

if (!isValid(nextX, nextY) || isInitialized(nextX, nextY)) {
changeDirection();
}
}

private int xDelta() {
return DIRECTIONS[direction][0];
}

private int yDelta() {
return DIRECTIONS[direction][1];
}

private void changeDirection() {
direction = (direction + 1) % DIRECTIONS.length;
}
}

public static void main(String[] args) {
final int width = 5;
final int height = 3;

System.out.println(SnailMatrix.ofSize(width, height));
}
}


## Program output

 1  2  3  4  5
12 13 14 15  6
11 10  9  8  7


## Critique request

As always, is there anything to improve?

Indentation is confusing here:

    private String getCellFormat() {
return BASE_FORMAT.formatted(String.valueOf(getCapacity())
.length());
}


should be:

    private String getCellFormat() {
return BASE_FORMAT.formatted(String.valueOf(getCapacity())
.length());
}


While you now have a generalized SnailMatrix, it does not implement any general Matrix functionality (addition, multiplication, transpose, ...) other than printing. The snail-pattern is just one of possibly many matrix initializations. You really should have a separate Matrix class (where operations can be defined), and then ...

class SnailMatrix {
public void static fill(Matrix &m) { ...}
}


... which stores the snail pattern into an existing matrix. Now you can't rely on the Matrix initially being entirely zero; you'll have to zero-fill it yourself, or determine another way of determining which elements have not yet been filled in.

Of course, this opens yet another can of worms: what kind of Matrix? As you'd want this to work with Matrix<int>, the ideal will have to wait for Valhalla...