JavaScript Challenge - Capturing Rainwater

This community-built FAQ covers the “Capturing Rainwater” code challenge in JavaScript. You can find that challenge here, or pick any challenge you like from our list.

Top Discussions on the JavaScript challenge Capturing Rainwater

There are currently no frequently asked questions or top answers associated with this challenge – that’s where you come in! You can contribute to this section by offering your own questions, answers, or clarifications on this challenge. Ask a question or post a solution by clicking reply (reply) below.

If you’ve had an “aha” moment about the concepts, formatting, syntax, or anything else with this challenge, consider sharing those insights! Teaching others and answering their questions is one of the best ways to learn and stay sharp.

Join the Discussion. Help a fellow learner on their journey.

Ask or answer a question about this exercise by clicking reply (reply) below!
You can also find further discussion and get answers to your questions over in #get-help.

Agree with a comment or answer? Like (like) to up-vote the contribution!

Need broader help or resources? Head to #get-help and #community:tips-and-resources. If you are wanting feedback or inspiration for a project, check out #project.

Looking for motivation to keep learning? Join our wider discussions in #community

Learn more about how to use this guide.

Found a bug? Report it online, or post in #community:Codecademy-Bug-Reporting

Have a question about your account or billing? Reach out to our customer support team!

None of the above? Find out where to ask other questions here!

function capturingRainwater(heights) { return heights.map((n, i) => Math.max(0, Math.min( Math.max(...heights.slice(i + 1)), Math.max(...heights.slice(0, i)) ) - n) ).reduce((a, b) => a + b, 0); } const testArray = [4, 2, 1, 3, 0, 1, 2]; console.log(capturingRainwater(testArray)); // Leave this so that we can test your code: module.exports = capturingRainwater;
2 Likes

Hey, here’s my try at this. I used pointers and bounds for this one.

function capturingRainwater(heights) { let total = 0; let leftPointer = 0; let rightPointer = heights.length - 1; let leftBound = 0; let rightBound = 0; while (leftPointer < rightPointer) { if (heights[leftPointer] <= heights[rightPointer]){ if (heights[leftPointer] > leftBound) { leftBound = heights[leftPointer]; } total += leftBound - heights[leftPointer]; leftPointer++; } else { if(heights[rightPointer] > rightBound) { rightBound = heights[rightPointer]; } total += rightBound - heights[rightPointer]; rightPointer--; } } return total; } const testArray = [4, 2, 1, 3, 0, 1, 2]; console.log(capturingRainwater(testArray));
2 Likes

My code is bit longer. But seems to work too.

function capturingRainwater(heights) {

  // amount of captured water
  let water = 0;

  // looping each row (bottom to up)
  for(let i_row = 0; i_row < Math.max(...heights); i_row++) {

    // looping inside row, cell by cell (left to right)...
    // and finding out if cell is filled with water...
    for(let i_col = 0; i_col < (heights.length); i_col++) {

      // if current cell contains water...
      // then all following tests must result true

      // TEST 1/3
      // cell must not be "solid"
      let notSolid = ((heights[i_col] - i_row) > 0) ? false : true;
      if(notSolid===false) {continue} // skip rest of the code block

      // TEST 2/3      
      // cell must not be part outer column
      let notOuter = (i_col == 0 || i_col == (heights.length - 1)) ? false : true;
      if(notOuter===false) {continue} // skip rest of the code block
      
      // TEST 3/3
      // at least 1 solid cell must be present in each direction from current position (to left and to right)
      let isBuried = isCellBuried(heights, i_row, i_col);
      if(isBuried===false) {continue} // skip rest of the code block
      
      // above tests result true...
      // add 1 to amount of captured water
      water++;
    }  
  }
  return water;
}

// testing if given cell with given coordinates (rowId, colId) in given array of heights (heightsArr) is horizontally surrounded with at least one "solid cell" in each direction (all the way to the left and to the right)
function isCellBuried(heightsArr, rowId, colId) {
  
  // this array will map items in given row
  // items:
  //    solid block as true (solidBlock = true)
  //    non-block as false (solidBlock = false)
  let rowMap = [];
  
  // mapping solid and non-solid blocks inside array row
  heightsArr.forEach((item, index) => {
    let solidBlock = (item - rowId) > 0 ? true : false;
    rowMap.push(solidBlock);
  })

  // slicing the array row into two array parts (left and right)
  // given column from argument being delimiter
  // current cell and outer cells are not included
  let leftPart = rowMap.slice(0, colId);
  let rightPart = rowMap.slice((colId + 1), heightsArr.length);

  // testing if both of the parts contain at least one solid block
  // if yes, then current cell is able to contain water
  let cellInHole = (leftPart.includes(true) && rightPart.includes(true)) ? true : false;
  return cellInHole;
}

const testArray = [4, 2, 1, 3, 0, 1, 2];
console.log(capturingRainwater(testArray));

