0
$\begingroup$

Requirements:

Given a 2D array of 1's and 0's (black and white cells) find blocks of white cells of size 1xT or Tx1 where T >= 1. Any block should not be a subset of a larger block. Return the number of these blocks.

An example of a solved grid

This is an example solution of a 5x3 grid. There should be 8 blocks. Block y:1, x:0 is not a block because it would be a subset of a bigger block y:1->3, x:0.

What I've tried so far

I've developed a pretty inefficient solution where I iterate through all white cells and for each one get a vertical and horizontal block. Then only accept either block if it isn't a subset of an already accepted block.

The most time complex operation in my code is checking whether a block is a subset of another accepted block.

My questions

What would be a more efficient solution to this problem?

Is there a data structure that would help me with this?

$\endgroup$
4
  • $\begingroup$ Do you want to find the minimum number of blocks? In that case, in your example case, $8$ blocks are not minimum. $\endgroup$ Commented Dec 28, 2021 at 17:16
  • 1
    $\begingroup$ Is $T$ an input? $\endgroup$
    – nir shahar
    Commented Dec 28, 2021 at 17:35
  • $\begingroup$ (The most interesting part is blocks of size 1 having no direction.) $\endgroup$
    – greybeard
    Commented Dec 28, 2021 at 17:49
  • $\begingroup$ Maximum number of blocks, not minimum. T is not an input. Direction doesn't matter so blocks of size 1 can be in row or column direction. $\endgroup$
    – Vid
    Commented Dec 28, 2021 at 18:59

3 Answers 3

0
$\begingroup$

you can solve it easily in O(mn). where m*n is matrix dimension. first consider only 1*T type blocks.

iterate each row and in any row whenever you see a 0->1 consecutive element increase your block count by 1. (if first element of row is 1 then also increase count) similarly, do this on columns.

(here don't consider 1*1 blocks initially in any iteration. finally after iterating over all rows and columns. check for 1*1 blocks separately)

$\endgroup$
4
  • $\begingroup$ Unless I'm missing something this only works for a few problems. Here's an example of an incorrect solution with this algorithm: i.imgur.com/hb5a9Zc.jpg Red is correct solution (5), blue is your algorithm. $\endgroup$
    – Vid
    Commented Dec 28, 2021 at 19:42
  • $\begingroup$ count only when you go from 0->1 not 1->0. $\endgroup$ Commented Dec 29, 2021 at 10:09
  • $\begingroup$ in your example this algo works fine $\endgroup$ Commented Dec 29, 2021 at 10:15
  • $\begingroup$ Your algorithm only works for the one example I provided, but with a few modifications it does work for all of my test cases as well. $\endgroup$
    – Vid
    Commented Dec 29, 2021 at 11:30
0
$\begingroup$

I fail to see any difficulty in this exercise.

Scan every row in turn, in any order. In a row, scan every column from left to right (or conversely), start a block and increment the counter when you meet a white, and end the block when you meet a black or the end of the row.

Scan every column in turn, in any order. In a column, scan every row from top to bottom (or conversely), start a block and increment the counter when you meet a white, and end the block when you meet a black or the end of the column.

$\endgroup$
2
  • $\begingroup$ Does this solve the "any block should not be a subset of a larger block"? I don't think it does, because it would also find the y1,x0 block in my example. $\endgroup$
    – Vid
    Commented Jan 24, 2023 at 9:52
  • $\begingroup$ @Vid: it has to find it as an horizontal block. Or your explanations are unclear. $\endgroup$
    – user16034
    Commented Jan 24, 2023 at 11:21
0
$\begingroup$

I have to agree with both the suggestions by Yves Daoust and Ashish gupta. The following observations should hold:

  • A $1 \times S$ block can only be a subset of a $T \times 1$ block if $ S = 1 $.
  • An $S \times 1$ block can only be a subset of a $1 \times T$ block if $ S = 1 $.
  • Only isolated $ 1 \times 1 $ blocks (without white neighbors) are not subsets of a larger blocks.
  • $ 1 \times T $ blocks in different rows can never be subsets of one another.
  • $ T \times 1 $ blocks in different columns can never be subsets of one another.

This should allow You to split the block finding as follows:

  1. Find $ 1 \times T $ blocks for $ T > 1 $ in each row separately
  2. Find $ T \times 1 $ blocks for $ T > 1 $ in each column separately
  3. Find all isolated $ 1 \times 1 $ blocks

Both 1. and 2. can both be reduced to the same 1D problem. The following Python code finds all white segments longer than 1 in a 1D array:

import numpy as np


def find_segments( array_1d ):
  array_1d = np.asarray(array_1d, dtype=bool)
  assert array_1d.ndim == 1

  # append black block for simplicity
  # (otherwise last segment needs separate handling)
  array_1d = np.append(array_1d, 1)

  start = None

  for i,ai in enumerate(array_1d):
    if start is None and ai == 0:
      # segment start found
      start = i

    if start is not None and ai == 1:
      # segment end found
      end = i-1
      if start < end: # <- only accept segments greater than 1 
        yield (start,end) 
      start = None

    i += 1

Finding isolated $ 1 \times 1 $ blocks should be as simple as going through each blocks and looking at its neighbors:

def find_1x1_blocks( array_2d ):
  array_2d = np.asarray(array_2d, dtype=bool)
  assert array_2d.ndim == 2

  # pad with black for simplicity
  array_2d = np.pad(array_2d, 1, constant_values=1)

  for (i,j),aij in np.ndenumerate(array_2d):
    if(
      aij == 0
      and array_2d[i-1,j  ] == 1 # <- North 
      and array_2d[i,  j+1] == 1 # <- East
      and array_2d[i+1,j  ] == 1 # <- South
      and array_2d[i,  j-1] == 1 # <- West
    ):
      # subtract padding
      i -= 1
      j -= 1
      yield (i,j)

Using these two subroutines together, we should be able to find all blocks without duplicates:

def find_all_blocks( array_2d ):  
  array_2d = np.asarray(array_2d, dtype=bool)
  assert array_2d.ndim == 2

  (m,n) = array_2d.shape

  # search rows
  for i in range(m):
    for (j,k) in find_segments(array_2d[i,:]):
      yield { 'y': i, 'x': (j,k) }

  # search columns
  for k in range(n):
    for (i,j) in find_segments(array_2d[:,k]):
      yield { 'y': (i,j), 'x': k }

  # collect 1x1 blocks
  for (i,j) in find_1x1_blocks(array_2d):
    yield { 'y': i, 'x': j }

All that's left now is to count the blocks. Let's use Your example:

input = np.array(
  [[1,0,0],
   [0,1,0],
   [0,0,1],
   [0,0,0],
   [1,0,0]]
)

blocks = [*find_all_blocks(input)]

for block in blocks:
  print(block)
# Output:
# {'y': 0, 'x': (1, 2)}
# {'y': 2, 'x': (0, 1)}
# {'y': 3, 'x': (0, 2)}
# {'y': 4, 'x': (1, 2)}
# {'y': (1, 3), 'x': 0}
# {'y': (2, 4), 'x': 1}
# {'y': (0, 1), 'x': 2}
# {'y': (3, 4), 'x': 2}

print(len(blocks))
# Output: 8
$\endgroup$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.