Creating a new array from a Flood Fill operation in JavaScript

I am seeking guidance on modifying this algorithm to generate a new array of updated elements instead of altering the global map array. This is the JavaScript implementation of the Flood Fill algorithm.

Starting Array:

var map = [
    [0, 0, 0, 1, 1],
    [0, 0, 0, 1, 0],
    [0, 0, 1, 1, 0],
    [0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0],
    [0, 1, 1, 1, 1]
];

Desired Result after calling floodFill function:

result = [
    [0, 2, 2],
    [0, 2, 0],
    [2, 2, 0],
    [0, 2, 0]
];

Below is the code snippet (which changes connected ones in the original array to twos):

var map = [
    [0, 0, 0, 1, 1],
    [0, 0, 0, 1, 0],
    [0, 0, 1, 1, 0],
    [0, 0, 0, 1, 0],
    [0, 0, 0, 0, 0],
    [0, 1, 1, 1, 1]
];

function floodFill(data, x, y, newValue) {
    // get target value
    var target = data[x][y];

    function flow(x,y) {
        // bounds check what we were passed
        if (x >= 0 && x < data.length && y >= 0 && y < data[x].length) {
            if (data[x][y] === target) {
                data[x][y] = newValue;
                flow(x-1, y);
                flow(x+1, y);
                flow(x, y-1);
                flow(x, y+1);
            }
        }
    }

    flow(x,y);
}

var result = floodFill(map, 1, 3, 2);
/*
result = [
    [0, 2, 2],
    [0, 2, 0],
    [2, 2, 0],
    [0, 2, 0]
]
*/

Answer №1

The initial step is to create a duplicate of the 2D array. This involves executing a process known as a deep-clone on the data, which will copy its contents into a fresh 2D array named newData. Subsequent changes can then be made to this duplicated version.

function floodFill(data, x0, y0, newValue) {
    var minX = x0, maxX = x0;
    var minY = y0, maxY = y0;

    // Deep clone operation to replicate data in newData
    var newData = [];
    for (var i = 0; i < data.length; i++)
        newData[i] = data[i].slice();

    // All modifications are done on newData moving forward
    var target = newData[x0][y0];
    function flow(x,y) {
        if (x >= 0 && x < newData.length && y >= 0 && y < newData[x].length) {
            if (newData[x][y] === target) {
                minX = Math.min(x, minX);
                maxX = Math.max(x, maxX);
                minY = Math.min(y, minY);
                maxY = Math.max(y, maxY);

                newData[x][y] = newValue;
                flow(x-1, y);
                flow(x+1, y);
                flow(x, y-1);
                flow(x, y+1);
            }
        }
    }
    flow(x0,y0);

    // Adjusting array size by cutting out a square
    var result = [];
    for (var i = minX; i < maxX + 1; i++)
        result[i] = newData[i].slice(minY, maxY + 1);
    return result;
}

To eliminate the zeros surrounding the outcome, it is essential to monitor and record the minimum and maximum x and y coordinates reached during the flood fill process. Subsequently, a square within the dimensions of [minX..maxX] x [minY..maxY] needs to be removed from the final result.

Answer №2

Presented below is a stack-based, iterative flood fill algorithm that operates out-of-place and returns the area of interest as a 2D sub-array post executing the flood fill with newValue:

function floodFill (data, x, y, newValue) {
  const target = data[y][x]
  const stack = []
  const fill = []
  const directions = {
    0: { x: -1, y:  0 },
    1: { x:  1, y:  0 },
    2: { x:  0, y: -1 },
    3: { x:  0, y:  1 }
  }
  const { X = 0, Y = 1, DIR = 2 } = {}
  let [minX, maxX, minY, maxY] = [x, x, y, y]

  function isChecked (x, y) {
    const hash = `${x},${y}`
    const checked = isChecked[hash]

    isChecked[hash] = true

    return checked
  }

  // validates spot as target
  function isValid (x, y) {
    return (
      x >= 0 && x < data[0].length &&
      y >= 0 && y < data.length &&
      data[y][x] === target &&
      !isChecked(x, y)
    )
  }

  // start with x, y  
  stack.push([x, y, [0, 1, 2, 3]])

  // continue flood fill while stack is not empty
  while (stack.length > 0) {
    let top = stack.pop()

    // if there are directions left to traverse
    if (top[DIR].length > 0) {
      // get next direction
      let dir = top[DIR].pop()
      let delta = directions[dir]
      // remember this spot before traversing
      stack.push(top)
      // calculate next spot
      top = [top[X] + delta.x, top[Y] + delta.y, [0, 1, 2, 3]]

      // if next spot doesn't match target value, skip it
      if (!isValid(...top)) continue

      // traverse to next spot
      stack.push(top)
    } else {
      // we're done with this spot
      // expand area of interest
      minX = Math.min(minX, top[X])
      maxX = Math.max(maxX, top[X])
      minY = Math.min(minY, top[Y])
      maxY = Math.max(maxY, top[Y])

      // and remember it for filling the copied sub-array later
      fill.push([top[X], top[Y]])
    }
  }

  // now that flood fill is completed, fetch result sub-array and fill it
  const result = []

  // generate result sub-array by copying data
  for (let i = minY; i <= maxY; i++) {
    result.push(data[i].slice(minX, maxX + 1))
  }
  // fill each remembered spot with newValue
  for (let i = 0; i < fill.length; i++) {
    let [gx, gy] = fill[i]
    let [rx, ry] = [gx - minX, gy - minY]

    result[ry][rx] = newValue
  }

  return result
}