// Leave this so that we can test your code:
module.exports = capturingRainwater;
function capturingRainwater(heights) { // amount of captured water let water = 0; // looping each row (bottom to up) for(let i_row = 0; i_row < Math.max(...heights); i_row++) { // looping inside row, cell by cell (left to right)... // and finding out if cell is filled with water... for(let i_col = 0; i_col < (heights.length); i_col++) { // if current cell contains water... // then all following tests must result true // TEST 1/3 // cell must not be "solid" let notSolid = ((heights[i_col] - i_row) > 0) ? false : true; if(notSolid===false) {continue} // skip rest of the code block // TEST 2/3 // cell must not be part outer column let notOuter = (i_col == 0 || i_col == (heights.length - 1)) ? false : true; if(notOuter===false) {continue} // skip rest of the code block // TEST 3/3 // at least 1 solid cell must be present in each direction from current position (to left and to right) let isBuried = isCellBuried(heights, i_row, i_col); if(isBuried===false) {continue} // skip rest of the code block // above tests result true... // add 1 to amount of captured water water++; } } return water; } // testing if given cell with given coordinates (rowId, colId) in given array of heights (heightsArr) is horizontally surrounded with at least one "solid cell" in each direction (all the way to the left and to the right) function isCellBuried(heightsArr, rowId, colId) { // this array will map items in given row // items: // solid block as true (solidBlock = true) // non-block as false (solidBlock = false) let rowMap = []; // mapping solid and non-solid blocks inside array row heightsArr.forEach((item, index) => { let solidBlock = (item - rowId) > 0 ? true : false; rowMap.push(solidBlock); }) // slicing the array row into two array parts (left and right) // given column from argument being delimiter // current cell and outer cells are not included let leftPart = rowMap.slice(0, colId); let rightPart = rowMap.slice((colId + 1), heightsArr.length); // testing if both of the parts contain at least one solid block // if yes, then current cell is able to contain water let cellInHole = (leftPart.includes(true) && rightPart.includes(true)) ? true : false; return cellInHole; } const testArray = [4, 2, 1, 3, 0, 1, 2]; console.log(capturingRainwater(testArray)); // Leave this so that we can test your code: module.exports = capturingRainwater;
1 Like
const capturingRainwater = (heights) => { let totalWater = 0; let leftPointer = 0; let rightPointer = heights.length - 1; let leftBound = 0; let rightBound = 0; while (leftPointer <= rightPointer) { leftBound = (heights[leftPointer] > leftBound) ? heights[leftPointer] : leftBound; rightBound = (heights[rightPointer] > rightBound) ? heights[rightPointer] : rightBound; if (leftBound > rightBound) { totalWater += rightBound - heights[rightPointer]; rightPointer--; } else { totalWater += leftBound - heights[leftPointer]; leftPointer++; } } return totalWater; } const testArray = [4, 2, 1, 3, 0, 1, 2]; console.log(capturingRainwater(testArray)); // Leave this so that we can test your code: module.exports = capturingRainwater;

I don’t think mine is much good because the code for mine is really, really long.

I started by finding the max and its index, and then found the volume to the next peak to the left repeatedly, and to the next peak to the right repeatedly.

I created a lot of helper functions for individual tasks, like finding the volume between two indices, in mine.

My solution
function capturingRainwater(heights) {
  const length = heights.length;
  if (length <= 1) {
    return 0;
  }
  let volume_total = 0;

  let max_so_far = 0;
  let index_of_max = 0;
  for (let i = 0; i < length; i++) {
    if (heights[i] > max_so_far) {
      max_so_far = heights[i];
      index_of_max = i;
    }
  }
  const max = max_so_far;

  if (max == 0) {
    return 0;
  }
  let indexByHeight = Array(max + 1);
  for (let h = 0; h <= max; h++) {
    indexByHeight[h] = [];
  }
  for (let i = 0; i < length; i++) {
    indexByHeight[heights[i]].push(i);
  }

  function getVolumeBetweenIndices(a, b) {
    if ((a < 0) || (b < 0)) {
      return 0;
    }
    else if ((a >= length) || (b >= length)) {
      return 0;
    }
    else if (a == b) {
      return 0;
    }
    else if (a > b) {
      let temp = a;
      a = b;
      b = temp;
    }
    const a_h = heights[a];
    const b_h = heights[b];
    const level = (a_h < b_h) ? a_h : b_h;
    let volume = 0;
    for (let j = a + 1; j < b; j++) {
      let depth = level - heights[j];
      //console.log(` >> @[${j}] +${depth}`);
      if (depth > 0) {
        volume += depth;
      }
    }
    //console.log(`@(${a},${b}) +${volume}  using level=${level}` );
    return (volume > 0) ? volume : 0;
  }

  function getNextMaxIndex(index) { 
    let current_height = 0;
    let previous_height = 0;
    let next_max = 0;
    let next_max_index = (index > 0) ? index : 0;
    for (i = index; i < length; i++) {
      current_height = heights[i];
      if (current_height > previous_height) {
        next_max = current_height;
        next_max_index = i;
      }
      previous_height = current_height;
    }
    return next_max_index;
  }

  function getNextMaxIndexReversed(index) {
    let current_height = 0;
    let previous_height = 0;
    let next_max = 0;
    let next_max_index = (index > 0) ? index : length - 1;
    for (i = index; i >= 0; i--) {
      current_height = heights[i];
      if (current_height > previous_height) {
        next_max = current_height;
        next_max_index = i;
      }
      previous_height = current_height;
    }
    return next_max_index;
  }

  const lastOf = arr => arr[arr.length - 1];

  const lowerMaxIndex = indexByHeight[max][0];
  const upperMaxIndex = lastOf(indexByHeight[max]);
  //console.log(lowerMaxIndex + ", " + upperMaxIndex);

  volume_total += getVolumeBetweenIndices(lowerMaxIndex, upperMaxIndex);
  //console.log(volume_total + " at j=" + max);
  let previous_lower = lowerMaxIndex;
  let previous_upper = upperMaxIndex;
  let lower_index = lowerMaxIndex - 1;
  let upper_index = upperMaxIndex + 1;

  for (let j = max - 1; j > 0; j--) {
    if (indexByHeight[j]) {
      const atThisHeight = indexByHeight[j];
      if (atThisHeight.length > 0) {
        let first = atThisHeight[0];
        let last = lastOf(atThisHeight);
        if (first < lower_index) {
          lower_index = first;
          volume_total += getVolumeBetweenIndices(lower_index, previous_lower);
          previous_lower = lower_index;
        }
        if (last > upper_index) {
          upper_index = last;
          volume_total += getVolumeBetweenIndices(previous_upper, upper_index);
          previous_upper = upper_index;
        }
        //console.log(volume_total + " at j=" + j);
      }
    }
  }
  
  return volume_total;
}

const testArray = [4, 2, 1, 3, 0, 1, 2];
console.log(capturingRainwater(testArray));
1 Like
const capturingRainwater = heights => {
  let totalRainWater = 0;
  heights.forEach((height, index) => {

    // Don't find water for left and right bounds
    if(index === 0 || index === heights.length - 1) return;
    
    // Find the max height on both sides of the current column
    let leftMax = Math.max(...heights.slice(0, index));
    let rightMax =  Math.max(...heights.slice(index + 1, heights.length));
    
    // Calculate the water for the current column
    let curColWater = Math.min(leftMax, rightMax) - height

    // Only add positive water
    if(curColWater <= 0) return
    totalRainWater += curColWater
  })
  return totalRainWater;
}

const testArray = [4, 2, 1, 3, 0, 1, 2];
console.log(capturingRainwater(testArray));

// Leave this so that we can test your code:
module.exports = capturingRainwater;

I don’t understand it, but it’s by far the most elegant solution i have seen.

Slow but really elegant and short.

Short and really fast )

