DEV Community

James Robb
James Robb

Posted on • Edited on

Build a Tower

Task description

Build a tower given an argument representing the number of floors.

A tower block is represented as *

For example, a tower of 3 floors looks like below

[
' * ',
' *** ',
'*****'
]
and a tower of 6 floors looks like below

[
' * ',
' *** ',
' ***** ',
' ******* ',
' ********* ',
'***********'
]

Task solution

Tests

describe("tower builder", () => {
  it("Throws when invalid input is provided", () => {
    expect(() => towerBuilder("1")).toThrow(/InvalidArgumentException/);
    expect(() => towerBuilder(1, 1)).toThrow(/InvalidArgumentException/);
    expect(() => towerBuilder(1, "**", " ", "1")).toThrow(/InvalidArgumentException/);
    expect(() => towerBuilder(1, "*", 1)).toThrow(/InvalidArgumentException/);
    expect(() => towerBuilder(1, "*", "  ")).toThrow(/InvalidArgumentException/);
    expect(() => towerBuilder(1, "*", " ", "1")).toThrow(/InvalidArgumentException/);
    expect(() => towerBuilder(1, "*", " ", 1, {})).toThrow(/InvalidArgumentException/);
  });

  it("Should return valid towers", () => {
    expect(towerBuilder(1, "$")).toEqual(["$"]);
    expect(towerBuilder(2, "$", "-", 2)).toEqual(["$$$"]);
    expect(towerBuilder(2, "$", "-", 1, ["test"])).toEqual(["test", "-$-", "$$$"]);
    expect(towerBuilder(3, "+", ">")).toEqual([">>+>>", ">+++>", "+++++"]);
    expect(towerBuilder(10)).toEqual(["         *         ", "        ***        ", "       *****       ", "      *******      ", "     *********     ", "    ***********    ", "   *************   ", "  ***************  ", " ***************** ", "*******************"]);
  });
});
Enter fullscreen mode Exit fullscreen mode

Beginning with the usual input validation cases, we then test our towers themselves, in the examples above you can see our Jest tests building towers of varying sizes and using different symbols to affect the appearance of the tower.

Implementation

For the implementation I knew I would use recursion from the second I read the challenge description since this is just a repetitive task which generates the same output on each iteration just at a different size for each item output. The implementation I ended up with looks like so:

function towerBuilderRecursive(
  nFloors,
  brickCharacter = "*",
  spacer = " ",
  currentIndex = 1,
  items = []
) {
  if (!Number.isInteger(nFloors)) {
    throw new Error(`InvalidArgumentException: Parameter 1 must be an integer. Received: ${typeof nFloors}`);
  } else if(typeof brickCharacter !== "string" || brickCharacter.length !== 1) {
    throw new Error(`InvalidArgumentException: Parameter 2 must be a string of length 1. Received: ${typeof brickCharacter} ${typeof brickCharacter === "string" && `with length ${brickCharacter.length}`}`);
  } else if(typeof spacer !== "string" || spacer.length !== 1) {
    throw new Error(`InvalidArgumentException: Parameter 3 must be a string of length 1. Received: ${typeof spacer} ${typeof spacer === "string" && `with length ${spacer.length}`}`);
  } else if (!Number.isInteger(currentIndex)) {
    throw new Error(`InvalidArgumentException: Parameter 4 must be an integer. Received: ${typeof currentIndex}`);
  } else if (!Array.isArray(items)) {
    throw new Error(`InvalidArgumentException: Parameter 5 must be an array. Received: ${typeof items}`);
  }

  const space = spacer.repeat(nFloors - currentIndex);
  const brick = brickCharacter.repeat((2 * currentIndex) - 1);
  items.push(`${space}${brick}${space}`);
  return currentIndex === nFloors ? items : towerBuilderRecursive(
    nFloors, 
    brickCharacter, 
    spacer, 
    currentIndex + 1, 
    items
  );
}
Enter fullscreen mode Exit fullscreen mode

We begin with our checks as usual to cover the test cases where we expect failure. From there we begin building the parts of our tower, namely, the space and the brick variables. The space is basically what we want to put on the left and right of the bricks and the brick itself is the body component the tower itself.

Imagine the following pseudo code:

floors = 3
brick = "+"
spacer = "-"
towerBuilderRecursive(floors, brick, spacer)
 -> first call
  -> currentIndex = 1
  -> space = spacer * (floors - currentIndex) = "--"
  -> brick = brick * (2 * currentIndex) - 1 = "+"
  -> items = ["--+--"]
 -> second call
  -> currentIndex = 2
  -> space = spacer * (floors - currentIndex) = "-"
  -> brick = brick * (2 * currentIndex) - 1 = "+++"
  -> items = ["--+--", "-+++-"]
 -> third call
  -> currentIndex = 3
  -> space = spacer * (floors - currentIndex) = ""
  -> brick = brick * (2 * currentIndex) - 1 = "+++++"
  -> items = ["--+--", "-+++-", "+++++"]
  -> currentIndex === floors -> return items
returns -> [
"--+--",
"-+++-",
"+++++"
]
Enter fullscreen mode Exit fullscreen mode

Conclusions

I always like to use recursion when I can and this was the perfect challenge to use it with. Overall the challenge itself was realtively simple once the algorithm was worked out and the math simplified down to what it is now. Thanks for reading and see you in the next one!

Top comments (0)