Ričards Mauriņš
Ričards Mauriņš

Reputation: 13

Print a rectangular frame around array values in C#

My task is to print words in an array in a rectangular frame. Like this:

*********
* Hello *
* World *
* in    *
* a     *
* Frame *
*********

I wrote a code witch works just fine, but I'm just curious - how can I do the same just with only one foreach cycle?

using System;

namespace RectangleAroundWords
{

class Program
{  
    public static void Main()
    {
        string[] words = new[] { "Hello", "World", "in", "a", "frame" };
        int length = 0;

        foreach (var item in words)
        {
            if (length < item.Length)
            {
                length = item.Length;
            }            
        }
        String tabs = new string('*', length + 4);

        Console.WriteLine(tabs);
        foreach (var item in words)
        {
            Console.WriteLine("* " + item.PadRight(length, ' ') + " *");
        }
        Console.WriteLine(tabs);
        Console.ReadKey();
    }
}
}

Upvotes: 0

Views: 1781

Answers (4)

Dryadwoods
Dryadwoods

Reputation: 2929

Based on the question: "how can I do the same just with only one foreach cycle?"

At this point, I only see 2 options:

  • If the string list is inserted by the user (BEFORE YOUR CODE and does not comes from a DB table), just read the length of the input and keep it on a MaxLengthTillNow, and then you are ok with it.
  • Crappy solution: make the "rectangle" fixed with with 100 :P no matter what you will write inside, will be ok..... but this is probably not the purpose of your problem :)

In conclusion: all the linq/lambdas/func, etc... solutions ARE always using "loops" behind doors..... so, probably there is no answer for your question.

Upvotes: 1

g t
g t

Reputation: 7463

You'd have to create coordinates then calculate the character at each coordinate somehow.

You know the maximum height without looping (words.Length) so increment a pointer and take modulo and divisor by this height to give x,y coords. Continue until you don't find any character at the given coordinates.

string[] words = new[] { "Hello", "World", "in", "a", "frame" };
int height = words.Length + 4;

int row = 0;
int column = 0;
void Write(char c)
{
    Console.SetCursorPosition(column, row);
    System.Console.Write(c);
}

int i = 0;
int completedWordCount = 0;
int? lastColumn = null;

do
{
    row = i % height;
    column = i / height;

    if (row == 0 || row == height - 1)
    {
        completedWordCount = 0;
        Write('*');
    }

    if (column == 0 || column == lastColumn)
    {
        Write('*');
    }

    if (row > 1 && row < height - 2 && column > 1)
    {
        string word = words[row - 2];

        if (column - 2 < word.Length)
        {
            Write(word[column - 2]);
        }
        else
        {
            completedWordCount++;
        }

        if (completedWordCount == words.Length && !lastColumn.HasValue)
        {
            lastColumn = column + 2;
        }
    }

    i++;
} while ((!lastColumn.HasValue || column < lastColumn) || row != height - 1);

Not exactly a foreach, but only one 'iteration'.

Upvotes: 1

abto
abto

Reputation: 1628

Although I think my first answer is the better approach in readability and best practices, it is possible to do this completley without any loop by using recursion (just to be a nit-picker ;-):

public static void Main()
{
    string[] words = new[] { "Hello", "World", "in", "a", "frame" };
    var output = Recurse(words);

    String tabs = new string('*', output.Item2 + 4);

    Console.WriteLine(tabs);
    Console.WriteLine(output.Item1);
    Console.WriteLine(tabs);

    Console.ReadKey();
}

Tuple<string, int> Recurse(string[] words, int index = 0, int maxLength = 0)
{
    maxLength = Math.Max(maxLength, words[index].Length);

    if (index < words.Length - 1)
    {
        var output = Recurse(words, index + 1, maxLength);

        maxLength = output.Item2;

        return Tuple.Create(
            string.Format("* {0} *{1}{2}", words[index].PadRight(maxLength), Environment.NewLine, output.Item1),
            maxLength);
    }

    return Tuple.Create(
        string.Format("* {0} *", words[index].PadRight(maxLength)),
        maxLength);
}

Compare and decide yourself...

Upvotes: 2

abto
abto

Reputation: 1628

With this snippet you can get the maximum length without the first loop:

int length = words.Select(w => w.Length).Max();

or shorter:

int length = words.Max(w => w.Length);

Also I think it would be better to first create the complete output string by using the StringBuilder class:

string[] words = new[] { "Hello", "World", "in", "a", "frame" };
int length = words.Max(w => w.Length);

var sb = new StringBuilder();

String tabs = new string('*', length + 4);

sb.AppendLine(tabs);
foreach (var item in words)
{
    sb.AppendFormat("* {0} *", item.PadRight(length));
    sb.AppendLine();
}
sb.AppendLine(tabs);

Console.WriteLine(sb.ToString());
Console.ReadKey();

Upvotes: 1

Related Questions