I have this generic "matrix" holding elements of arbitrary type. It supports any horizontal or vertical rotations (of any step size) in \$\mathcal{O}(1)\$. See what I have:
package net.coderodde.util;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* This class implements a generic, fixed-sized matrix of generic elements. It
* supports any rotations in constant time.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 5, 2016)
*/
public final class RotableMatrix<E> {
private final Object[][] matrix;
private final int width;
private final int height;
private int cursorX;
private int cursorY;
public RotableMatrix(int width, int height) {
checkWidth(width);
checkHeight(height);
this.width = width;
this.height = height;
this.matrix = new Object[height][width];
}
public void rotateUp() {
rotateUp(1);
}
public void rotateRight() {
rotateRight(1);
}
public void rotateDown() {
rotateDown(1);
}
public void rotateLeft() {
rotateLeft(1);
}
public void rotateUp(int steps) {
if (steps < 0) {
rotateDown(-steps);
return;
}
cursorY = (cursorY + steps) % height;
}
public void rotateRight(int steps) {
if (steps < 0) {
rotateLeft(-steps);
return;
}
cursorX = (cursorX - steps) % width;
if (cursorX < 0) {
cursorX += width;
}
}
public void rotateDown(int steps) {
if (steps < 0) {
rotateUp(-steps);
return;
}
cursorY = (cursorY - steps) % height;
if (cursorY < 0) {
cursorY += height;
}
}
public void rotateLeft(int steps) {
if (steps < 0) {
rotateRight(-steps);
return;
}
cursorX = (cursorX + steps) % width;
}
public E get(int x, int y) {
checkXCoordinate(x);
checkYCoordinate(y);
return (E) matrix[(y + cursorY) % height][(x + cursorX) % width];
}
public void set(int x, int y, E element) {
checkXCoordinate(x);
checkYCoordinate(y);
matrix[(y + cursorY) % height][(x + cursorX) % width] = element;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
sb.append(get(x, y));
if (x < width - 1) {
sb.append(" ");
}
}
if (y < height - 1) {
sb.append("\n");
}
}
return sb.toString();
}
private void checkWidth(int width) {
checkIntegerIsNoLessThan(width,
1,
"The matrix width is too small (" + width +
"). Must be at least 1.");
}
private void checkHeight(int height) {
checkIntegerIsNoLessThan(height,
1,
"The matrix height is too small (" + height +
"). Must be at least 1.");
}
private void checkIntegerIsNoLessThan(int test,
int lowerBound,
String errorMessage) {
if (test < lowerBound) {
throw new IllegalArgumentException(errorMessage);
}
}
private void checkXCoordinate(int x) {
if (x < 0) {
throw new IndexOutOfBoundsException(
"The x-coordinate is negative: " + x);
}
if (x >= width) {
throw new IndexOutOfBoundsException(
"The x-coordinate is too large: " + x + ". The width of " +
"this matrix is " + width);
}
}
private void checkYCoordinate(int y) {
if (y < 0) {
throw new IndexOutOfBoundsException(
"The y-coordinate is negative: " + y);
}
if (y >= height) {
throw new IndexOutOfBoundsException(
"The y-coordinate is too large: " + y + ". The height of " +
"this matrix is " + height);
}
}
private static final class MyCanvas extends Canvas {
private final RotableMatrix<Color> matrix;
MyCanvas(RotableMatrix<Color> matrix) {
this.matrix = matrix;
this.setPreferredSize(new Dimension(matrix.getWidth(),
matrix.getHeight()));
this.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
switch (e.getKeyChar()) {
case 'w':
case 'W':
matrix.rotateUp(10);
repaint();
break;
case 'd':
case 'D':
matrix.rotateRight(10);
repaint();
break;
case 's':
case 'S':
matrix.rotateDown(10);
repaint();
break;
case 'a':
case 'A':
matrix.rotateLeft(10);
repaint();
break;
}
}
@Override
public void keyPressed(KeyEvent e) {}
@Override
public void keyReleased(KeyEvent e) {}
});
}
@Override
public void update(Graphics g) {
BufferedImage image = new BufferedImage(matrix.getWidth(),
matrix.getHeight(),
BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < matrix.getHeight(); ++y) {
for (int x = 0; x < matrix.getWidth(); ++x) {
image.setRGB(x, y, matrix.get(x, y).getRGB());
}
}
g.drawImage(image, 0, 0, this);
}
@Override
public void paint(Graphics g) {
update(g);
}
}
private static final int WIDTH = 1000;
private static final int HEIGHT = 1000;
public static void main(String[] args) {
Random random = new Random();
RotableMatrix<Color> matrix = createColorMatrix(WIDTH, HEIGHT, random);
JFrame frame = new JFrame();
JPanel panel = new JPanel();
MyCanvas canvas = new MyCanvas(matrix);
canvas.setPreferredSize(new Dimension(1000, 1000));
panel.add(canvas);
frame.getContentPane().add(panel);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setVisible(true);
}
private static RotableMatrix<Color> createColorMatrix(int width,
int height,
Random random) {
RotableMatrix<Color> matrix = new RotableMatrix<>(width, height);
matrix.set(0, 0, createRandomColor(random));
for (int y = 1; y < height; ++y) {
matrix.set(0, y, createRandomColor(random, matrix.get(0, y - 1)));
}
for (int y = 0; y < height; ++y) {
for (int x = 1; x < width; ++x) {
Color previousColor = matrix.get(x - 1, y);
matrix.set(x, y, createRandomColor(random, previousColor));
}
}
return matrix;
}
private static Color createRandomColor(Random random) {
return new Color(random.nextInt(256),
random.nextInt(256),
random.nextInt(256));
}
private static Color createRandomColor(Random random, Color previousColor) {
int prevRed = previousColor.getRed();
int prevGreen = previousColor.getGreen();
int prevBlue = previousColor.getBlue();
int nextRed = prevRed + random.nextInt(17) - 8;
int nextGreen = prevGreen + random.nextInt(17) - 8;
int nextBlue = prevBlue + random.nextInt(17) - 8;
return new Color(filter(nextRed), filter(nextGreen), filter(nextBlue));
}
private static int filter(int a) {
if (a < 0) {
return 0;
}
if (a > 255) {
return 255;
}
return a;
}
}
The demonstration uses this matrix for efficient rotations of an image. As always, tell me anything that comes to mind.