const map = [
  [0, 1, 1, 1, 1],
  [0, 1, 0, 1, 0],
  [0, 1, 1, 1, 0],
  [0, 0, 0, 1, 0],
  [0, 0, 0, 0, 0],
  [0, 1, 1, 1, 1]
]

let result = floodFill(map, 3, 1, 2)

console.log(stringify(map))
console.log(stringify(result))

// for console.log(), ignore this
function stringify(data) {
  return data.map(row => row.join(', ')).join('\n')
}

The usage of isChecked() in this implementation prevents infinite iteration cycles by ensuring any single spot is considered only once during the flood fill process.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Alter the hue of the div

Having trouble with this code snippet: function red(opt) { var sqr; sqr="r"+parseInt(opt); hilight_css = {"background-color":"#f00"}; $(#sqr).css(hilight_css); } I am calling the function like so: red(questions); The variable question ...

Tips for importing numerous text files to an array using Ruby

As a newcomer to Ruby, I am currently working on a program that involves pushing multiple text files into an array line by line. Since I am unable to test my code at the moment due to work constraints, I would greatly appreciate some feedback on whether my ...

Tips on passing a variable into a controller using jQuery AJAX from the URL

Can you help me figure out how to pass an id to a function in the controller? I keep getting an error saying it cannot find the method in the controller. Here's my view: function Delete1(id) { if (confirm("Are you sure?")) { $. ...

Tips on showing the prior user entry in addition to the latest input

I'm a beginner in HTML and I'm attempting to create a script that will show user input back to the user. The issue I'm facing is that each new input overrides the previous one, but I want to display all user inputs. How can I achieve this us ...

Why is it that one of my useQuery hooks is returning a value while the other one is returning undefined?

I'm currently facing an issue with React Query where one of my useQuery hooks is logging undefined while the other one is displaying the correct data. Both functions are async and perform similar tasks. I've been troubleshooting this problem for ...

Stop Jade from collapsing the directory hierarchy

When it comes to implementing a build solution using NPM scripts instead of Gulp or Grunt, I have been facing some challenges in managing multiple Jade files efficiently. I've referred to resources like and for guidance. The Jade CLI allows for com ...

Interfacing Electron Frontend with Python Backend through seamless communication

Upon completing the development of a Python CLI app, it became apparent that creating an Electron frontend for better user interaction was necessary. What is the best way for the Electron app to communicate with the Python app when a user action occurs on ...

Ensure AngularJS creates a controller just once

In my search for a way to create a controller only once, I have not come across any satisfactory solutions. I have discovered some alternative approaches: Creating a controller list in $rootScope and adding newly created controllers to it Developing a m ...

What are the potential disadvantages of relocating the login logic from the 'signIn()' function in NextAuth.js?

My experience with NextAuth.js for the first time has led me to realize that signing in using the Credentials provider can be a bit tricky when it comes to error handling. It seems like the default implementation should resemble something along these lines ...

Identifying the completion of loading a HTML page using an object tag

I am trying to load an HTML page using an object tag, and once the page is fully downloaded, I want to display something. I have attempted the following approach, but it is not working: document.querySelector(".obj_excel").addEventListener("load", funct ...

Error: The function nodemailer.createTransport is not defined or does not exist

I'm currently working on integrating nodemailer into my nodejs application for sending emails. Check out the code below: var express = require('express'); var nodemailer = require('node-mailer'); var app = express(); app.post(&a ...

Error in Jquery Ajax autocomplete script

After countless hours staring at this function, my tired eyes are desperate for assistance. While the data is flowing correctly, the autocomplete feature fails to appear. function LookUp(InputBox) { $( "#JobCodeInput" ).autocomplete({ source: ...

What is the best way to minimize the number of requests sent in AngularJS?

Currently in my demo, each time a user types something into an input field, a request is sent to the server. However, I would like only one request to be fired. For example, if the user types "abc," it currently fires three requests. Is there a way for the ...

Intensive analysis of objects comparing their characteristics

Currently delving into the world of Javascript, I encountered a coding exercise involving a "deepEqual" function that I attempted to write but ultimately struggled with. Even after reviewing the solution, one particular part perplexed me - the for loop, ...

How can I achieve a "tab selector" effect with "input" elements on a website?

I am attempting to add "tab-highlights" similar to the ones shown above the form in the screenshot to my "input" elements. This will help users know which field they are currently focused on or have clicked. I have tried using "focus" and "::selection" to ...

Explore the data visualization capabilities of Angular Elements

Utilizing Angular elements to embed an angular page within an existing Spring-Boot project has been a seamless process thus far. An inquire button triggers the loading of data from the backend in the component typescript file, which is subsequently displa ...

identifying which rows in an array have infinity values in all columns

Seeking assistance with a Python query: I have an array where certain rows contain a value of "inf", however, I am looking for rows where all columns have the value "inf". I am able to identify a row with this value, but I require all values in that row ...

Solving layout issues / Techniques for determining element heights

This new question is in relation to my previous one: How to position relative elements after absolute elements I have updated the JsFiddle from that question to better represent my current HTML, which I unfortunately do not have a URL for at the moment. Y ...

Inspecting the session array to identify a specific value

function CHECKITEMEXIST($cartarray, $sub){ foreach ($cartarray as $item){ foreach ($item as $item2){ if($item2['subject'] = $sub){ return '1'; }else{ return '0&ap ...

Error messages arising from JSON files generated by PHP

My current setup involves using a PHP script to generate a cache of JSON files. We utilize a PHP file on our server to interact with a large database, fetching result sets in JSON format for use with jQuery and other frameworks on our site. The core scrip ...