FRQ 2012 4

A grayscale image is represented by a 2-dimensional rectangular array of pixels (picture elements). A pixel is an integer value that represents a shade of gray. In this question, pixel values can be in the range from 0 through 255, inclusive. A black pixel is represented by 0, and a white pixel is represented by 255.

The declaration of the GrayImage class is shown below. You will write two unrelated methods of the GrayImage class.

public class GrayImage{
    public static final int BLACK = 0;
    public static final int WHITE = 255;
    
    /** The 2-dimensional representation of this image. Guaranteed not to be null.
    * All values in the array are within the range [BLACK, WHITE], inclusive.
    */
    private int[][] pixelValues;
    
    /** @return the total number of white pixels in this image.
    * Postcondition: this image has not been changed.
    */
    public int countWhitePixels()
    { /* to be implemented in part (a) */ }
    
    /** Processes this image in row-major order and decreases the value of each pixel at
    * position (row, col) by the value of the pixel at position (row + 2, col + 2) if it exists.
    * Resulting values that would be less than BLACK are replaced by BLACK.
    * Pixels for which there is no pixel at position (row + 2, col + 2) are unchanged.
    */
    public void processImage()
    { /* to be implemented in part (b) */ }
}

(a) Write the method countWhitePixels that returns the number of pixels in the image that contain the value WHITE. For example, assume that pixelValues contains the following image.

  0 1 2 3 4
0 255 184 178 84 129
1 84 255 255 130 84
2 78 255 0 0 78
3 84 130 255 130 84

A call to countWhitePixels method would return 5 because there are 5 entries that have the value WHITE.

Write the method processImage that modifies the image by changing the values in the instance variable pixelValues according to the following description. The pixels in the image are processed one at a time in row-major order. Row-major order processes the first row in the array from left to right and then processes the second row from left to right, continuing until all rows are processed from left to right. The first index of pixelValues represents the row number, and the second index represents the column number.

The pixel value at position (row, col) is decreased by the value at position (row + 2, col + 2) if such a position exists. If the result of the subtraction is less than the value BLACK, the pixel is assigned the value of BLACK. The values of the pixels for which there is no pixel at position (row + 2, col + 2) remain unchanged. You may assume that all the original values in the array are within the range [BLACK, WHITE], inclusive.

The following diagram shows the contents of the instance variable pixelValues before and after a call to processImage. The values shown in boldface represent the pixels that could be modified in a grayscale image with 4 rows and 5 columns.

Before call to processImage

  0 1 2 3 4
0 221 184 178 84 135
1 84 255 255 130 84
2 78 255 0 0 78
3 84 130 255 130 84

After call to processImage

  0 1 2 3 4
0 221 184 100 84 129
1 0 125 171 130 84
2 78 255 0 0 78
3 84 130 255 130 84
public class GrayImage {
    public static final int BLACK = 0;
    public static final int WHITE = 255;

    private int[][] pixelValues;

    public GrayImage(int[][] pixelValues) {
        this.pixelValues = pixelValues;
    }


    public int countWhitePixels() {
        int whitePixelCount = 0;
        for (int[] row : this.pixelValues) { // iterating through rows
            for (int pv : row) {
                if (pv == this.WHITE) { // if pixel is white, increment the count
                    whitePixelCount++;
                }
            }
        }
        return whitePixelCount;
    }

    public void processImage() {
        // Loop through each pixel, excluding the last 2 rows and columns
        for (int row = 0; row < this.pixelValues.length - 2; row++) { 
            for (int col = 0; col < this.pixelValues[0].length - 2; col++) {
                // Subtract pixel value from the corresponding pixel 2 rows and 2 columns away
                this.pixelValues[row][col] -= this.pixelValues[row + 2][col + 2];
                // If the result is less than black, set it to black
                if (this.pixelValues[row][col] < BLACK) {
                    this.pixelValues[row][col] = BLACK;
                }
            }
        }
    }

    public static void main(String[] args) {
        int[][] pixels = {
            {255, 0, 235, 0, 218},
            {17, 255, 186, 0, 255},
            {255, 0, 87, 255, 0},
            {0, 75, 255, 128, 255},
            {255, 38, 0, 0, 206}
        };
        
        GrayImage image = new GrayImage(pixels);

        System.out.println("White pixels before processing: " + image.countWhitePixels());

        image.processImage();

        System.out.println("White pixels after processing: " + image.countWhitePixels());
    }
}
GrayImage.main(null)
White pixels before processing: 8
White pixels after processing: 6
public class Matrix {
    private final int[][] matrix;

    // store matrix
    public Matrix(int[][] matrix) {
        this.matrix = matrix;
    }

    // nest for loops to format output of a matrix
    public String toString() {
        // construct output of matrix using for loops
        // outer loop for row
        StringBuilder output = new StringBuilder();
        for (int[] row : matrix) {
            // inner loop for column
            for (int cell : row) {
                output.append((cell==-1) ? " " : String.format("%x",cell)).append(" ");
            }
            output.append("\n"); // new line
        }
        return output.toString();
    }

