0

I have to write a program that will read a picture and then print out the number of blocks inside it.

I have to read the picture as a binary matrix of the size r × c (number of rows times number of columns). The blocks are groups of one or more adjacent elements with the value 1.

  • Blocks are built exclusively of elements with value 1
  • Each element with value 1 is a part of some block
  • Adjacent elements with value 1 belong to the same block. We only take into account the horizontal and vertical adjacency but not diagonal.

INPUT:

In the first line of the input we have the integers r and c, separated with one space. Then we have the r lines, where each contains s 0's and 1's. The numbers inside the individual lines are NOT separated by spaces.

The OUTPUT only print the number of blocks in the picture.

For example:

EXAMPLE 1

INPUT:

7 5

01000
00010
00000
10000
01000
00001
00100

OUTPUT: 6

EXAMPLE 2:

INPUT:

25 20

00010000000000000000
00010000000000000000
00010000000000000100
00000000000000000100
00011111111000000100
00000000000000000100
00000000000000000100
00000000000000000100
00000000000000000100
01111111111000000100
00000000000000000100
00000000000000100100
00000000000000100100
00000000000000100100
01000000000000100000
01000000000000100000
01000000000000100000
01000000000000100000
00000000000000100000
00000000000000000000
00000000000000000000
00000000000000000000
00000000000000000000
00011111111111100000
00000000000000000000

OUTPUT: 7

THE PROBLEM:

The problem that I have is that my program only works for inputs such as in example 1. So pictures that only consist of blocks of size 1. But it doesnt work if there are multiples 1's in a picture, such as EXAMPLE 2.

In example 2 where the output should be 7(Blocks are elements of 1.They can either be vertial or horizontal).... my programs output is 30.

I don't know how to adjust the program in a correct manner so it will give me the correct input.

Thank you for your help in advance, here is my code that I am posting bellow.

import java.util.Scanner;
class Blocks{

    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);

        int rowNum=sc.nextInt();
        int columnNum=sc.nextInt();


        char[][] matrix = new char[rowNum][columnNum];

        int nbrOfBlocks = 0;

        for (int a = 0; a < rowNum; a++) {
          matrix[a] = sc.next().toCharArray();
          int index = 0;
          while (index < matrix[a].length) {
            if (matrix[a][index] == '1') {
              ++nbrOfBlocks;
              while (index < matrix[a].length && matrix[a][index] == '1') {
                ++index;
                 }
                }

            ++index;
          }
        }

        System.out.println(nbrOfBlocks);

    }
}
13
  • 2
    Can you have T shapes, or are all blocks either 1-cell high or 1-cell wide? Commented Dec 19, 2014 at 16:31
  • You need to compare this line to the last line to know if you've reached the end of a block. So keep a line-width's worth of flags that has 0 if you're not part way through a vertical line, and 1 if you are. If the current line has a 0 where you have a flag of 1, you;ve just ended a vertical line. If 1 and 1, the line continues, if 1 and 0, you're starting one. After some thought you may realise your 'flags' line is just a copy of the last line... Commented Dec 19, 2014 at 16:33
  • 1
    It's actually a lot more complicated if blocks are not 1-high or 1-wide so I'm guessing that your assignment is keeping it simple. Commented Dec 19, 2014 at 16:37
  • 1
    I'd do it by reading in all the content. [A] Now find a 1, change it to 2. Look for other 1s next to a 2, and change those to 2. Repeat until there are no changes. That's one block. Change all the 2s to 0s ('cos we know about that block). Repeat from [A] Commented Dec 19, 2014 at 16:47
  • 1
    @DanAllen. Or "fools seldom differ". Your choice :) And they're not quite the same - yours produces the number but mine also could work out what the blocks actually were. But since that's not asked for, it's a bit overkill, maybe Commented Dec 19, 2014 at 16:48

3 Answers 3

1

EDIT: Ok, here is a solution that will work for complex shapes

public class BlockCounter {

    public static void main(String[] args) {
        Board board = null;

        try {
            board = new Board("in3.txt");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }

        System.out.println("Block count: " + board.getBlockCount());
    }
}

class Board {
    ArrayList<String> data = new ArrayList<>();
    boolean[][] used;
    int colCount = 0;

    public Board(String filename) throws FileNotFoundException, IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
            String line;

