Java Arena
Step 5 of 6

Rows of different lengths, and grids of objects

Rows of different lengths

Step 2 showed that a grid is an array of rows, and each row an array of its own. Nothing says the rows must be equally long. A small clinic logs how many minutes each patient waited, day by day. Some days have more patients than others, and on a day the clinic is closed there are none:

int[][] waits = {
    {12, 25, 8},
    {},
    {30, 15},
    {5, 20, 10, 18}
};
for (int day = 0; day < waits.length; day++) {
    System.out.print("Day " + (day + 1) + ":");
    for (int patient = 0; patient < waits[day].length; patient++) {
        System.out.print(" " + waits[day][patient]);
    }
    System.out.println();
}
Output
Day 1: 12 25 8
Day 2:
Day 3: 30 15
Day 4: 5 20 10 18

The inner loop asks each row for its own length, waits[day].length, so it fits every row: 3 patients, none, 2 and 4. With waits[0].length instead, the inner loop would try 3 patients every day. That's too many for days 2 and 3 and too few for day 4. The program crashes on day 2, which has no patients at all, so it never gets any further. That's why the loops in this module say grid[row].length: it works for grids of every shape. (Step 3's column totals used sales[0].length, which is fine only when all rows are equally long.)

Printing a whole grid

Arrays.toString doesn't help much with a grid:

int[][] waits = {{12, 25}, {30}};
System.out.println(Arrays.toString(waits));

This prints something like [[I@4e50df2e, [I@1d81eb93]. The elements of the outer array are rows, and an array printed as itself shows only [I@ and a code. The code says nothing about the values, and it can change from one run to the next and from one Java system to another. Arrays.deepToString goes into the rows too:

int[][] waits = {
    {12, 25, 8},
    {},
    {30, 15}
};
System.out.println(Arrays.deepToString(waits));
Output
[[12, 25, 8], [], [30, 15]]

It's a handy way to check what a grid holds while you work on a program, as Arrays.toString is for a one-dimensional array.

Creating the rows one by one

new int[4][], with the second size left out, creates only the outer array: four places for rows, each null until a row is put there. Then each row can be created with its own length:

int[][] stairs = new int[4][];
for (int row = 0; row < stairs.length; row++) {
    stairs[row] = new int[row + 1];
    for (int column = 0; column < stairs[row].length; column++) {
        stairs[row][column] = column + 1;
    }
}
System.out.println(Arrays.deepToString(stairs));
Output
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]

A row is an array of its own

Each place of the outer array holds a reference to a row, just as an int[] variable does (module 15). So grid[1] = grid[0]; doesn't copy row 0: afterwards both places lead to the same row, and a change made through one shows through the other.

int[][] grid = {
    {1, 2},
    {3, 4}
};
grid[1] = grid[0];
grid[1][0] = 7;
System.out.println(Arrays.deepToString(grid));
Output
[[7, 2], [7, 2]]

A real copy of a row is a new array of the same length, with the values copied over in a loop.

Grids of Strings

The cells can be of any type. A tic-tac-toe board is a String[][], and a new one holds null in every cell, which works well as "nothing here yet":

String[][] board = new String[3][3];
board[1][1] = "X";
board[0][2] = "O";
board[2][0] = "X";
System.out.println(Arrays.deepToString(board));
int free = 0;
for (int row = 0; row < board.length; row++) {
    for (int column = 0; column < board[row].length; column++) {
        if (board[row][column] == null) {
            free++;
        }
    }
}
System.out.println(free + " free squares");
System.out.println("X".equals(board[0][0]) + " " + "X".equals(board[1][1]));
Output
[[null, null, O], [null, X, null], [X, null, null]]
6 free squares
false true

== null finds an empty cell. Calling a method on an empty cell, such as board[0][0].equals("X"), would crash with a NullPointerException, since there's no String there to call it on. With the known String first, "X".equals(board[0][0]) is simply false for an empty cell.

Grids of your own objects

Objects of your own classes go in a grid the same way. A box of chocolates holds them in rows:

Main.java
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Chocolate[][] box = new Chocolate[2][3];
        box[0][0] = new Chocolate("praline", 12);
        box[0][1] = new Chocolate("truffle", 15);
        box[1][2] = new Chocolate("nougat", 10);
        System.out.println(Arrays.deepToString(box));

        Chocolate eaten = box[0][1];
        box[0][1] = null;
        System.out.println("Ate the " + eaten + ", " + eaten.getGrams() + " g");
        System.out.println(Arrays.deepToString(box));
    }
}
Chocolate.java
public class Chocolate {
    private String name;
    private int grams;

    public Chocolate(String name, int grams) {
        this.name = name;
        this.grams = grams;
    }

    public int getGrams() {
        return this.grams;
    }

    @Override
    public String toString() {
        return this.name;
    }
}
Output
[[praline, truffle, null], [null, null, nougat]]
Ate the truffle, 15 g
[[praline, null, null], [null, null, nougat]]
  • new Chocolate[2][3] creates the grid, but no chocolates: all six places are null. Each chocolate is created with new Chocolate(...) and put in its place.
  • Arrays.deepToString writes each object with its own toString, and null for an empty place.
  • Setting a place to null empties it. The chocolate itself still exists as long as a variable, here eaten, refers to it.

Your task

Challenge 1 of 3

An outdoor theater's rows get longer toward the back: the front row has 4 seats, and each row has one seat more than the row in front of it. Fill in the blanks: create the outer array for 3 rows, with the length of the rows left out, give each row its length, and print the whole grid. The program prints:

[[1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]

Challenge 2

What does this program print? Keep in mind which places of the outer array lead to the same row.

Challenge 3

A box of chocolates once more. Chocolate.java is ready: a chocolate has a name and a weight in grams, and its toString returns the name. In ChocolateBox.java, write the class ChocolateBox, which keeps its chocolates in a Chocolate[][], with null for an empty place:

  • a constructor ChocolateBox(int rows, int columns), which creates an empty box of that size
  • put(int row, int column, Chocolate chocolate), which puts the chocolate in that place
  • take(int row, int column), which returns the chocolate in that place and leaves the place empty, or returns null when the place is already empty
  • gramsLeft(), which returns the total weight of the chocolates in the box
  • toString(), which returns the grid as Arrays.deepToString writes it

Main.java is a test drive, and it prints:

[[praline, null, truffle], [null, caramel, null]]
Grams left: 36
Took: truffle
Took: null
[[praline, null, null], [null, caramel, null]]
Grams left: 21