    // print it backwards matrix
    public String reverse() {
        // outer loop starting at end row
        StringBuilder output = new StringBuilder();
        for (int i = matrix.length;  0 < i; i--) {
            // inner loop for column
            for (int j =  matrix[i-1].length; 0 < j; j--) {
                output.append((matrix[i-1][j-1]==-1) ? " " : String.format("%x",matrix[i-1][j-1])).append(" ");
            }
            output.append("\n"); // new line
        }
        return output.toString();
    }

    // declare and initialize a matrix for a keypad
    static int[][] keypad() {
        return new int[][]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, {-1, 0, -1} };
    }

    // declare and initialize a random length arrays
    static int[][] numbers() {
        return new int[][]{ { 0, 1 },
                { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
                { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } };
    }

    // tester method for matrix formatting
    public static void main(String[] args) {
        Matrix m0 = new Matrix(keypad());
        System.out.println("Keypad:");
        System.out.println(m0);
        System.out.println(m0.reverse());


        Matrix m1 = new Matrix(numbers());
        System.out.println("Numbers Systems:");
        System.out.println(m1);
        System.out.println(m1.reverse());

    }
}
Matrix.main(null);
Keypad:
1 2 3 
4 5 6 
7 8 9 
  0   

  0   
9 8 7 
6 5 4 
3 2 1 

Numbers Systems:
0 1 
0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 5 6 7 8 9 a b c d e f 

f e d c b a 9 8 7 6 5 4 3 2 1 0 
9 8 7 6 5 4 3 2 1 0 
1 0 
  1. toString() Method:
    public String toString() {
        StringBuilder output = new StringBuilder();
        for (int[] row : matrix) {
            for (int cell : row) {
                output.append((cell == -1) ? " " : String.format("%x", cell)).append(" ");
            }
            output.append("\n");
        }
        return output.toString();
    }
    
  2. reverse() Method:
    public String reverse() {
        StringBuilder output = new StringBuilder();
        for (int i = matrix.length; 0 < i; i--) {
            for (int j = matrix[i - 1].length; 0 < j; j--) {
                output.append((matrix[i - 1][j - 1] == -1) ? " " : String.format("%x", matrix[i - 1][j - 1])).append(" ");
            }
            output.append("\n");
        }
        return output.toString();
    }
    
  3. Static Methods for Matrix Initialization:
    static int[][] keypad() {
        return new int[][]{
             {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {-1, 0, -1}
         };
    }
    
    static int[][] numbers() {
        return new int[][]{
             {0, 1}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
         };
    }
    

Common Mistakes and Errors

These can be hard to spot, but below is an example:

import java.util.ArrayList;
import java.util.List;

public class ConcurrentModificationExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();

        // Add some elements to the list
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);

        // Iterate over the list and try to remove an element within the loop
        for (Integer number : numbers) {
            System.out.println(number);
            // Concurrent modification
            numbers.remove(number);
        }
    }
}

ConcurrentModificationExample.main();

Common Tips

For Methods & Control Structures questions, here are the steps we recommend:

For M&CT questions that involve 2D Arrays, here are the steps we recommend:

(a) Concept of Iteration over a 2D Array in Java

Iteration over a 2D array involves traversing through the array’s elements, which are arranged in rows and columns, similar to a matrix. In Java, a 2D array is an array of arrays. To iterate over a 2D array, you typically use nested loops: the outer loop iterates over each row (array), and the inner loop iterates over each element (column) within that row.

Example Scenario: Iterating over a 2D array is particularly useful in scenarios involving grid-like data structures, such as game boards (chess, tic-tac-toe), pixel matrices in images, and in your case, tracking player scores across different levels and attempts in a game. For instance, in a tic-tac-toe game, a 2D array can represent the game board where each cell holds a value indicating an empty spot, a “X”, or a “O”. Iterating over this array allows the program to check for win conditions, draw the board, or update it.

(b) Code: calculateTotalScore Method

Below is the implementation of the calculateTotalScore method. This method takes a 2D array of integers as input, representing player scores across different levels and attempts. It returns the sum of all scores by iterating through each element in the 2D array.

public class GameScoreTracker {
    /**
     * Calculates the total score from a 2D array of player scores.
     * @param scores A 2D array where each row represents a level and each column represents an attempt.
     * @return The sum of all scores in the array.
     */
    public static int calculateTotalScore(int[][] scores) {
        int totalScore = 0; // Initialize total score to 0.
        
        // Outer loop to iterate through each level (row).
        for (int level = 0; level < scores.length; level++) {
            // Inner loop to iterate through each attempt (column) in the current level.
            for (int attempt = 0; attempt < scores[level].length; attempt++) {
                // Add the score of the current attempt to the total score.
                totalScore += scores[level][attempt];
            }
        }
        
        // Return the total score after iterating through all levels and attempts.
        return totalScore;
    }
    
    // Example usage
    public static void main(String[] args) {
        int[][] playerScores = {
            {10, 20, 15},  // Scores from level 1
            {25, 30, 20},  // Scores from level 2
            {5, 7, 10}     // Scores from level 3
        };
        
        int total = calculateTotalScore(playerScores);
        System.out.println("Total Score: " + total);
    }
}

GameScoreTracker.main(null);
Total Score: 142

Explanation: