Fork me on GitHub

Project Notes

#452 minRepairs

Using Ruby to make minimum repairs to a broken array; cassidoo’s interview question of the week (2026-04-27).

Notes

The interview question of the week (2026-04-27):

You are given a 2D grid where 1 represents an intact tile and 0 represents a broken tile. A “broken region” is a group of connected 0s (connected horizontally or vertically). Find the minimum number of tiles you need to repair to ensure no broken region has an area larger than k.

Examples:

const grid = [
  [1, 0, 0, 1],
  [1, 0, 0, 1],
  [1, 1, 0, 1],
  [0, 1, 1, 1],
];
const k = 2;

let newGrid = [
  [1, 0, 0, 1],
  [1, 0, 0, 1],
  [1, 1, 0, 1],
  [0, 0, 1, 1],
];
let newK = 1;

minRepairs(grid, k)
> 2

minRepairs(newGrid, newK)
> 3

Thinking about the Problem

Finding the area of broken (“0”) regions is relatively trivial. Deciding how to best break them apart is not?

Intuitively, it seems that “0” cells with the largest number of horizontal and vertical neighbouring “0”s should be the first to be repaired.

A first approach

I’ve adapted the intuitive approach:

  • compile all the regions larger than allowed
  • “repair” the panel in each region with the most neighboring cells
  • repeat until all regions cleared

The essential code:

def minRepairs
    result = 0
    loop do
      regions = extractRegions
      break if regions.empty?

      regions.each_with_index do |region, idx|
        max_neighbors = 0
        cell_to_repair = nil

        region.each do |ci, cj|
          neighbors = 0
          [[0, 1], [0, -1], [1, 0], [-1, 0]].each do |di, dj|
            ni, nj = ci + di, cj + dj
            neighbors += 1 if region.include?([ni, nj])
          end
          if neighbors > max_neighbors
            max_neighbors = neighbors
            cell_to_repair = [ci, cj]
          end
        end
        if cell_to_repair
          input[cell_to_repair[0]][cell_to_repair[1]] = 1
          result += 1
        end
      end
    end
    result
  end
end

How does it fare? The data for the first example is in data1.json, and the answer is correct for k=2:


$ cat data1.json
[
  [1, 0, 0, 1],
  [1, 0, 0, 1],
  [1, 1, 0, 1],
  [0, 1, 1, 1]
]
$ ./examples.rb
Usage: ruby ./examples.rb <json-file> <k>
$ ./examples.rb data1.json 2
Input array: [[1, 0, 0, 1], [1, 0, 0, 1], [1, 1, 0, 1], [0, 1, 1, 1]]
k: 2
Broken regions: [[[0, 1], [0, 2], [1, 1], [1, 2], [2, 2]]]
Repaired [1, 2], repairs: 1
Broken regions: [[[0, 1], [0, 2], [1, 1]]]
Repaired [0, 1], repairs: 2
Broken regions: []
Result: 2

The data for the second example is in data1.json, and the answer is correct for k=3:


$ cat data2.json
[
  [1, 0, 0, 1],
  [1, 0, 0, 1],
  [1, 1, 0, 1],
  [0, 0, 1, 1]
]
$ ./examples.rb data2.json 1
Input array: [[1, 0, 0, 1], [1, 0, 0, 1], [1, 1, 0, 1], [0, 0, 1, 1]]
k: 1
Broken regions: [[[0, 1], [0, 2], [1, 1], [1, 2], [2, 2]], [[3, 0], [3, 1]]]
Repaired [1, 2], repairs: 1
Repaired [3, 0], repairs: 2
Broken regions: [[[0, 1], [0, 2], [1, 1]]]
Repaired [0, 1], repairs: 3
Broken regions: []
Result: 3

Tests

I’ve setup some validation in test_examples.rb:

$ ./test_examples.rb
Run options: --seed 20195

# Running:

..

Finished in 0.000321s, 6230.5296 runs/s, 6230.5296 assertions/s.

2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

Example Code

Final code is in examples.rb:

#!/usr/bin/env ruby

require 'json'

class Repairer
  attr_reader :input
  attr_reader :k
  attr_reader :logging

  def initialize(input, k, logging = false)
    @input = input
    @k = k
    @logging = logging
  end

  def log(message)
    puts message if logging
  end

  def extractRegions
    visited = Array.new(input.length) { Array.new(input[0].length, false) }
    result = []

    input.each_with_index do |row, i|
      row.each_with_index do |cell, j|
        next if visited[i][j] || cell == 1

        # BFS to find connected broken tiles
        region = []
        queue = [[i, j]]
        visited[i][j] = true

        while queue.any?
          ci, cj = queue.shift
          region << [ci, cj]

          [[0, 1], [0, -1], [1, 0], [-1, 0]].each do |di, dj|
            ni, nj = ci + di, cj + dj
            next if ni < 0 || ni >= input.length || nj < 0 || nj >= input[0].length
            next if visited[ni][nj] || input[ni][nj] == 1

            visited[ni][nj] = true
            queue << [ni, nj]
          end
        end

        result << region if region.length > k
      end
    end
    result
  end

  def minRepairs
    result = 0
    loop do
      regions = extractRegions
      log "Broken regions: #{regions.inspect}"
      break if regions.empty?

      # in each region, find the cell with the most neighbors and "repair it" (delete it and inc result)
      # Find the cell with the most neighbors in any region
      regions.each_with_index do |region, idx|
        max_neighbors = 0
        cell_to_repair = nil

        region.each do |ci, cj|
          neighbors = 0
          [[0, 1], [0, -1], [1, 0], [-1, 0]].each do |di, dj|
            ni, nj = ci + di, cj + dj
            neighbors += 1 if region.include?([ni, nj])
          end
          if neighbors > max_neighbors
            max_neighbors = neighbors
            cell_to_repair = [ci, cj]
          end
        end
        if cell_to_repair
          input[cell_to_repair[0]][cell_to_repair[1]] = 1
          result += 1
          log "Repaired #{cell_to_repair}, repairs: #{result}"
        end
      end
    end

    result
  end
end

if __FILE__ == $PROGRAM_NAME
  (puts "Usage: ruby #{$0} <json-file> <k>"; exit) unless ARGV.length > 1
  json_file_name = ARGV[0]
  begin
    input = JSON.parse(File.read(json_file_name))
  rescue
    puts "Error: Invalid JSON file - #{json_file_name}"
    exit
  end
  k = ARGV[1].to_i
  calculator = Repairer.new(input, k, true)
  puts "Input array: #{calculator.input.inspect}"
  puts "k: #{calculator.k.inspect}"
  puts "Result: #{calculator.minRepairs}"
end

Credits and References

About LCK#452
Rubycassidoo

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

Project Source on GitHub Return to the LittleCodingKata Catalog
About LittleCodingKata

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.

Follow the Blog follow projects and notes as they are published in your favourite feed reader