Quite fast. Iteration level by level.

function capturingRainwater(heights) {
  let sum = 0;
  let level = 1;
  while (true) {
    let hasLeftBound = false;
    let water = 0;
    let waterAdded = false;
    let maxCountInLine = 0;
    let mayBeHigher = false;
    for (let i = 0; i < heights.length; i++) {
      if (heights[i] >= level) {
        if (water) {
          waterAdded = true;
          sum += water;
          water = 0;
        }
        hasLeftBound = true;
        maxCountInLine++;
        if (maxCountInLine === 3) {
          mayBeHigher = true;
        }
      } else {
        maxCountInLine = 0;
        if (hasLeftBound) {
          water++;
        }
      }
    }
    if (!waterAdded && !mayBeHigher) break;
    level++;
    waterAdded = false;
  }
  return sum;
}
function capturingRainwater(heights) {
  let boundaries = [heights[0], heights[heights.length-1]];
  if(boundaries[0] > boundaries[1]) {
    heights = [...heights].reverse();
    boundaries.reverse();
  }
  let gaps = [];
  heights.forEach(height => {
    boundaries[0] = Math.max(boundaries[0], height);
    gaps.push(Math.max(0, Math.min(...boundaries) - height));
  });
  return gaps.reduce((acc, el) => acc + el, 0);
}

const testArray = [4, 2, 1, 3, 0, 1, 2];
console.log(capturingRainwater(testArray));

function capturingRainwater(heights) {
// Write your code here
let leftPointer = 0;
let rightPointer = heights.length - 1;
let leftBound = 0;
let rightBound = 0;
let totalRain = 0;
while (leftPointer < rightPointer) {
if (heights[leftPointer] <= heights[rightPointer]) {
if (heights[leftPointer] > leftBound) {
leftBound = heights[leftPointer]
}
totalRain += leftBound - heights[leftPointer];
leftPointer++;
} else {
if (heights[rightPointer] > rightBound) {
rightBound = heights[rightPointer]
}
totalRain += rightBound - heights[rightPointer];
rightPointer–;
}
}
return totalRain
}

const testArray = [4, 2, 1, 3, 0, 1, 2];
console.log(capturingRainwater(testArray));

// Leave this so that we can test your code:
module.exports = capturingRainwater;