Search code examples
pythoncmatrixpuzzle

Finding out same colored block in a 2D matrix


I am trying to find out a block of same colored region starting from the top left corner in a 2D matrix. For example: I have the following Matrix:

1 1 1 2 2 3
1 1 2 3 4 5
1 1 1 1 3 4
1 4 3 2 1 5
2 3 4 5 1 2

say, The initial top left corner is 1 and I want to find out the adjacent area containing 1's (I will only consider starting from the top left corner). In the above matrix the digits 1,2,3,4,5 represent different colors. I have tried using the following code segment for finding out the same colored block:

colors = ["red", "green", "blue", "purple", "orange"]

# create an array of the colors so we can do work on it
colors_array = [[random.randint(0, len(colors)-1) for x in range(num_wide)] for x in range(num_high)]

// keep track of what colors are touching in a giant array
touching_array = [[[0 for x in range(num_col)] for x in range(num_row)] for x in range(len(colors))]
origin_color = colors_array[0][0]
for x in range(num_row):
for y in range(num_col):
    # first row only cares about what's to the left of it
    if (x == 0):
        # if this is the same color as the origin
        if colors_array[x][y] == origin_color:
            # store a '1' signifying that this color is touching
            touching_array[origin_color][x][y] = 1
        else:
            # once they don't match, stop looking
            break
    # other rows need to match the row above it
    else:
        # if this color is the same color as the origin
        if (colors_array[x][y] == origin_color):
            # AND the one above it is "touching"
            if (colors_array[x][y] == touching_array[origin_color][x-1][y]):
                # store a '1' signifying that this color is touching
                touching_array[origin_color][x][y] = 1

But I haven't got the same colored block starting from top left corner as output. Is there anything wrong in the above code segment? How will I make it correct? It would be better If someone provide a C/C++ solution of the above problem.


