Reputation: 13
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
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:
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
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
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
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