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?
1 Answer 1
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...