Solving Problems on HackerRank

dorinandreidragan - Aug 23 - - Dev Community

Solving Problems on HackerRank

Solving problems on HackerRank is a fantastic way to keep your coding skills sharp. With the right environment setup and a structured approach, you can solve problems efficiently and effectively. In this guide, I’ll walk you through my environment setup and demonstrate my approach to solving the staircase problem using Test-Driven Development (TDD) in C#.

Environment Setup

The following instructions assume you’ll be using C# with Visual Studio Code. Let's get everything set up!

Step 1 - Install .NET SDK

Note: Skip this step if you already have the .NET SDK installed.

First, head over to Microsoft’s .NET SDK for Visual Studio Code to install the SDK. Then, verify your installation with the following command:

dotnet --info
Enter fullscreen mode Exit fullscreen mode

You should see output similar to this:

Note: The output may differ depending on your operating system.

❯ dotnet --info
.NET SDK:
 Version:           8.0.108
 Commit:            665a05cea7
 Workload version:  8.0.100-manifests.109ff937
 ...
 # content truncated for readability
Enter fullscreen mode Exit fullscreen mode

Step 2 - Create a .NET Solution and Test Project

Next, let's create a .NET solution for the problem:

mkdir Staircase
cd Staircase
dotnet new sln -n Staircase
Enter fullscreen mode Exit fullscreen mode

Then, create a test project and add it to the solution:

dotnet new xunit -n Staircase.Tests
dotnet sln add Staircase.Tests
Enter fullscreen mode Exit fullscreen mode

Finally, build and test the solution to ensure everything is set up correctly:

dotnet build
dotnet test
Enter fullscreen mode Exit fullscreen mode

If all goes well, your test project should build and run successfully.

Step 3 - Run Tests with Hot Reload

To make your problem-solving process faster, use this command in your solution folder:

dotnet watch test --project Staircase.Tests
Enter fullscreen mode Exit fullscreen mode

This enables hot reloading, so your tests automatically run whenever you make changes, providing instant feedback. 🚀

The Problem-Solving Process

My approach to solving HackerRank problems involves Test-Driven Development (TDD). Here’s a brief overview of TDD and how I apply it.

A Bit of Theory

TDD is guided by three core laws:

  1. Write a failing test before writing any production code.
  2. Write only enough test code to fail, or fail to compile.
  3. Write only enough production code to make the failing test pass.

These laws guide the Red/Green/Refactor cycle:

  1. Create a unit test that fails. 🔴
  2. Write production code to make the test pass. 🟢
  3. Refactor the code to clean up any mess. ✨

For a deeper dive into TDD, I highly recommend reading The Cycles of TDD by Uncle Bob.

Problem-Solving in Action

Now, let's apply this process to the staircase problem.

In your Staircase.Tests project:

  • Create a StaircaseTests.cs file for your tests.
  • Run dotnet watch test --project Staircase.Tests in your terminal.

First Cycle

Let's start with the case when the staircase should be EmptyWhenNoStairs.

🔴 Create a Unit Test That Fails

Write a unit test that fails in the StaircaseTests.cs file:

[Fact]
public void EmptyWhenNoStairs()
{
    Assert.True(string.IsNullOrEmpty(Result.staircase(0)));
}
Enter fullscreen mode Exit fullscreen mode

Your terminal should show output like this:

dotnet watch 🚀 Started
  Determining projects to restore...
  All projects are up-to-date for restore.
/path/to/staircase/Staircase.Tests/StaircaseTests.cs(10,42): error CS0103: The name 'Result' does not exist in the current context [/path/to/staircase/Staircase.Tests/Staircase.Tests.csproj]
dotnet watch ❌ Exited with error code 1
dotnet watch ⏳ Waiting for a file to change before restarting dotnet...
Enter fullscreen mode Exit fullscreen mode

🟢 Write Production Code to Make the Test Pass

Create the Result class and the staircase method to fix the failing test:

