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);

    }
}
msrd0
  • 7,816
  • 9
  • 47
  • 82
Killian19
  • 23
  • 6
  • 2
    Can you have T shapes, or are all blocks either 1-cell high or 1-cell wide? – The Archetypal Paul Dec 19 '14 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... – The Archetypal Paul Dec 19 '14 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. – The Archetypal Paul Dec 19 '14 at 16:37
  • Actually you are right. The blocks can also be of any shape.Including T. But I am trying to do it step by step. Firstly making the progrsm for those 1 wide,high blocks and so on... – Killian19 Dec 19 '14 at 16:42
  • Really? Then that's quite complex. I'm not sure your step by step approach will be that easy to extend. Consider a U shape, or an H, or even more complex shapes. – The Archetypal Paul Dec 19 '14 at 16:45
  • 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] – The Archetypal Paul Dec 19 '14 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 – The Archetypal Paul Dec 19 '14 at 16:48
  • It's not (necessarily) recursive. It is iterative. – The Archetypal Paul Dec 19 '14 at 17:05
  • All recursive logic can be unravelled into sequential. Indeed they are - by compilers implementing a stack! But I read what you said as doing something that looks like 'backtracking'. When you look 'next to' a 2 you will need to look around it (above,below,left,right) and then 3 of above,below,left & right of that and so on. – Persixty Dec 19 '14 at 17:11
  • You take a pass over the array turning all 1s adjacent to 2s to 2s. But you don't backtrack, you take one pass through. If you changed anything, you take another pass over the array, and this time you'll change all 2s adjacent to the ones you changed to 2s last time. Or you could recurse, as you say. – The Archetypal Paul Dec 19 '14 at 17:29
  • I dont really have an idea on how to really put thay into code...I am really trzing to solve this – Killian19 Dec 19 '14 at 17:34
  • I think that's not true. Go back to my comment with [A] in it. Which parts of that can't you do? It's no more complex than what you've already implemented. However, it seems quant's given you an answer - I would still implement it yourself, you'll learn more that way. – The Archetypal Paul Dec 19 '14 at 17:42
  • Just out of curiosity, shouldn't the output be 6? I tried re-tracing the adjacent values and this is what i got - row0=column3, row2=column17, row4=column4 column5 column6 column7 column8 column9 column10, row9=column1 column2, row11=column14, row23=column11 column12 column13 – Vikram Ezhil Dec 19 '14 at 22:39

3 Answers3

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.

ulix
  • 999
  • 9
  • 13
  • Won't a U shape have two top left corners, according to this? It's still just one block, though – The Archetypal Paul Dec 19 '14 at 17:45
  • 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 Dec 19 '14 at 18:27
  • @ulix Hey if you have the time and are willing to do so I would kindly ask you to help with that please – Killian19 Dec 19 '14 at 19:02
  • " 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. – The Archetypal Paul Dec 19 '14 at 19:33
  • Ok, just added the solution for complex shapes – ulix Dec 19 '14 at 20:03
  • OK, so that's quite different from the original and close to quants. Did you try it on 1000x1000 all 1s, just for interest? – The Archetypal Paul Dec 19 '14 at 20:59
  • I didn't but I'm pretty sure it will work for any size – ulix Dec 19 '14 at 21:03
  • Sorry @Paul, I was at work and could respond properly. My solution was optimized for simplicity and not for performance or memory usage and it might not be ideal for very large arrays. There are many other flood fill algorithms with better performance that can be used instead of the one I've chosen (at the cost of clarity), though. – ulix Dec 19 '14 at 22:57
  • And yes, now I see quant offered a very similar solution. – ulix Dec 19 '14 at 23:02
  • So @Paul, in order to avoid the impression that I just carbon copied quants solution, I've added a second algorithm that should perform better on larger images. – ulix Dec 20 '14 at 01:02
  • Sorry, I wasn't suggesting you'd copied his solution - it's a natural solution to the problem. Some interesting ideas here: http://en.wikipedia.org/wiki/Flood_fill – The Archetypal Paul Dec 20 '14 at 08:51
  • @ulix I tried testing your version but I can't seem to figure out how to change it so it will work for the user input. So the user types in: r(row), c(column) and the sets of 0's and 1's so the matrix is complete – Killian19 Dec 20 '14 at 10:14
  • Hi Killian19, I'll make the adaptations you need and append to my answer. – ulix Dec 21 '14 at 01:22
  • No worries @Paul. The impression of my answer being a copy was inevitable at that point as it was indeed very similar to that of quants and came out later. You pointing that out (even if unintentionally) was actually a good thing because it gave me the chance to make for a fix. Next time I'd better give one last check on other user posts just BEFORE posting mine to avoid this sort of thing. – ulix Dec 21 '14 at 01:32
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);
    }
}
quant
  • 2,184
  • 2
  • 19
  • 29
  • It would be interesting to feed this one a 100x100 all-1s array and find out how deep your stack can be :) – The Archetypal Paul Dec 19 '14 at 17:44
  • It will then get down to 100x100 = 10000 Stackelements. – quant Dec 19 '14 at 17:46
  • If the stack becomes the problem: http://stackoverflow.com/questions/3700459/how-to-increase-the-java-stack-size – quant Dec 19 '14 at 17:47
  • 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. – The Archetypal Paul Dec 19 '14 at 17:48
  • @Paul: I agree that recursive algorithms, such as my algorithm may run in problems when recursion goes to deep (apart from the fact, that 10000 Stackelements are normally not giving you any problems). However they are easier to design/to write, so I choose this way. If you want, you can rewrite the removeBlock function. – quant Dec 19 '14 at 17:52
  • and you can (often) easier prove the correctness of recursive algorithms. – quant Dec 19 '14 at 17:53
  • Hey, yes this seem to be working great Thank you! I do however don't understand if that would work for matrix 1000x1000 and if it would only consist of 1's. .... so the number of blocks would be 1. @quant – Killian19 Dec 19 '14 at 19:08
  • It gives me an time exception error for a removeBlock funtion, when the testing input is 1000x1000 consisting of all 1's @Paul – Killian19 Dec 19 '14 at 19:28
  • There isn't a "time exception" when things take too long in standard Java, as far as I know. Is this a test framework for your assignment? It will recurse (an awful lot) in that test case. – The Archetypal Paul Dec 19 '14 at 19:31
  • Yes. I tried the program with tests that our professor has given us. And he only allows us around 1,5 seconds for the program to run. Is there a way to limit this recursion in that case if the stack is so big and only consist of 1's. @Paul – Killian19 Dec 19 '14 at 19:34
  • Yes, there is. Why don't you think of what could be done. It's an assignment. I rather doubt your professor expects you to get your answers here. – The Archetypal Paul Dec 19 '14 at 19:36
  • The only assignment was to make it look for vertical and horizontal blocks. But the bonus questions for trying to do more is this... so I am looking here to help me do the bonus so I learn this way, not to do my homework here – Killian19 Dec 19 '14 at 19:43
  • @Killian19: I give you a keyword, that may help you: "dynamic programming". I'm not quite sure, whether it helps you here (that's something I have to think about too). But it is often helpful, to reduce the computation time/memory a recursive algorithm (like the removeBlock function) takes. – quant Dec 19 '14 at 20:24
  • 1
    Thankyou quant. I will sure read about it and try to fix the program in that way.! you have been a great help! – Killian19 Dec 20 '14 at 10:17
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

Community
  • 1
  • 1
The Archetypal Paul
  • 41,321
  • 20
  • 104
  • 134