{"id":"multidimensional-arrays-uneven-rows-and-objects","slug":"rows-of-different-lengths","title":"Rows of different lengths, and grids of objects","text":"### Rows of different lengths\n\nStep 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:\n\n```java\nint[][] waits = {\n    {12, 25, 8},\n    {},\n    {30, 15},\n    {5, 20, 10, 18}\n};\nfor (int day = 0; day < waits.length; day++) {\n    System.out.print(\"Day \" + (day + 1) + \":\");\n    for (int patient = 0; patient < waits[day].length; patient++) {\n        System.out.print(\" \" + waits[day][patient]);\n    }\n    System.out.println();\n}\n```\n\n```output\nDay 1: 12 25 8\nDay 2:\nDay 3: 30 15\nDay 4: 5 20 10 18\n```\n\nThe 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.)\n\n### Printing a whole grid\n\n`Arrays.toString` doesn't help much with a grid:\n\n```java\nint[][] waits = {{12, 25}, {30}};\nSystem.out.println(Arrays.toString(waits));\n```\n\nThis 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:\n\n```java\nint[][] waits = {\n    {12, 25, 8},\n    {},\n    {30, 15}\n};\nSystem.out.println(Arrays.deepToString(waits));\n```\n\n```output\n[[12, 25, 8], [], [30, 15]]\n```\n\nIt'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.\n\n### Creating the rows one by one\n\n`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:\n\n```java\nint[][] stairs = new int[4][];\nfor (int row = 0; row < stairs.length; row++) {\n    stairs[row] = new int[row + 1];\n    for (int column = 0; column < stairs[row].length; column++) {\n        stairs[row][column] = column + 1;\n    }\n}\nSystem.out.println(Arrays.deepToString(stairs));\n```\n\n```output\n[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]\n```\n\n### A row is an array of its own\n\nEach 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.\n\n```java\nint[][] grid = {\n    {1, 2},\n    {3, 4}\n};\ngrid[1] = grid[0];\ngrid[1][0] = 7;\nSystem.out.println(Arrays.deepToString(grid));\n```\n\n```output\n[[7, 2], [7, 2]]\n```\n\nA real copy of a row is a new array of the same length, with the values copied over in a loop.\n\n### Grids of Strings\n\nThe 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\":\n\n```java\nString[][] board = new String[3][3];\nboard[1][1] = \"X\";\nboard[0][2] = \"O\";\nboard[2][0] = \"X\";\nSystem.out.println(Arrays.deepToString(board));\nint free = 0;\nfor (int row = 0; row < board.length; row++) {\n    for (int column = 0; column < board[row].length; column++) {\n        if (board[row][column] == null) {\n            free++;\n        }\n    }\n}\nSystem.out.println(free + \" free squares\");\nSystem.out.println(\"X\".equals(board[0][0]) + \" \" + \"X\".equals(board[1][1]));\n```\n\n```output\n[[null, null, O], [null, X, null], [X, null, null]]\n6 free squares\nfalse true\n```\n\n`== 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.\n\n### Grids of your own objects\n\nObjects of your own classes go in a grid the same way. A box of chocolates holds them in rows:\n\n```java\nimport java.util.Arrays;\n\npublic class Main {\n    public static void main(String[] args) {\n        Chocolate[][] box = new Chocolate[2][3];\n        box[0][0] = new Chocolate(\"praline\", 12);\n        box[0][1] = new Chocolate(\"truffle\", 15);\n        box[1][2] = new Chocolate(\"nougat\", 10);\n        System.out.println(Arrays.deepToString(box));\n\n        Chocolate eaten = box[0][1];\n        box[0][1] = null;\n        System.out.println(\"Ate the \" + eaten + \", \" + eaten.getGrams() + \" g\");\n        System.out.println(Arrays.deepToString(box));\n    }\n}\n// ==== Chocolate.java ====\npublic class Chocolate {\n    private String name;\n    private int grams;\n\n    public Chocolate(String name, int grams) {\n        this.name = name;\n        this.grams = grams;\n    }\n\n    public int getGrams() {\n        return this.grams;\n    }\n\n    @Override\n    public String toString() {\n        return this.name;\n    }\n}\n```\n\n```output\n[[praline, truffle, null], [null, null, nougat]]\nAte the truffle, 15 g\n[[praline, null, null], [null, null, nougat]]\n```\n\n- `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.\n- `Arrays.deepToString` writes each object with its own `toString`, and `null` for an empty place.\n- Setting a place to `null` empties it. The chocolate itself still exists as long as a variable, here `eaten`, refers to it.\n","task":"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:\n\n```\n[[1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]\n```\n","kind":"fill","seed":"import java.util.Arrays;\n\npublic class Main {\n    public static void main(String[] args) {\n        int[][] seats = new int[[[3]]][];\n        for (int row = 0; row < seats.length; row++) {\n            seats[row] = new int[[[row + 4‖4 + row]]];\n            for (int seat = 0; seat < seats[row].length; seat++) {\n                seats[row][seat] = seat + 1;\n            }\n        }\n        System.out.println(Arrays.[[deepToString]](seats));\n    }\n}\n","solution":"import java.util.Arrays;\n\npublic class Main {\n    public static void main(String[] args) {\n        int[][] seats = new int[3][];\n        for (int row = 0; row < seats.length; row++) {\n            seats[row] = new int[row + 4];\n            for (int seat = 0; seat < seats[row].length; seat++) {\n                seats[row][seat] = seat + 1;\n            }\n        }\n        System.out.println(Arrays.deepToString(seats));\n    }\n}\n","hints":["The outer array has a place for each of the 3 rows: `new int[3][]`. Row 0 has 4 seats, row 1 has 5, so row `row` has `row + 4`.","The method of `Arrays` that prints the values inside the rows too is `deepToString`."],"tests":[{"name":"Output","stdin":"","expect":"[[1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6]]","hidden":false}],"require":[],"forbid":[],"more":[{"task":"What does this program print? Keep in mind which places of the outer array lead to the same row.\n","kind":"predict","seed":"import java.util.Arrays;\n\npublic class Main {\n    public static void main(String[] args) {\n        int[][] grid = new int[3][];\n        grid[0] = new int[2];\n        grid[1] = new int[] {4, 5, 6};\n        grid[2] = grid[1];\n        grid[2][0] = 9;\n        System.out.println(grid[1][0] + \" \" + grid[0][1]);\n        System.out.println(grid.length + \" \" + grid[2].length);\n        System.out.println(Arrays.deepToString(grid));\n    }\n}\n","solution":"import java.util.Arrays;\n\npublic class Main {\n    public static void main(String[] args) {\n        int[][] grid = new int[3][];\n        grid[0] = new int[2];\n        grid[1] = new int[] {4, 5, 6};\n        grid[2] = grid[1];\n        grid[2][0] = 9;\n        System.out.println(grid[1][0] + \" \" + grid[0][1]);\n        System.out.println(grid.length + \" \" + grid[2].length);\n        System.out.println(Arrays.deepToString(grid));\n    }\n}\n","hints":["`grid[2] = grid[1];` makes row 2 and row 1 the same array, so `grid[2][0] = 9` changes `grid[1][0]` too. Row 0 is a new array of two ints, both 0.","The outer array has 3 places, and row 2 is the row `{4, 5, 6}` (now starting with 9), which has 3 elements."],"tests":[{"name":"Output","stdin":"","expect":"9 0\n3 3\n[[0, 0], [9, 5, 6], [9, 5, 6]]","hidden":false}],"require":[],"forbid":[],"lines":["9 0","3 3","[[0, 0], [9, 5, 6], [9, 5, 6]]"]},{"task":"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:\n\n- a constructor `ChocolateBox(int rows, int columns)`, which creates an empty box of that size\n- `put(int row, int column, Chocolate chocolate)`, which puts the chocolate in that place\n- `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\n- `gramsLeft()`, which returns the total weight of the chocolates in the box\n- `toString()`, which returns the grid as `Arrays.deepToString` writes it\n\n`Main.java` is a test drive, and it prints:\n\n```\n[[praline, null, truffle], [null, caramel, null]]\nGrams left: 36\nTook: truffle\nTook: null\n[[praline, null, null], [null, caramel, null]]\nGrams left: 21\n```\n","kind":"code","seed":"// ==== ChocolateBox.java ====\nimport java.util.Arrays;\n\npublic class ChocolateBox {\n\n    public ChocolateBox(int rows, int columns) {\n\n    }\n\n    public void put(int row, int column, Chocolate chocolate) {\n\n    }\n\n    public Chocolate take(int row, int column) {\n        return null;\n    }\n\n    public int gramsLeft() {\n        return 0;\n    }\n\n    @Override\n    public String toString() {\n        return \"\";\n    }\n}\n\n// ==== Chocolate.java ====\npublic class Chocolate {\n    private String name;\n    private int grams;\n\n    public Chocolate(String name, int grams) {\n        this.name = name;\n        this.grams = grams;\n    }\n\n    public int getGrams() {\n        return this.grams;\n    }\n\n    @Override\n    public String toString() {\n        return this.name;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        ChocolateBox box = new ChocolateBox(2, 3);\n        box.put(0, 0, new Chocolate(\"praline\", 12));\n        box.put(0, 2, new Chocolate(\"truffle\", 15));\n        box.put(1, 1, new Chocolate(\"caramel\", 9));\n        System.out.println(box);\n        System.out.println(\"Grams left: \" + box.gramsLeft());\n        System.out.println(\"Took: \" + box.take(0, 2));\n        System.out.println(\"Took: \" + box.take(0, 2));\n        System.out.println(box);\n        System.out.println(\"Grams left: \" + box.gramsLeft());\n    }\n}\n","solution":"// ==== ChocolateBox.java ====\nimport java.util.Arrays;\n\npublic class ChocolateBox {\n    private Chocolate[][] places;\n\n    public ChocolateBox(int rows, int columns) {\n        this.places = new Chocolate[rows][columns];\n    }\n\n    public void put(int row, int column, Chocolate chocolate) {\n        this.places[row][column] = chocolate;\n    }\n\n    public Chocolate take(int row, int column) {\n        Chocolate chocolate = this.places[row][column];\n        this.places[row][column] = null;\n        return chocolate;\n    }\n\n    public int gramsLeft() {\n        int grams = 0;\n        for (Chocolate[] row : this.places) {\n            for (Chocolate chocolate : row) {\n                if (chocolate != null) {\n                    grams += chocolate.getGrams();\n                }\n            }\n        }\n        return grams;\n    }\n\n    @Override\n    public String toString() {\n        return Arrays.deepToString(this.places);\n    }\n}\n\n// ==== Chocolate.java ====\npublic class Chocolate {\n    private String name;\n    private int grams;\n\n    public Chocolate(String name, int grams) {\n        this.name = name;\n        this.grams = grams;\n    }\n\n    public int getGrams() {\n        return this.grams;\n    }\n\n    @Override\n    public String toString() {\n        return this.name;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        ChocolateBox box = new ChocolateBox(2, 3);\n        box.put(0, 0, new Chocolate(\"praline\", 12));\n        box.put(0, 2, new Chocolate(\"truffle\", 15));\n        box.put(1, 1, new Chocolate(\"caramel\", 9));\n        System.out.println(box);\n        System.out.println(\"Grams left: \" + box.gramsLeft());\n        System.out.println(\"Took: \" + box.take(0, 2));\n        System.out.println(\"Took: \" + box.take(0, 2));\n        System.out.println(box);\n        System.out.println(\"Grams left: \" + box.gramsLeft());\n    }\n}\n","hints":["The box needs one field, `private Chocolate[][] places;`, which the constructor creates with `new Chocolate[rows][columns]`. `take` keeps the chocolate in a variable, sets the place to `null`, and returns the variable.","`gramsLeft` goes through every place, and adds `getGrams()` only for a place that isn't `null`: calling a method on `null` crashes. `toString` returns `Arrays.deepToString(this.places)`."],"tests":[{"name":"The test drive","stdin":"","expect":"[[praline, null, truffle], [null, caramel, null]]\nGrams left: 36\nTook: truffle\nTook: null\n[[praline, null, null], [null, caramel, null]]\nGrams left: 21","hidden":false},{"name":"Test 2","stdin":"","call":"ChocolateBox box = new ChocolateBox(1, 2);\nSystem.out.println(box + \" \" + box.gramsLeft() + \" \" + box.take(0, 1));\nbox.put(0, 1, new Chocolate(\"mint\", 7));\nbox.put(0, 0, new Chocolate(\"nut\", 11));\nChocolate taken = box.take(0, 0);\nSystem.out.println(taken.getGrams() + \" \" + box + \" \" + box.gramsLeft());\n","expect":"[[null, null]] 0 null\n11 [[null, mint]] 7","hidden":true},{"name":"Test 3","stdin":"","call":"ChocolateBox box = new ChocolateBox(3, 2);\nfor (int row = 0; row < 3; row++) {\n    for (int column = 0; column < 2; column++) {\n        box.put(row, column, new Chocolate(\"c\" + row + column, row + column + 5));\n    }\n}\nSystem.out.println(box.gramsLeft());\nSystem.out.println(box.take(1, 0) + \" \" + box.take(1, 1) + \" \" + box.take(1, 1));\nSystem.out.println(box);\nSystem.out.println(box.gramsLeft());\n","expect":"39\nc10 c11 null\n[[c00, c01], [null, null], [c20, c21]]\n26","hidden":true},{"name":"Test 4","stdin":"","call":"ChocolateBox small = new ChocolateBox(1, 1);\nChocolateBox big = new ChocolateBox(2, 4);\nsmall.put(0, 0, new Chocolate(\"fig\", 8));\nbig.put(1, 3, new Chocolate(\"lime\", 6));\nSystem.out.println(small + \" \" + small.gramsLeft());\nSystem.out.println(big + \" \" + big.gramsLeft());\n","expect":"[[fig]] 8\n[[null, null, null, null], [null, null, null, lime]] 6","hidden":true}],"require":[{"pattern":"Chocolate\\s*\\[\\s*\\]\\s*\\[\\s*\\]","message":"Keep the chocolates in a two-dimensional array: a `Chocolate[][]`."}],"forbid":[{"pattern":"\\b(ArrayList|HashMap|List|Map)\\b","message":"Keep the chocolates in the two-dimensional array, not in a list or a map."}]}]}