Centering a line of text

Posted on

Problem

I need a procedure that “centers” a string inside a string that is consecutive characters of a fixed length. For instance,

======================== Instructions ============================

or

**************************** hehe ********************************

might be printed by calling

PrintBetweenChars("Instructions",'=');

or

PrintBetweenChars("hehe",'*');

respectively. Per my rules, the output will always have atleast one leading character and space and one trailing character and space, and the input will be shrunk if need be. So if input is abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ and the maximum line length is only 26 and the character is '-' then the ouput is

- abcdefghijklmnopqrstuv -

Furthermore, to handle the case of input having an odd length like 5, there will be one more character on the left than right. So the output would be like

-- abcdefghijklmnopqrstu -

if the input was abcdefghijklmnopqrstu and the maximum line length was 26. I tried implementing this and came up with the messiest-looking procedure ever:

public static void PrintBetweenChars ( string input, char c )
{

    // Require input be at most (_outputMaxLength - 4) chars to accomodate 1 beginning and trailing char and space
    // Shrink input if necessary  
    int shrunkInputLen = OutputFormatter._outputMaxLength - 4;
    if ( input.Length > (shrunkInputLen) )
    {
        input.Remove(shrunkInputLen);
    }
    string charline = new String(c, (OutputFormatter._outputMaxLength - input.Length - 2) / 2);
    Console.WriteLine("{0} {1} {2}", (input.Length & 1) == 1 ? charline + c.ToString() : charline, input, charline);
}

Is there a more compact, elegant, efficient, clever and readable way of doing this using the .NET library?

Solution

In testing your code I noticed a crash with text longer than the max. Not sure if you consider this simpler but it does handle the tails case.

public void PrintBetweenChars(string input, char c)
{
    // Require input be at most (_outputMaxLength - 4) chars to accomodate 1 beginning and trailing char and space
    // Shrink input if necessary  
    string UserText = " " + input.Substring(0, Math.Min(OutputFormatter._outputMaxLength - 4, input.Length)) + " ";
    string DisplayText = UserText.PadLeft((OutputFormatter._outputMaxLength / 2) + (UserText.Length / 2) + (UserText.Length % 2), c);
    Console.WriteLine(DisplayText.PadRight(OutputFormatter._outputMaxLength, c));
}

Bug

One day I’ll have to stop doing this but… Unicode.

From what I understand of your function you always want a line to be the maximum number of characters. It won’t.

Here’s an example using your original code modified to use a max length of 64 rather the OutputFormatter stuff:

string e = "e";
string eAcute = "é".Normalize(NormalizationForm.FormD);
PrintBetweenChars(e, '*');
PrintBetweenChars(eAcute, '*');

// ******************************* e ******************************
// ****************************** é ******************************

The second output has 1 fewer ‘character’ in it.

Using NormalizationForm.FormD decomposes the string into its canonical form. I could have written var eAcute = "eu0301"; but I would have had to look up the modifier character (which, I’ve ended up doing anyway).

There’s no built-in way to do that in .NET, but if what worries you is the readability of your code, try some refactoring. Extract constants, use meaningful names, and pay attention to indenting. Here’s an example:

public void PrintCenteredAndSurrounded(string input, char surroundingChar)
{
    const int minimumSurroundingCharsOnEachSide = 1;
    const int surroundingSpaceLength = 1;

    int maxLengthToPrint =
        OutputFormatter._outputMaxLength 
        - (minimumSurroundingCharsOnEachSide + surroundingSpaceLength) * 2;
    if(input.Length > maxLengthToPrint)
    {
        input = input.Substring(0, maxLengthToPrint);
    }

    var surroundingCharlineLength =
        (OutputFormatter._outputMaxLength 
        - input.Length 
        - minimumSurroundingCharsOnEachSide 
        - surroundingSpaceLength) / 2;

    string charline = new String(surroundingChar, surroundingCharlineLength);

    var extraCharNeeded = (input.Length & 1) == 1;
    Console.WriteLine(
       "{0}{1} {2} {3}",
       extraCharNeeded ? surroundingChar.ToString() : "",
       charline,
       input,
       charline);
}

The code is now lengthier and perhaps too verbose, but you get the idea.

This is what I came up with…

public static string PrintBetweenChars(string value, char c, int maxLength = 26)
{
    var maxValueLength = maxLength - 2;

    if (value.Length > maxValueLength)
    {
        value = value.Remove(maxValueLength);
    }

    var paddingLength = maxLength - value.Length;
    var halfPaddingLength = paddingLength / 2;
    var extraPadding = halfPaddingLength * 2 != paddingLength ? 1 : 0;
    var left = new string(c, halfPaddingLength + extraPadding);
    var right = new string(c, halfPaddingLength);

    return $"{left} {value} {right}";
}

The important things to note:

  • I’ve made it a pure function, there’s no dependency on the OutputFormatter or the Console. The function should probably be renamed at this point.
  • maxLength is passed in, but it has a default value
  • I tried to name the variables so that the code is self documenting.
  • The last line uses a C# 6 feature called string interpolation. It can be replaced by a string.Format call if needed.

To use it you might do something like:

var value = PrintBetweenChars("hello world", '=', OutputFormatter._outputMaxLength);
Console.WriteLine(value);

Making it a pure function has many benefits. It could be pulled out into a library or perhaps turned into a string extension method.

Would it be weird to go?

Console.WriteLine("hello world".BetweenChars('=', 15));

Anyway, that was a fun one 🙂

Is there a more compact, elegant, efficient, clever and readable way of doing this using the .NET library?

There’s no built-in for something so specific, but it’s just a case of figuring out the logic of what you need and writing it out.

This is more compact, and I think more elegant, clever and (perhaps) readable. I don’t know about efficient, you’d have to test it.

private string FormatString(char c, string input, int maxLength)
{
    // The maximum length the text can be is four less than the whole maxLength, to allow for the space and character requirement either side
    int maxTextLength = maxLength - 4;

    // The text to display is therefore a substring of the original text, if the maxtextlength is shorter than the input length
    input = input.Substring(0, Math.Min(maxTextLength, input.Length));

    // surround the final text with spaces and one character
    input = c + " " + input + " " + c;

    // The number of characters to append now is the difference, but at least 0. Then half it (integer division), because they need to be the same either side
    int charCount = Math.Max((maxLength - input.Length) / 2, 0);

    // put the same number of the char either side, and padleft to add the additional one if required
    return string.Format("{0}{1}{0}", new string(c, charCount), input).PadLeft(maxLength, c);
}

That said, words like “elegant”, “clever” and “readable” are fairly subjective terms.

Leave a Reply

Your email address will not be published. Required fields are marked *