            while ((line = br.readLine()) != null) {
                data.add(line);
                colCount = Math.max(colCount, line.length());
            }
        }
    }

    public int getBlockCount() {
        used = new boolean[data.size()][colCount];
        int count = 0;

        for (int row = 0; row < data.size(); row++)
            for (int col = 0; col < colCount; col++)
                used[row][col] = peek(row, col) == '1';

        for (int row = 0; row < data.size(); row++)
            for (int col = 0; col < colCount; col++)
                if (used[row][col]) {
                    fill(row, col);
                    count++;
                }

        used = null;
        return count;
    }

    public char peek(int row, int col) {
        if (row < 0 || row >= data.size() || col < 0)
            return '0';

        String rowData = data.get(row);

        if (col >= rowData.length())
            return '0';

        return rowData.charAt(col);
    }

    public void fill(int row, int col) {
        if (used[row][col]) {
            used[row][col] = false;

            if (row > 0 && used[row - 1][col])
                fill(row - 1, col);

            if (col > 0 && used[row][col - 1])
                fill(row, col - 1);

            if (col < colCount - 1 && used[row][col + 1])
                fill(row, col + 1);

            if (row < data.size() - 1 && used[row + 1][col])
                fill(row + 1, col);
        }
    }

    public int getRowCount() {
        return data.size();
    }

    public int getColCount() {
        return colCount;
    }
}

Explanation: When Board.getBlockCount() is called if creates a temporary array of booleans to work with so the original board is not messed up. Then it searches the entire board for "trues" (which correspond to '1's on the board). Every time a "true" is found, a flood fill algorithm clears the entire shape to which it is connected. If you need more performance and less memory usage (specially stack) for larger boards, you can use another flood fill algorithm like in the example that follows. The big advantage here is that it doesn't use the stack for every pixel like the one above. It is considerably more complex though.

public class BlockCounter2 {

    public static void main(String[] args) {
        Board2 board = null;

        try {
            board = new Board2("in4.txt");
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }

        System.out.println("Block count: " + board.getBlockCount());
    }
}

class Board2 {
    ArrayList<String> data = new ArrayList<>();
    boolean[][] used;
    Deque<Point> pointStack = new LinkedList<>();
    int colCount = 0;

    public Board2(String filename) throws FileNotFoundException, IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
            String line;

            while ((line = br.readLine()) != null) {
                data.add(line);
                colCount = Math.max(colCount, line.length());
            }
        }
    }

    public int getBlockCount() {
        used = new boolean[data.size()][colCount];
        int count = 0;

        for (int row = 0; row < data.size(); row++)
            for (int col = 0; col < colCount; col++)
                used[row][col] = peek(row, col) == '1';

        for (int row = 0; row < data.size(); row++)
            for (int col = 0; col < colCount; col++)
                if (used[row][col]) {
                    fill(row, col);
                    count++;
                }

        used = null;
        return count;
    }

    public char peek(int row, int col) {
        if (row < 0 || row >= data.size() || col < 0)
            return '0';

        String rowData = data.get(row);

        if (col >= rowData.length())
            return '0';

        return rowData.charAt(col);
    }

    public void fill(int row, int col) {
        pointStack.push(new Point(col, row));
        Point p;

        while (pointStack.size() > 0) {
            p = pointStack.pop();
            fillRow(p.y, p.x);
        }
    }

    private void checkRow(int row, int col, int minCol, int maxCol) {
        boolean uu = false;

        for (int x = col; x < maxCol; x++) {
            if (!uu && used[row][x])
                pointStack.add(new Point(x, row));

            uu = used[row][x];
        }

        uu = true;

        for (int x = col; x > minCol; x--) {
            if (!uu && used[row][x])
                pointStack.add(new Point(x, row));

            uu = used[row][x];
        }
    }

    private void fillRow(int row, int col) {
        int lx, rx;

        if (used[row][col]) {
            for (rx = col; rx < colCount; rx++)
                if (used[row][rx])
                    used[row][rx] = false;
                else
                    break;

            for (lx = col - 1; lx >= 0; lx--)
                if (used[row][lx])
                    used[row][lx] = false;
                else
                    break;

            if (row > 0)
                checkRow(row - 1, col, lx, rx);

            if (row < data.size() - 1)
                checkRow(row + 1, col, lx, rx);
        }
    }

    public int getRowCount() {
        return data.size();
    }

    public int getColCount() {
        return colCount;
    }
}