internal class Result
{
    internal static string staircase(int n)
    {
        return string.Empty;
    }
}
Enter fullscreen mode Exit fullscreen mode

✨ Refactor the Code to Clean Up Any Mess

In this step, there’s no mess to clean up, so let’s move on.

Yay! We’ve solved one case, and our production code works! 🎉

Second Cycle

Now, let's move on to the next case: FirstLevelHasOneStair.

🔴 Create a Unit Test That Fails

[Fact]
public void FirstLevelHasOneStair()
{
    Assert.Equal("#", Result.staircase(1));
}
Enter fullscreen mode Exit fullscreen mode

dotnet watch test should show that this test is failing, which is exactly what we want.

🟢 Write Production Code to Make the Test Pass

Update the staircase method to make the test pass:

public static string staircase(int n)
{
    if (n == 1) return "#";
    return string.Empty;
}
Enter fullscreen mode Exit fullscreen mode

Your terminal should now display:

Passed!  - Failed:     0, Passed:     2, Skipped:     0, Total:     2, Duration: 3 ms - Staircase.Tests.dll (net8.0)
dotnet watch ⌚ Exited
dotnet watch ⏳ Waiting for a file to change before restarting dotnet...
Enter fullscreen mode Exit fullscreen mode

✨ Refactor the Code to Clean Up Any Mess

Again, there’s no need for cleanup at this point, but always make it a habit to check for any mess that needs tidying up.

⚠️ Remember, test code is as important as production code. Keep it clean!

Next Cycles

By now, you’ve got the hang of it. Think about the next small step or the next unit test that should fail.

Here are all the steps I followed, each representing a TDD cycle:

[Fact]
public void EmptyWhenNoStairs()
{
    Assert.True(string.IsNullOrEmpty(Result.staircase(0)));
}

[Fact]
public void FirstLevelHasOneStair()
{
    Assert.Equal("#", Result.staircase(1));
}

[Fact]
public void HasTheGivenNumberOfStairs()
{
    var stairs = Result.staircase(10).Split('\n');

    Assert.Equal(10, stairs.Length);
}

[Fact]
public void EachStairHasTheMaximumWidth()
{
    var stairs = Result.staircase(10).Split('\n');

    foreach (var stair in stairs)
    {
        Assert.Equal(10, stair.Length);
    }
}

[Fact]
public void EachStairEndsWithHash()
{
    var stairs = Result.staircase(10).Split('\n');

    foreach (var stair in stairs)
    {
        Assert.Equal('#', stair[10 - 1]);
    }
}

[Fact]
public void EachStairHasTheCorrespondingNumberOfHashes()
{
    var stairs = Result.staircase(10).Split('\n');

    for (int i = 0; i < 10; i++)
    {
        var expectedNoOfHashes = i + 1;
        var actualNoOfHases = stairs[i].ToCharArray().Count(c => c == '#');
        Assert.Equal(expectedNoOfHashes, actualNoOfHases);
    }
}
Enter fullscreen mode Exit fullscreen mode

And here’s my final solution for the staircase problem:

internal class Result
{
    internal static string staircase(int n)
    {
        var result = new StringBuilder();
        for (int currentLevel = 1; currentLevel <= n; currentLevel++)
        {
            result.Append(BuildStair(currentLevel, n));
        }
        return result.ToString();
    }

    private static string BuildStair(int currentLevel, int noOfLevels)
    {
        var hashes = "#".PadLeft(currentLevel, '#');
        var stair = hashes.PadLeft(noOfLevels, ' ');
        if (currentLevel != noOfLevels)
            stair += "\n";
        return stair;
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

By setting up your environment correctly and following a disciplined approach like TDD, you can tackle HackerRank challenges efficiently. Each small step in TDD, no matter how trivial, builds up to a robust and well-tested solution. The key is to move slowly and methodically—small, deliberate steps will ultimately lead to faster, more reliable problem-solving. Happy coding! 😄

. .