Saturday 22 May 2021

Web Tutorial: The Ruby CrossBox

Some time ago, someone forwarded me this programming joke.



It was a pretty amusing one because I could relate. When I was a student, we would get these practice questions and I would fantasize about getting cheeky like that.

Well, today we will be doing this exercise for real. The above exercise looks to have been done in C++, but my C++ is rusty AF. So I will be using Ruby because it's been a while since I touched her gloriously sexy syntax. (Boy, that came out weird)

Since the question wants us to use a nested For loop, this is what we'll begin with. Obviously, we will deal with rows and columns, thus each For loop we will use variables row and col. The box in the question is a 10 by 10 square, so this is what we will use for the variable size. Each iteration goes from 1 to 10.
size = 10

for row in 1..size do
    for col in 1..size do

    end
end


The variable display, in each iteration, is set to false initially.
size = 10

for row in 1..size do
    for col in 1..size do
        display = false
    end
end


If display is true, then we display "*". If not, we display a space. We use a print statement instead of puts because we don't want to automatically go to the next line.
for row in 1..size do
    for col in 1..size do
        display = false
    end

    if (display)
        print "*"
    else
        print " "
    end

end


We will, however, want output to go to the next line if it's the end of the line. It's the end of the line if col is equal to size.
for col in 1..size do
    display = false;

    if (display)
        print "*"
    else
        print " "
    end

    if (col == size)
        print "\n"
    end

end


Now what we need to do is set a condition where display is true. Note that the box has a border of asterisks. That means that if col or row is 1 or the maximum value, size, display is set to true.
for col in 1..size do
    display = false;

    if (row == 1 || col == 1 || row == size || col == size)
        display = true
    end


    if (display)
        print "*"
    else
        print " "
    end

    if (col == size)
        print "\n"
    end
end


This should net us a nice box of asterisks!


Now, in all other cases, it's still possible for display to be true. Look at the crossbox. Are there any patterns in the output we can exploit? I'll start - the asterisk is displayed if row is equal to col.
if (row == 1 || col == 1 || row == size || col == size)
    display = true
else
    if (row == col)
        display = true
    end

end


Now you get that sweet diagonal slash!


What about the other diagonal? That's a separate pattern. We want a diagonal from the other direction, so now we have to compare col to the current value of row subtracted from the value of size. So if row is for example, 2, that means col has to be at (10 - 2 = 7) for display to be true.
if (row == col || size - row == col)
    display = true
end


Well this is nice, but it's just a little bit off. It's in the correct general direction, though, so we just need to adjust slightly...


...like so.
if (row == col || size - row == col - 1)
    display = true
end


Yes!


Improvements to the code

Sure, we met the objectives of the question. We used a nested For loop to accomplish this. But when it that ever enough? We can make this better.

Let's perform some abstraction. First, let's place all that code into a function definition. Call that function generateCrossBox().

def generateCrossBox()
    size = 10

    for row in 1..size do
        for col in 1..size do
            display = false;

            if (row == 1 || col == 1 || row == size || col == size)
                display = true
            else
                if (row == col || size - row == col - 1)
                    display = true
                end
            end

            if (display)
                print "*"
            else
                print " "
            end

            if (col == size)
                print "\n"
            end
        end
    end
end


That function should allow a parameter, size. Remove that first line. size will now be defined by whatever argument is passed in.
def generateCrossBox(size)
    #size = 10

    for row in 1..size do
        for col in 1..size do
            display = false;

            if (row == 1 || col == 1 || row == size || col == size)
                display = true
            else
                if (row == col || size - row == col - 1)
                    display = true
                end
            end

            if (display)
                print "*"
            else
                print " "
            end

            if (col == size)
                print "\n"
            end
        end
    end
end


Then we call that function. Pass in a number, say, 20.
def generateCrossBox(size)
    #size = 10

    for row in 1..size do
        for col in 1..size do
            display = false;

            if (row == 1 || col == 1 || row == size || col == size)
                display = true
            else
                if (row == col || size - row == col - 1)
                    display = true
                end
            end

            if (display)
                print "*"
            else
                print " "
            end

            if (col == size)
                print "\n"
            end
        end
    end
end

generateCrossBox(20)


Hot damn!


Wait... we're not done. The code only works with even numbers, so let's do this. Multiply whatever size is, by 2 and set it to doubleSize.
def generateCrossBox(size)
    #size = 10

    doubleSize = size * 2

    for row in 1..size do
        for col in 1..size do
            display = false;

            if (row == 1 || col == 1 || row == size || col == size)
                display = true
            else
                if (row == col || size - row == col - 1)
                    display = true
                end
            end

            if (display)
                print "*"
            else
                print " "
            end

            if (col == size)
                print "\n"
            end
        end
    end
end


Then within the nested For loop, replace all instances of size with doubleSize. Now the code will always be performed on an even number, because we doubled it.
def generateCrossBox(size)
    #size = 10

    doubleSize = size * 2

    for row in 1..doubleSize do
        for col in 1..doubleSize do
            display = false;

            if (row == 1 || col == 1 || row == doubleSize || col == doubleSize)
                display = true
            else
                if (row == col || doubleSize - row == col - 1)
                    display = true
                end
            end

            if (display)
                print "*"
            else
                print " "
            end

            if (col == doubleSize)
                print "\n"
            end
        end
    end
end


Now you get a much larger crossbox, because the number has been doubled.


One more improvement...

Let's generate this crossbox using user input. Add this to the code. Here, we're printing out a message and then setting a variable, continue, to an empty string.
end

puts "Welcome to TeochewThunder's CrossBox!"

continue = ""


generateCrossBox(20)


We want this to continue for as long as continue is "Y" or "y". Since continue is an empty string, this code is going to run once, then stop.
puts "Welcome to TeochewThunder's CrossBox!"

continue = ""
begin
    generateCrossBox(20)
end while continue == "Y" or continue == "y"


Here, we ask the user to enter a value. This gets assigned to the variable size. We then pass in size instead of 20 to generateCrossBox().
begin
    puts "Please enter the size of your CrossBox:"
    size = gets.chomp    


    generateCrossBox(size)
end while continue == "Y" or continue == "y"

 
In the generateCrossBox() function, set the variable finalSize to size after converting size to an integer using the to_i() method. The next If block ensures that finalSize is at least 5. Finally, modify the code to set doubleSize to double of finalSize instead of size.
def generateCrossBox(size)
    #size = 10

    finalSize = size.to_i

    if (finalSize < 5)
        finalSize = 5
    end


    doubleSize = finalSize * 2

    for row in 1..doubleSize do


I entered "3", but it gives me a box with a size of 5!


But this is what happens when I enter 7!


Changing the message slightly would probably make things clearer for the user.
puts "Please enter the size of your CrossBox (min 5):"


Add another message asking for user input. This time, it's to ask the user if they want to continue. Set the variable continue to the value of the user input.
continue = ""
begin
    puts "Please enter the size of your CrossBox (min 5):"
    size = gets.chomp    

    generateCrossBox(size)

    puts "Do you wish to continue? (Y/N)"
    continue = gets.chomp

end while continue == "Y" or continue == "y"


So now you can go on for as long as you like!


Well, that was fun...

...but yes, kind of basic. Sure takes me back to my days of being a wide-eyed IT student, though!

May you live in e⛝citing times,
T___T

No comments:

Post a Comment