EDIT2: Both solutions were made using input from txt files in order to make the debugging and testing easier for larger arrays. If you need them to work with user input (the same you have in your code) as well, just make the following changes:

  1. Change the main method so it will listen from user input (again):

    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
    
        int rowNum=sc.nextInt();
        int columnNum=sc.nextInt();     // Note columnNum is not necessary 
    
        String[] matrix = new String[rowNum];  // I hope char[][] is not a requirement
    
        for (int a = 0; a < rowNum; a++)       // Read array data from user input
            matrix[a] = sc.next();
    
        sc.close();
        Board2 board = new Board2(matrix);      // Call the new constructor
        System.out.println("Block count: " + board.getBlockCount());
    }
    
  2. Add a new constructor to Board2, that takes a String[] as input:

    public Board2(String[] data) {
        for (String line : data) {
            this.data.add(line);
            colCount = Math.max(colCount, line.length());
        }
    }
    

You may remove the previous constructor Board2(String filename) if it is not useful for you but it's not necessary.

Sign up to request clarification or add additional context in comments.

14 Comments

Won't a U shape have two top left corners, according to this? It's still just one block, though
It won't work for non rectangular shapes because I don't see where complex shapes were part of the requirements. But anyway, I can easily make changes to address that issue if that is what Killian19 needs.
@ulix Hey if you have the time and are willing to do so I would kindly ask you to help with that please
" don't see where complex shapes were part of the requirements" It's in a later comment from the OP. I don't think there's an easy change from your approach to deal with that, but if there is, I'm keen to see it.
Ok, just added the solution for complex shapes
|
0

Are you searching for this:

import java.util.Scanner;

class Blocks {

    public static void removeBlock(char[][] matrix, int posX, int posY) {
        if(0 <= posX && posX < matrix.length
            && 0 <= posY && posY < matrix[posX].length) {
            if(matrix[posX][posY] == '0') {
                return;
            }
            matrix[posX][posY] = '0';
        } else {
            return;
        }
        removeBlock(matrix, posX - 1, posY);
        removeBlock(matrix, posX + 1, posY);
        removeBlock(matrix, posX, posY - 1);
        removeBlock(matrix, posX, posY + 1);
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // read in
        char[][] matrix = new char[sc.nextInt()][sc.nextInt()];
        for(int a = 0; a < matrix.length; a++) {
            matrix[a] = sc.next().toCharArray();
        }

        // calculate number of blocks
        int nrBlocks = 0;

        for(int i = 0; i < matrix.length; i++) {
            for(int j = 0; j < matrix[i].length; j++) {
                if(matrix[i][j] == '1') {
                    // we have found a block, so increment number of blocks
                    nrBlocks += 1;
                    // remove any 1's of the block from the array, so that they each block is not counted multiple times
                    removeBlock(matrix, i, j);
                }
            }
        }

        System.out.println(nrBlocks);
    }
}

14 Comments

It would be interesting to feed this one a 100x100 all-1s array and find out how deep your stack can be :)
It will then get down to 100x100 = 10000 Stackelements.
If the stack becomes the problem: stackoverflow.com/questions/3700459/…
Sorry, I can't agree that pretty much unbounded recursion makes a good solution, even if you can increase the stack size. It works, though, so I'm not downvoting it.
Thankyou quant. I will sure read about it and try to fix the program in that way.! you have been a great help!
|
0

There's a linear time (in number of cells) solution to this. If I get time, I'll add the code to this answer, but if not the Wikipedia article (see EDIT below) gives pseudo code.

The idea is to scan line-by-line, assigning an incrementing unique run numbers to each runs of 1s we see (and changing the cell content to be that unique number) If in any run, the cells immediately above in the previous line were also 1 (that is, they now have a run number), then we know that their unique-run-number and the current unique-run number form part of a single block. So record that the run-above-run-number, and the current-run-number are equivalent (but don't bother to change anything on the board)

At the end, we have a count of the runs we've seen. and a set of equivalence relationships of unique-run-numbers. For each set of equivalent-numbers (say runs 3, 5, 14 form a block), we subtract from the run count the number of runs, minus 1 (in other words, replacing the multiple runs with a single block count).

I think I have some ideas to mitigate the worst case, but they're probably not worth it.

And that's the number of blocks.

The worst case for the equivalence classes is O(size of board) though (1-wide vertical blocks, with one space between them will do this). Fortunately, the entirely-black board will need only O(height of board) - each row will get one number, which will be marked equivalent with the next row.

EDIT: There's a Stackoverflow question about this already: can counting contiguous regions in a bitmap be improved over O(r * c)?

and it turns out I've just re-invented the two-pass "Connected Component Labelling" algorithm discussed on Wikipedia

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.