Solution

  • You forgot that the tiles can touch along both axes, in all 4 directions.

    Consider the following input:

    1 1 1 1 1 1
    2 2 2 3 3 1
    1 1 1 1 3 1
    1 3 3 3 3 1
    1 1 1 1 1 1
    

    I wrote a new algorithm in two versions -- one using recursion, the other a loop and queue. It is similar to what J.Mac described in his answer.

    The algorithm is simple. We are given a target color to search for, the position to search at, an input matrix of colors, and output matrix of flags to identify matched tiles.

    To search a tile:

    • If the tile has the correct color AND it is not marked
      • Mark the tile
      • Search all neighboring tiles (2-4 depending on whether we're in the middle, at the edge or in the corner).

    We can either use recursion to search the neighboring tiles, or we can use a queue to keep track of the tiles that still need to be searched and repeatedly searching tiles from the front of the queue until none remain.

    #include <cstdint>
    #include <vector>
    #include <queue>
    #include <string>
    #include <iostream>
    
    typedef std::vector<int32_t> vec_1d;
    typedef std::vector<vec_1d> vec_2d;
    
    // Print the 2d vector with a label
    void dump(std::string const& label, vec_2d const& v)
    {
        std::cout << label << "\n";
        for (std::size_t y(0); y < v.size(); ++y) {
            for (std::size_t x(0); x < v[0].size(); ++x) {
                std::cout << v[y][x] << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
    
    // Recursive implementation of the search
    void find_connected_r(int32_t target_color
        , std::size_t x
        , std::size_t y
        , vec_2d const& colors
        , vec_2d& result)
    {
        if ((result[y][x] == 1) || (colors[y][x] != target_color)) {
            return;
        }
    
        result[y][x] = 1;
    
        std::size_t width(colors[0].size());
        std::size_t height(colors.size());
    
        if (x > 0) {
            find_connected_r(target_color, x - 1, y, colors, result);
        }
        if (y > 0) {
            find_connected_r(target_color, x, y - 1, colors, result);
        }
        if (x < (width - 1)) {
            find_connected_r(target_color, x + 1, y, colors, result);
        }
        if (y < (height - 1)) {
            find_connected_r(target_color, x, y + 1, colors, result);
        }
    }
    
    // Non-recursive implementation of the search
    void find_connected(int32_t target_color
        , std::size_t x
        , std::size_t y
        , vec_2d const& colors
        , vec_2d& result)
    {
        std::size_t width(colors[0].size());
        std::size_t height(colors.size());
    
        typedef std::pair<std::size_t, std::size_t> position;
        std::queue<position> s;
    
        s.push(position(x, y));
    
        while (!s.empty()) {
            position pos(s.front());
            s.pop();
    
            if (result[pos.second][pos.first] == 1) {
                continue;
            }
            if (colors[pos.second][pos.first] != target_color) {
                continue;
            }
            result[pos.second][pos.first] = 1;
    
            if (pos.first > 0) {
                s.push(position(pos.first - 1, pos.second));
            }
            if (pos.second > 0) {
                s.push(position(pos.first, pos.second - 1));
            }
            if (pos.first < (width - 1)) {
                s.push(position(pos.first + 1, pos.second));
            }
            if (pos.second < (height - 1)) {
                s.push(position(pos.first, pos.second + 1));
            }
        }
    }
    
    // Entry point to the search, select the implementation with last param
    vec_2d find_connected(std::size_t x, std::size_t y, vec_2d const& colors, bool recursive)
    {
        if (colors.empty() || colors[0].empty()) {
            throw std::runtime_error("Invalid input array size");
        }
    
        int32_t target_color(colors[y][x]);
    
        vec_2d result(colors.size(), vec_1d(colors[0].size(), 0));
    
        if (recursive) {
            find_connected_r(target_color, x, y, colors, result);
        } else {
            find_connected(target_color, x, y, colors, result);
        }
    
        return result;
    }
    
    
    
    int main()
    {
        vec_2d colors{
            { 1, 1, 1, 1, 1, 1 }
            , { 2, 2, 2, 3, 3, 1 }
            , { 1, 1, 1, 1, 3, 1 }
            , { 1, 3, 3, 3, 3, 1 }
            , { 1, 1, 1, 1, 1, 1 }
        };
    
        dump("Input", colors);
        dump("Search from (0,0) Recursive", find_connected(0, 0, colors, true));
        dump("Search from (0,0) Loop", find_connected(0, 0, colors, false));
        dump("Search from (1,1) Recursive", find_connected(1, 1, colors, true));
        dump("Search from (1,1) Loop", find_connected(1, 1, colors, false));
        dump("Search from (1,3) Recursive", find_connected(1, 3, colors, true));
        dump("Search from (1,3) Loop", find_connected(1, 3, colors, false));
    }
    

    Output:

    Input
    1 1 1 1 1 1
    2 2 2 3 3 1
    1 1 1 1 3 1
    1 3 3 3 3 1
    1 1 1 1 1 1
    
    Search from (0,0) Recursive
    1 1 1 1 1 1
    0 0 0 0 0 1
    1 1 1 1 0 1
    1 0 0 0 0 1
    1 1 1 1 1 1
    
    Search from (0,0) Loop
    1 1 1 1 1 1
    0 0 0 0 0 1
    1 1 1 1 0 1
    1 0 0 0 0 1
    1 1 1 1 1 1
    
    Search from (1,1) Recursive
    0 0 0 0 0 0
    1 1 1 0 0 0
    0 0 0 0 0 0
    0 0 0 0 0 0
    0 0 0 0 0 0
    
    Search from (1,1) Loop
    0 0 0 0 0 0
    1 1 1 0 0 0
    0 0 0 0 0 0
    0 0 0 0 0 0
    0 0 0 0 0 0
    
    Search from (1,3) Recursive
    0 0 0 0 0 0
    0 0 0 1 1 0
    0 0 0 0 1 0
    0 1 1 1 1 0
    0 0 0 0 0 0
    
    Search from (1,3) Loop
    0 0 0 0 0 0
    0 0 0 1 1 0
    0 0 0 0 1 0
    0 1 1 1 1 0
    0 0 0 0 0 0
    

    Printing coordinates

    This is very simple, like dump(...).

    We iterate through all the elements, but instead of printing values we print coordinates, and only when the element value is not 0.

    void dump_coordinates(std::string const& label, vec_2d const& v)
    {
        std::cout << label << "\n";
        for (std::size_t y(0); y < v.size(); ++y) {
            for (std::size_t x(0); x < v[0].size(); ++x) {
                if (v[y][x]) {
                    std::cout << "(" << x << ", " << y << ") ";
                }
            }
        }
        std::cout << "\n";
    }
    

    Call it:

    dump_coordinates("Coordinates searching from (1,3)", find_connected(1, 3, colors, true));
    

    And you get:

    Input
    1 1 1 1 1 1
    2 2 2 3 3 1
    1 1 1 1 3 1
    1 3 3 3 3 1
    1 1 1 1 1 1
    
    ...
    
    Search from (1,3) Loop
    0 0 0 0 0 0
    0 0 0 1 1 0
    0 0 0 0 1 0
    0 1 1 1 1 0
    0 0 0 0 0 0
    
    Coordinates searching from (1,3)
    (3, 1) (4, 1) (4, 2) (1, 3) (2, 3) (3, 3) (4, 3)
    

    NB: Coordinates are (row, column), and both are 0-indexed. The coordinates are not sorted in order of search.


    Replacing connected elements with another color

    Since we already have a method to obtain a mask of all the connected elements, we just need to use this mask to change all the appropriate values.

    This is similar to what we did in dump_coordinates(...).

    Again, we iterate over all the elements, but this time instead of printing, we change the color value at given position when the mask value is not 0.

    Code:

    vec_2d& change_masked(int32_t new_color
        , vec_2d& colors
        , vec_2d const& mask)
    {
        for (std::size_t y(0); y < mask.size(); ++y) {
            for (std::size_t x(0); x < mask[0].size(); ++x) {
                if (mask[y][x]) {
                    colors[y][x] = new_color;
                }
            }
        }
    
        return colors;
    }
    

    Call it:

    dump("Search from (0,0), replace all found with color from (1,1)"
        , change_masked(colors[1][1], colors, find_connected(0, 0, colors, true)));
    

    Output:

    Input
    1 1 1 1 1 1
    2 2 2 3 3 1
    1 1 1 1 3 1
    1 3 3 3 3 1
    1 1 1 1 1 1
    
    Search from (0,0) Recursive
    1 1 1 1 1 1
    0 0 0 0 0 1
    1 1 1 1 0 1
    1 0 0 0 0 1
    1 1 1 1 1 1
    
    ...
    
    Search from (0,0), replace all found with color from (1,1)
    2 2 2 2 2 2
    2 2 2 3 3 2
    2 2 2 2 3 2
    2 3 3 3 3 2
    2 2 2 2 2 2