3
\$\begingroup\$

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?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

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...

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.