2

Here's the issue, I'm trying to display text within boxes, so I wrote this code:

string Lines = " If our road signs    Catch your eye          Smile        But don't forget        To buy          Burma Shave    ";
string Sign1 = "┎───────────────────┒\n";
string Sign2 = "│";
string Sign3 = "\n└───────────────────┘";

int highInt;
int lowInt;

for (int i = 0; i < 94; i++)
{
    lowInt = i * 19;
    highInt = lowInt + 19;
    string tempLine = Lines.Substring(lowInt, highInt);

    Console.Write(Sign1);
    Console.Write(Sign2);
    Console.Write(tempLine);
    Console.Write(Sign2);
    Console.Write(Sign3);

    ReadLine();

    tempLine = "";
}
Console.ReadLine();

But instead of outputting

┎───────────────────┒
│ If our road signs │
└───────────────────┘
┎───────────────────┒
│  catch your eye   │
└───────────────────┘
┎───────────────────┒
│       smile       │
└───────────────────┘

It outputs:

┎───────────────────┒
│ If our road signs │
└───────────────────┘
┎───────────────────┒
│   catch your eye          Smile      │
└───────────────────┘
┎───────────────────┒
│        Smile        But don't forget        To buy      │
└───────────────────┘

It seems like it's grabbing exponentially more characters: 19 then 38 then 57 and I'm not sure why. I'm new to C# so sorry if the answer's obvious.

Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
  • 3
    Why not split the string on multiple spaces and just use those strings? – Ron Beyer Aug 29 '18 at 20:32
  • 2
    If you debug your code you can see what `tempLine` becomes in each iteration. debug > identify the problem > fix It would be a better idea to store those strings in an array so you won't have to deal with substring. – Selman Genç Aug 29 '18 at 20:32
  • 2
    Yes, string[] arrays are your friend. Don't be afraid of it, it's so useful later on. – Iskandar Reza Aug 29 '18 at 20:33
  • 2
    Using StackOverflow as a debugger is a slow, unreliable way to debug your program. **Learn to use a debugger**; it is the most important skill you can learn to maximize your productivity. Some good advice for beginners is here: https://ericlippert.com/2014/03/05/how-to-debug-small-programs/ – Eric Lippert Aug 29 '18 at 20:47
  • 3
    Always ask yourself "how could I have caught this bug earlier?" Your problem here is that the code does not match its meaning, **and the meaning is impossible to understand from the code** because you've chosen `lowInt` and `highInt` as your variable names. If you have to name a variable based on its type instead of its meaning, you're doing something wrong. If you had named those variables `startPosition` and `endPosition` then the bug -- that `Substring`'s second argument is *not a position in the first place* -- would be much easier to see. – Eric Lippert Aug 29 '18 at 20:49

5 Answers5

2

You can leverage some cool C#/.NET features to reduce your code complexity. First, lets extract the "sign formatting" into its own reusable static class, while also adding some flexibility to it:

public static class SignFormatter
{
    private static char SignHorizontalSide = '─';   
    private static char SignTopLeft = '┎'; 
    private static char SignTopRight = '┒';
    private static char SignBottomLeft = '└';
    private static char SignBottomRight = '┘'; 
    private static char SignVerticalSide = '|';

    public static string FormatAsSign(string input, int length)
    {
        //Needed to adjust for end pipes
        length -= 2;

        StringBuilder sb = new StringBuilder();

        //calculates the padding needed to center the string in the sign
        int spaces = length - input.Length;
        int padLeft = spaces / 2 + input.Length;

        //Makes the sign with the centered text
        sb.AppendLine($"{SignTopLeft}{new String(SignHorizontalSide, length)}{SignTopRight}");
        sb.AppendLine($"{SignVerticalSide}{input.PadLeft(padLeft).PadRight(length)}{SignVerticalSide}");
        sb.AppendLine($"{SignBottomLeft}{new String(SignHorizontalSide, length)}{SignBottomRight}");

        return sb.ToString();
    }   
}

Now that this is in its own class, you can leverage Regex to split the input string on multiple spaces:

string Lines = " If our road signs    Catch your eye          Smile        But don't forget        To buy          Burma Shave    ";

//splits on multiple spaces, and only takes strings that arent empty
var splitLines = Regex.Split(Lines, @"\s{2,}").Where(s => s != String.Empty);

Then from there we just iterate over the splitLines IEnumerable and apply our sign formatting:

foreach(string s in splitLines)
{
    Console.Write(FormatAsSign(s, 21));
}

Based on your input, and a sign length of 21 you would get this output:

┎───────────────────┒
| If our road signs |
└───────────────────┘
┎───────────────────┒
|  Catch your eye   |
└───────────────────┘
┎───────────────────┒
|       Smile       |
└───────────────────┘
┎───────────────────┒
| But don't forget  |
└───────────────────┘
┎───────────────────┒
|      To buy       |
└───────────────────┘
┎───────────────────┒
|    Burma Shave    |
└───────────────────┘

I made a fiddle here so you can see it in action

maccettura
  • 10,514
  • 3
  • 28
  • 35
  • I prefer this due to the padding. Might even be slightly cleaner to even just use string interpolation for the padding and can then even remove the string builder altogether. – TyCobb Aug 29 '18 at 22:00
1

substring method takes two parameters.1st parameter is starting position. and the 2nd one is the length. In your case, your code should be like this.

Here's the official link.

https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.primitives.stringsegment.substring?view=aspnetcore-2.1

    string Lines = " If our road signs    Catch your eye          Smile        But don't forget        To buy          Burma Shave    ";
    string Sign1 = "┎───────────────────┒\n";
    string Sign2 = "│";
    string Sign3 = "\n└───────────────────┘";

    int length = 19;
    int lowInt;
    for (lowInt = 0; lowInt < Lines.Length ; lowInt+=length )
    {
        var unTraversed = Lines.Length - lowInt;
        if (unTraversed >= length)
        {
            string tempLine = Lines.Substring(lowInt, length);
            Console.Write(Sign1 + Sign2 + tempLine + Sign2 + Sign3);
            Console.ReadLine();
        }
        else
        {
            string tempLine = Lines.Substring(lowInt, Lines.Length - lowInt);
            Console.Write(Sign1 + Sign2 + tempLine + Sign2 + Sign3);
            Console.ReadLine();
        }

    }
    Console.ReadLine();
Shakhawat95
  • 443
  • 1
  • 6
  • 19
  • 1
    You are correct but still there are at least two errors. Try the code. – Steve Aug 29 '18 at 20:42
  • Uhm, no that's not one of the errors I see in the code. Look at that loop condition. And what happen when you write the last line of the box? – Steve Aug 29 '18 at 20:49
  • 1
    @Steve is right, `lowInt` will easily become more than the length of the string after few iterations and Substring will throw. – Selman Genç Aug 29 '18 at 20:54
  • I just edited my answer. Hopefully, it will work perfectly now. – Shakhawat95 Aug 29 '18 at 21:18
  • I appreciate your effort, but clearly you haven't tested it. After writing the Sign3 you need to add a newline or clear the Console. – Steve Aug 29 '18 at 21:24
  • @Steve as we are Console.readLine(), is it necessary to use newline after sign3? – Shakhawat95 Aug 29 '18 at 21:30
0

First, let's extract a method that centralize the string:

  private static String ToCenter(String value, int length) {
    if (length <= 0)
      return value;
    else if (string.IsNullOrEmpty(value))
      return new string(' ', length);
    else if (value.Length >= length)
      return value;

    return new string(' ', (length - value.Length) / 2) +
           value +
           new string(' ', (length - value.Length + 1) / 2);
  }

Now with a help of Linq let's process

  string Lines = " If our road signs    Catch your eye          Smile        But don't forget        To buy          Burma Shave    ";

  // Let's get rid of '\n' and have a clear text 
  string Sign1 = "┎───────────────────┒";
  string Sign2 = "│";
  string Sign3 = "└───────────────────┘";

we can create an enumerable of rectangles (please, avoid using magic numbers like 94, 19 but change them into Sign1.Length):

  using System.Linq;
  using System.Text.RegularExpressions;
  ...

  var rectangles = Regex
    .Split(Lines, @"\s{2,}")
    .Select(item => item.Trim())
    .Where(item => !string.IsNullOrEmpty(item))
  //.Take(3) // If you want at most 3 rectangles
    .Select(item => string.Join(Environment.NewLine, 
       Sign1, 
       Sign2 + ToCenter(item, Sign1.Length - 2) + Sign2, 
       Sign3));

  foreach (string rectangle in rectangles) {
    // print out the next rectangle
    Console.WriteLine(rectangle);

    //TODO: add all the relevant code here
    // ReadLine();
  }

The only tricky thing is

  Regex.Split(Lines, @"\s{2,}") 

we split on two or more spaces:

 "Smile        But don't forget" -> string[]{" Smile", "But don't forget"}

Outcome:

┎───────────────────┒
│ If our road signs │
└───────────────────┘
┎───────────────────┒
│  Catch your eye   │
└───────────────────┘
┎───────────────────┒
│       Smile       │
└───────────────────┘
┎───────────────────┒
│ But don't forget  │
└───────────────────┘
┎───────────────────┒
│      To buy       │
└───────────────────┘
┎───────────────────┒
│    Burma Shave    │
└───────────────────┘

If you uncomment Take(3) you'll get

┎───────────────────┒
│ If our road signs │
└───────────────────┘
┎───────────────────┒
│  Catch your eye   │
└───────────────────┘
┎───────────────────┒
│       Smile       │
└───────────────────┘
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
0

Here is a solution that features various .NET API and C# language features:

And the method:

public void Boxify()
{
    var lines = " If our road signs    Catch your eye          Smile        But don't forget        To buy          Burma Shave    ";
    var phrases = Regex.Split(lines.Trim(), @"\s{2,}");
    var maxPhraseWidth = phrases.Max(x => x.Length) + 2;

    foreach (var phrase in phrases.Select(Box))
    {
        Console.WriteLine(phrase);
        Console.ReadLine();
    }

    string Box(string phrase)
    {
        var spaceCount = maxTextWidth - phrase.Length;
        var leftSpaceCount = spaceCount / 2;
        var rightSpaceCount = (spaceCount + 1) / 2;

        return $"┎{new string('─', maxTextWidth)}┒\n" +
               $"|{new string(' ', leftSpaceCount)}{phrase}{new string(' ', rightSpaceCount)}|\n" +
               $"└{new string('─', maxTextWidth)}┘";
    }
 }

Since you're new to C#, I want to point out that this is an idiomatic solution and adheres to generally adopted coding conventions for C# code.

Kit
  • 20,354
  • 4
  • 60
  • 103
0

As others have pointed out, you're calling Substring with incorrect values. It takes a starting position and a length (and you're passing in an end position).

However, a different way to approach this problem might be to write a method that generates a sign for you, with specific text and a specific width, and then you can simply call that method as many times as you like to generate equal-sized signs.

If we write a method that takes in a string and a width, we can generate a sign accordingly. One question that occurs quickly, however, is what do we do if the string is longer than the width? I think there are two options:

  1. Truncate the string to fit the width
  2. Expand the sign to fit the string

So I've added an expandWidth parameter that, when set to true, expands the sign to accommodate the longest string. If set to false, it will truncate the string.

The rest of the method is fairly self-explanatory: Split the input string on the newline characters in case the sign should have more than one line, determine the longest line we need to display and adjust our sign width if expandWidth is true, write out our header string (the top of the sign), use Substring, PadLeft, and PadRight to center each line within the width, and finally write our footer (the bottom of the sign):

public static void  WriteSign(string signText, int signWidth = 10, bool expandWidth = true)
{
    // Split input into lines, in case there's 
    // more than one line to display on the sign
    var lines = signText
        .Split(new[] {'\r', '\n'}, StringSplitOptions.None)
        .Select(line => line.Trim());

    // Determine the sign width based on the longest line
    var actualSignWidth = expandWidth 
        ? Math.Max(lines.Max(l => l.Length), signWidth) 
        : signWidth;

    // Write header
    Console.WriteLine("╔" + new string('═', Math.Max(0, actualSignWidth)) + "╗");

    // Write lines
    foreach (var line in lines)
    {
        var signLine = line.Substring(0, Math.Min(line.Length, actualSignWidth))
        .PadLeft(Math.Min(actualSignWidth, (actualSignWidth + line.Length) / 2))
        .PadRight(actualSignWidth);

        Console.WriteLine("║" + signLine + "║");
    }

    // Write footer
    Console.WriteLine("╚" + new string('═', Math.Max(0, actualSignWidth)) + "╝");
}

Now we can create a string with \n characters between each group of words, and we can display them all on one sign, or split them and display a bunch of separate signs.

For example:

private static void Main(string[] cmdArgs)
{
    var signText = "If our road signs\nCatch your eye\nSmile\nBut don't forget\nTo buy\nBurma shave";

    var splitText = signText.Split('\n');
    var signWidth = splitText.Max(line => line.Length) + 2;

    // Write a sign with all the lines on one sign
    WriteSign(signText, signWidth);

    // Write a divider to separate the first sign from the rest
    Console.WriteLine(new string('-', Console.WindowWidth));

    // Write a separate sign for each line
    foreach (var line in splitText) WriteSign(line, signWidth);

    GetKeyFromUser("\nDone! Press any key to exit...");
}

Output

enter image description here

Rufus L
  • 36,127
  • 5
  • 30
  • 43