Print a rectangular frame around array values in C# - 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();
}
}
}

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...

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();

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'.

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.

Related

Rotating a multiline string 90 degrees - lines become columns

Say I have a string like
Line 1
Line 2
I want to turn this string 90 degrees clockwise, so that it becomes
LL
ii
nn
ee
12
The rotation needs to be performed only once such that it is in effect 'turning lines into columns.' Performing it twice should give the original string. (If it was truly rotating by 90 degrees, it would have to be repeated four times to arrive back at the original.)
using System;
using System.Collections;
using System.Collections.Generic;
namespace rotateString
{
class Program
{
static void Main(string[] args)
{
var strings = new string[] { "Line 1", "Line 2" };
var lines = new List<string>();
var done = false;
var j = 0;
do
{
var line = "";
for (var i = 0; i < strings.Length; ++i)
{
var s = strings[i];
if (j >= s.Length)
{
done = true;
break;
}
line += s[j];
}
lines.Add(line);
++j;
} while (!done);
for(int i=0; i < lines.Count; ++i)
{
Console.WriteLine(string.Format("{0} : '{1}'", i, lines[i]));
}
}
}
}
Output:
0 : 'LL'
1 : 'ii'
2 : 'nn'
3 : 'ee'
4 : ' '
5 : '12'
6 : ''
Note that this pretty much assumes the strings are all the same length. Adjusting it to work with the longest string length is trivial.
Using a StringBuilder would be a bit more efficient but, unless you're working with a lot of very long strings, you'll never see the difference in performance.
This was interesting to try out and write. I haven't spent time on validation and characters, but I was trying to see if I can write something that is somewhat "compact" (challenge to myself mostly in evening hours).
#KayZed I will also run your implementation, I see you did some more validations about the inputs.
#3Dave I see we had similar ideas about this ;)
My idea around this was a projection of the input strings (plus now I hard-coded the length of the Span<char>, that calculation can be made simple) into the "flattened" structure of all characters with the offset of the index based on the number of inputs.
My "solution":
using System.Collections.Generic;
public class Program
{
public static void Main(string[] args)
{
var inputs = new[] { "Short", "Line 1", "Line 2", "Much longer line 3", "🧐" };
Rotate(inputs);
}
public static void Rotate(IReadOnlyCollection<string> aValues)
{
Span<char> chars = stackalloc char[100];
var offset = 0;
foreach (var value in aValues)
{
var index = 0;
foreach (char character in value)
{
chars[index + offset] = character;
index += aValues.Count;
}
offset++;
}
var position = 0;
foreach (char character in chars)
{
Console.Write(character == default(char) ? ' ' : character);
Console.Write(' ');
if (position == aValues.Count - 1)
{
Console.WriteLine();
position = 0;
continue;
}
position++;
}
}
}
I probably missed some edge cases (and didn't handle the "special" characters), but hope this gives an idea on some "optimisations".
The output:
S L L M �
h i i u �
o n n c
r e e h
t
1 2 l
o
n
g
e
r
l
i
n
e
3
It will need to add leading spaces (i.e. columns which were preceding shorter lines in the original string.) This method gives you an option to make this visible (fillChar - several options provided as constants.)
Also the additional ReverseLineOrder method reverses the line order in case you would want to rotate 90 degrees, reversing all lines.
using System;
using System.Globalization;
using System.Text;
namespace Library.Text
{
public static class TextRotate
{
public const char Space = ' ';
public const char MiddleDot = '\u00b7';
public const char Circle = '\u25cb';
public const char BlackSquare = '\u25a0';
public const char NoBreakSpace = '\u00a0';
public static string LinesToColumns(string s, char fillChar = Space)
{
// A line feed at the end of the text is not seen as content.
// However, if the text ends in a line feed, we make sure the output ends in one, too.
bool endsWithNewline = s.EndsWith(Environment.NewLine);
string[] linesIn = s.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
int[][] textElements = new int[linesIn.Length][];
int longestLine_chars = 0;
int longestLine_elems = 0; // The longest line, in text elements
for (int l = 0; l < linesIn.Length; l++)
{
string line = linesIn[l];
if (line.Length > longestLine_chars)
{
longestLine_chars = line.Length;
}
var elems = StringInfo.ParseCombiningCharacters(line); // Gets indices of surrogate pairs, combining characters etc.
if (elems.Length > longestLine_elems)
{
longestLine_elems = elems.Length;
}
textElements[l] = elems;
}
// Go through columns (columns in the input text, and columns in terms of text elements - NOT chars)
string[] columns = new string[longestLine_elems];
var builder = new StringBuilder(longestLine_chars * linesIn.Length + Math.Max(longestLine_chars, linesIn.Length) * Environment.NewLine.Length);
for (int column = 0; column < longestLine_elems; column++)
{
builder.Clear();
System.Diagnostics.Debug.Assert(builder.Length == 0);
int cutoff = 0;
for (int l = 0; l < linesIn.Length; l++)
{
// Is the line long enough to reach to this column?
int[] lineTextElements = textElements[l];
int textElementsInLine = lineTextElements.Length;
if (textElementsInLine > column)
{
int firstCharIndex = lineTextElements[column];
if (column + 1 < textElementsInLine)
{
int nrOfChars = lineTextElements[column + 1] - firstCharIndex;
builder.Append(linesIn[l], firstCharIndex, nrOfChars);
}
else
{
builder.Append(linesIn[l], firstCharIndex, linesIn[l].Length - firstCharIndex);
}
cutoff = builder.Length;
}
else
{
builder.Append(fillChar);
}
}
// Trim the fill char off line endings (for when rotating back)
while (cutoff > 0 && builder[cutoff - 1] == fillChar)
{
cutoff--;
}
// Resulting column
columns[column] = builder.ToString(0, cutoff);
}
// Turn the columns into lines
builder.Clear();
foreach (var c in columns)
{
builder.AppendLine(c);
}
if (!endsWithNewline && builder.Length > 0)
{
builder.Length -= Environment.NewLine.Length;
}
return builder.ToString();
}
public static string ReverseLineOrder(string s)
{
// A line feed at the end of the text is not seen as content.
// However, if the text ends in a line feed, we make sure the output ends in one, too.
bool endsWithNewline = s.EndsWith(Environment.NewLine);
string[] linesIn = s.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
var builder = new StringBuilder(s.Length);
for (int l = linesIn.Length - (endsWithNewline ? 2 : 1); l >= 0; l--)
{
builder.AppendLine(linesIn[l]);
}
if (!endsWithNewline && builder.Length > 0)
{
builder.Length -= Environment.NewLine.Length;
}
return builder.ToString();
}
}
}

How to split characters of string equally between buttons?

I have an array of string elements of various words. I need the characters of each word be split equally into the text component of 3 buttons. For example, the array could hold the elements "maybe", "his", "car". In each game one of these words will be pulled from the array and its characters divided into the 3 buttons. For example, button 1 will have "ma", button 2 will have "yb" and button 3 "e" (for the word maybe). I then hide the text element of one button for the user to drag and drop the correct missing letter(s) into the space. The purpose of the game is to help children learn to spell. Does anyone know how I could go about dividing the characters equally into the 3 buttons?
Here's a function that would split the word into the amount of segments you want. You can then iterate over that list to set each segment to a button.Text.
public List<string> SplitInSegments(string word, int segments)
{
int wordLength = word.Length;
// The remainder tells us how many segments will get an extra letter
int remainder = wordLength % segments;
// The base length of a segment
// This is a floor division, because we're dividing ints.
// So 5 / 3 = 1
int segmentLength = wordLength / segments;
var result = new List<string>();
int startIndex = 0;
for (int i = 0; i < segments; i++)
{
// This segment may get an extra letter, if its index is smaller then the remainder
int currentSegmentLength = segmentLength + (i < remainder ? 1 : 0);
string currentSegment = word.Substring(startIndex, currentSegmentLength);
// Set the startindex for the next segment.
startIndex += currentSegmentLength;
result.Add(currentSegment);
}
return result;
}
usage:
// returns ["ma", "yb", "e"]
var segments = SplitInSegments("maybe", 3);
Edit
I like the fact that this is for teaching children. So here comes.
Regarding your question on splitting the string based on specific letter sequences: After you've split the string using regex, you will have an array of strings. Then determine the amount of items in the splitted string and concatenate or split further based on the number of segments:
// sequences to split on first
static readonly string[] splitSequences = {
"el",
"ol",
"bo"
};
static readonly string regexDelimiters = string.Join('|', splitSequences.Select(s => "(" + s + ")"));
// Method to split on sequences
public static List<string> SplitOnSequences(string word)
{
return Regex.Split(word, regexDelimiters).Where(s => !string.IsNullOrEmpty(s)).ToList();
}
public static List<string> SplitInSegments(string word, int segments)
{
int wordLength = word.Length;
// The remainder tells us how many segments will get an extra letter
int remainder = wordLength % segments;
// The base length of a segment
// This is a floor division, because we're dividing ints.
// So 5 / 3 = 1
int segmentLength = wordLength / segments;
var result = new List<string>();
int startIndex = 0;
for (int i = 0; i < segments; i++)
{
// This segment may get an extra letter, if its index is smaller then the remainder
int currentSegmentLength = segmentLength + (i < remainder ? 1 : 0);
string currentSegment = word.Substring(startIndex, currentSegmentLength);
// Set the startindex for the next segment.
startIndex += currentSegmentLength;
result.Add(currentSegment);
}
return result;
}
// Splitword will now always return 3 segments
public static List<string> SplitWord(string word)
{
if (word == null)
{
throw new ArgumentNullException(nameof(word));
}
if (word.Length < 3)
{
throw new ArgumentException("Word must be at least 3 characters long", nameof(word));
}
var splitted = SplitOnSequences(word);
var result = new List<string>();
if (splitted.Count == 1)
{
// If the result is not splitted, just split it evenly.
result = SplitInSegments(word, 3);
}
else if (splitted.Count == 2)
{
// If we've got 2 segments, split the shortest segment again.
if (splitted[1].Length > splitted[0].Length
&& !splitSequences.Contains(splitted[1]))
{
result.Add(splitted[0]);
result.AddRange(SplitInSegments(splitted[1], 2));
}
else
{
result.AddRange(SplitInSegments(splitted[0], 2));
result.Add(splitted[1]);
}
}
else // splitted.Count >= 3
{
// 3 segments is good.
result = splitted;
// More than 3 segments, combine some together.
while (result.Count > 3)
{
// Find the shortest combination of two segments
int shortestComboCount = int.MaxValue;
int shortestComboIndex = 0;
for (int i = 0; i < result.Count - 1; i++)
{
int currentComboCount = result[i].Length + result[i + 1].Length;
if (currentComboCount < shortestComboCount)
{
shortestComboCount = currentComboCount;
shortestComboIndex = i;
}
}
// Combine the shortest segments and replace in the result.
string combo = result[shortestComboIndex] + result[shortestComboIndex + 1];
result.RemoveAt(shortestComboIndex + 1);
result[shortestComboIndex] = combo;
}
}
return result;
}
Now when you call the code:
// always returns three segments.
var splitted = SplitWord(word);
Here is another approach.
First make sure that the word can be divided by the desired segments (add a dummy space if necessary) , then use a Linq statement to get your parts and when adding the result trim away the dummy characters.
public static string[] SplitInSegments(string word, int segments)
{
while(word.Length % segments != 0) { word+=" ";}
var result = new List<string>();
for(int x=0; x < word.Count(); x += word.Length / segments)
result.Add((new string(word.Skip(x).Take(word.Length / segments).ToArray()).Trim()));
return result.ToArray();
}
You can split your string into a list and generate buttons based on your list. The logic for splitting the word into a string list would be something similar to this:
string test = "maybe";
List list = new List();
int i = 0, len = 2;
while(i <= test.Length)
{
int lastIndex = test.Length - 1;
list.Add(test.Substring(i, i + len > lastIndex? (i + len) - test.Length : len));
i += len;
}
HTH

Divide the elements of a given string

I should divide the elements into several small substrings with equal length. The count of the substrings should be equal to the given partitions. If the string cannot be exactly divided into the given partitions, I must make all partitions except the last with equal lengths, and make the last one – the longest.
I've tried to make the first part, but it's not working in all cases. Can you show me a way but if it's possible with for-loops, etc.?
For example:
{abcd}, 3 partitions -> {a, b, cd}; {qrstuvwxyz}, 5 partitions -> {qr st uv wx yz}
private static List<string> Divide(List<string> input, int index, int partitions)
{
string stringToDivide = input[index];
input.RemoveAt(index);
string add = "";
if (stringToDivide.Length % partitions == 0)
{
for (int i = 0; i < stringToDivide.Length; i++)
{
add += stringToDivide[i] + " ";
}
input.Insert(index, add.Trim());
}
else
{
}
return input;
Console.WriteLine(string.Join(' ', input));
}
You can do something like this:
private static List<string> Divide(List<string> input, int index, int partitions)
{
var stringToDivide = input[index];
input.RemoveAt(index);
var stringToAdd = "";
var partitionLength = stringToDivide.Length / partitions;
for (int i = 0, partitionNum = 0; i < stringToDivide.Length; i++)
{
if (i % partitionLength == 0 && partitionNum != partitions) // skip space in last part
{
if (i > 0) // do not add leading space
{
stringToAdd += " ";
}
partitionNum++;
}
stringToAdd += stringToDivide[i];
}
input.Insert(index, stringToAdd);
return input;
}
This code adds spaces each partitionLength, but skips space for last string. Note that for long strings it's better to use StringBuilder.
stringToDivide.Length % partitions is the number of characters left over after dividing into partitions number of partitions.
stringToDivide.Length / partitions is the number of characters that should be in each partition except the last, which should have the left-over characters appended to it.
So just take the first partitions number of stringToDivide.Length / partitions length chunks, and append what's left over to the last chunk.
When the string is evenly divisible, stringToDivide.Length % partitions is zero, so it's not a special case.
public static List<string> DivideIntoPartitions(string stringToDivide, int partitions)
{
var parts = new List<string>(partitions);
var len = stringToDivide.Length;
if (len < partitions)
{
throw new ArgumentException("partitions should be less than length");
}
if (len % partitions == 0)
{
var eachSubstrLength = len / partitions;
for (int i = 0; i < stringToDivide.Length; i += eachSubstrLength)
{
parts.Add(stringToDivide.Substring(i, eachSubstrLength));
}
}
else
{
var nextDivisibleNumber = len + (partitions - (len % partitions));
var lengthOfLastSubstr = nextDivisibleNumber / partitions;
var lastItem = stringToDivide.Substring((len - lengthOfLastSubstr));
stringToDivide = stringToDivide.Remove((len - lengthOfLastSubstr));
var chunksize = stringToDivide.Length / (partitions - 1);
for (int i = 0; i < stringToDivide.Length; i += chunksize)
{
parts.Add(stringToDivide.Substring(i, chunksize));
}
parts.Add(lastItem);
}
return parts;
}
var result = DivideIntoPartitions("qrstuvwxyz", 3);

How to get all the possible 3 letter permutations? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Listing all permutations of a string/integer
For example,
aaa .. aaz .. aba .. abz .. aca .. acz .. azz .. baa .. baz .. bba .. bbz .. zzz
Basically, imagine counting binary but instead of going from 0 to 1, it goes from a to z.
I have been trying to get this working to no avail and the formula is getting quite complex. I'm not sure if there's a simpler way to do it.
Edit
I have something like this at the moment but it's not quite there and I'm not sure if there is a better way:
private IEnumerable<string> GetWordsOfLength(int length)
{
char letterA = 'a', letterZ = 'z';
StringBuilder currentLetters = new StringBuilder(new string(letterA, length));
StringBuilder endingLetters = new StringBuilder(new string(letterZ, length));
int currentIndex = length - 1;
while (currentLetters.ToString() != endingLetters.ToString())
{
yield return currentLetters.ToString();
for (int i = length - 1; i > 0; i--)
{
if (currentLetters[i] == letterZ)
{
for (int j = i; j < length; j++)
{
currentLetters[j] = letterA;
}
if (currentLetters[i - 1] != letterZ)
{
currentLetters[i - 1]++;
}
}
else
{
currentLetters[i]++;
break;
}
}
}
}
For a variable amount of letter combinations, you can do the following:
var alphabet = "abcdefghijklmnopqrstuvwxyz";
var q = alphabet.Select(x => x.ToString());
int size = 4;
for (int i = 0; i < size - 1; i++)
q = q.SelectMany(x => alphabet, (x, y) => x + y);
foreach (var item in q)
Console.WriteLine(item);
var alphabet = "abcdefghijklmnopqrstuvwxyz";
//or var alphabet = Enumerable.Range('a', 'z' - 'a' + 1).Select(i => (char)i);
var query = from a in alphabet
from b in alphabet
from c in alphabet
select "" + a + b + c;
foreach (var item in query)
{
Console.WriteLine(item);
}
__EDIT__
For a general solution, you can use the CartesianProduct here
int N = 4;
var result = Enumerable.Range(0, N).Select(_ => alphabet).CartesianProduct();
foreach (var item in result)
{
Console.WriteLine(String.Join("",item));
}
// Eric Lippert’s Blog
// Computing a Cartesian Product with LINQ
// http://blogs.msdn.com/b/ericlippert/archive/2010/06/28/computing-a-cartesian-product-with-linq.aspx
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
// base case:
IEnumerable<IEnumerable<T>> result = new[] { Enumerable.Empty<T>() };
foreach (var sequence in sequences)
{
var s = sequence; // don't close over the loop variable
// recursive case: use SelectMany to build the new product out of the old one
result =
from seq in result
from item in s
select seq.Concat(new[] { item });
}
return result;
}
You have 26^3 counts for 3 "digits". Just iterate from 'a' to 'z' in three loops.
Here's a very simple solution:
for(char first = 'a'; first <= (int)'z'; first++)
for(char second = 'a'; second <= (int)'z'; second++)
for(char third = 'a'; third <= (int)'z'; third++)
Console.WriteLine(first.ToString() + second + third);

Fastest function to generate Excel column letters in C#

What is the fastest c# function that takes and int and returns a string containing a letter or letters for use in an Excel function? For example, 1 returns "A", 26 returns "Z", 27 returns "AA", etc.
This is called tens of thousands of times and is taking 25% of the time needed to generate a large spreadsheet with many formulas.
public string Letter(int intCol) {
int intFirstLetter = ((intCol) / 676) + 64;
int intSecondLetter = ((intCol % 676) / 26) + 64;
int intThirdLetter = (intCol % 26) + 65;
char FirstLetter = (intFirstLetter > 64) ? (char)intFirstLetter : ' ';
char SecondLetter = (intSecondLetter > 64) ? (char)intSecondLetter : ' ';
char ThirdLetter = (char)intThirdLetter;
return string.Concat(FirstLetter, SecondLetter, ThirdLetter).Trim();
}
I currently use this, with Excel 2007
public static string ExcelColumnFromNumber(int column)
{
string columnString = "";
decimal columnNumber = column;
while (columnNumber > 0)
{
decimal currentLetterNumber = (columnNumber - 1) % 26;
char currentLetter = (char)(currentLetterNumber + 65);
columnString = currentLetter + columnString;
columnNumber = (columnNumber - (currentLetterNumber + 1)) / 26;
}
return columnString;
}
and
public static int NumberFromExcelColumn(string column)
{
int retVal = 0;
string col = column.ToUpper();
for (int iChar = col.Length - 1; iChar >= 0; iChar--)
{
char colPiece = col[iChar];
int colNum = colPiece - 64;
retVal = retVal + colNum * (int)Math.Pow(26, col.Length - (iChar + 1));
}
return retVal;
}
As mentioned in other posts, the results can be cached.
I can tell you that the fastest function will not be the prettiest function. Here it is:
private string[] map = new string[]
{
"A", "B", "C", "D", "E" .............
};
public string getColumn(int number)
{
return map[number];
}
Don't convert it at all. Excel can work in R1C1 notation just as well as in A1 notation.
So (apologies for using VBA rather than C#):
Application.Worksheets("Sheet1").Range("B1").Font.Bold = True
can just as easily be written as:
Application.Worksheets("Sheet1").Cells(1, 2).Font.Bold = True
The Range property takes A1 notation whereas the Cells property takes (row number, column number).
To select multiple cells: Range(Cells(1, 1), Cells(4, 6)) (NB would need some kind of object qualifier if not using the active worksheet) rather than Range("A1:F4")
The Columns property can take either a letter (e.g. F) or a number (e.g. 6)
Here's my version: This does not have any limitation as such 2-letter or 3-letter.
Simply pass-in the required number (starting with 0) Will return the Excel Column Header like Alphabet sequence for passed-in number:
private string GenerateSequence(int num)
{
string str = "";
char achar;
int mod;
while (true)
{
mod = (num % 26) + 65;
num = (int)(num / 26);
achar = (char)mod;
str = achar + str;
if (num > 0) num--;
else if (num == 0) break;
}
return str;
}
I did not tested this for performance, if someone can do that will great for others.
(Sorry for being lazy) :)
Cheers!
You could pre-generate all the values into an array of strings. This would take very little memory and could be calculated on the first call.
Here is a concise implementation using LINQ.
static IEnumerable<string> GetExcelStrings()
{
string[] alphabet = { string.Empty, "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
return from c1 in alphabet
from c2 in alphabet
from c3 in alphabet.Skip(1) // c3 is never empty
where c1 == string.Empty || c2 != string.Empty // only allow c2 to be empty if c1 is also empty
select c1 + c2 + c3;
}
This generates A to Z, then AA to ZZ, then AAA to ZZZ.
On my PC, calling GetExcelStrings().ToArray() takes about 30 ms. Thereafter, you can refer to this array of strings if you need it thousands of times.
Once your function has run, let it cache the results into a dictionary. So that, it won't have to do the calculation again.
e.g. Convert(27) will check if 27 is mapped/stored in dictionary. If not, do the calculation and store "AA" against 27 in the dictionary.
The absolute FASTEST, would be capitalizing that the Excel spreadsheet only a fixed number of columns, so you would do a lookup table. Declare a constant string array of 256 entries, and prepopulate it with the strings from "A" to "IV". Then you simply do a straight index lookup.
Try this function.
// Returns name of column for specified 0-based index.
public static string GetColumnName(int index)
{
var name = new char[3]; // Assumes 3-letter column name max.
int rem = index;
int div = 17576; // 26 ^ 3
for (int i = 2; i >= 0; i++)
{
name[i] = alphabet[rem / div];
rem %= div;
div /= 26;
}
if (index >= 676)
return new string(name, 3);
else if (index >= 26)
return new string(name, 2);
else
return new string(name, 1);
}
Now it shouldn't take up that much memory to pre-generate each column name for every index and store them in a single huge array, so you shouldn't need to look up the name for any column twice.
If I can think of any further optimisations, I'll add them later, but I believe this function should be pretty quick, and I doubt you even need this sort of speed if you do the pre-generation.
Your first problem is that you are declaring 6 variables in the method. If a methd is going to be called thousands of times, just moving those to class scope instead of function scope will probably cut your processing time by more than half right off the bat.
This is written in Java, but it's basically the same thing.
Here's code to compute the label for the column, in upper-case, with a 0-based index:
public static String findColChars(long index) {
char[] ret = new char[64];
for (int i = 0; i < ret.length; ++i) {
int digit = ret.length - i - 1;
long test = index - powerDown(i + 1);
if (test < 0)
break;
ret[digit] = toChar(test / (long)(Math.pow(26, i)));
}
return new String(ret);
}
private static char toChar(long num) {
return (char)((num % 26) + 65);
}
Here's code to compute 0-based index for the column from the upper-case label:
public static long findColIndex(String col) {
long index = 0;
char[] chars = col.toCharArray();
for (int i = 0; i < chars.length; ++i) {
int cur = chars.length - i - 1;
index += (chars[cur] - 65) * Math.pow(26, i);
}
return index + powerDown(chars.length);
}
private static long powerDown(int limit) {
long acc = 0;
while (limit > 1)
acc += Math.pow(26, limit-- - 1);
return acc;
}
#Neil N -- nice code I think the thirdLetter should have a +64 rather than +65 ? am I right?
public string Letter(int intCol) {
int intFirstLetter = ((intCol) / 676) + 64;
int intSecondLetter = ((intCol % 676) / 26) + 64;
int intThirdLetter = (intCol % 26) + 65; ' SHOULD BE + 64?
char FirstLetter = (intFirstLetter > 64) ? (char)intFirstLetter : ' ';
char SecondLetter = (intSecondLetter > 64) ? (char)intSecondLetter : ' ';
char ThirdLetter = (char)intThirdLetter;
return string.Concat(FirstLetter, SecondLetter, ThirdLetter).Trim();
}
Why don't we try factorial?
public static string GetColumnName(int index)
{
const string letters = "ZABCDEFGHIJKLMNOPQRSTUVWXY";
int NextPos = (index / 26);
int LastPos = (index % 26);
if (LastPos == 0) NextPos--;
if (index > 26)
return GetColumnName(NextPos) + letters[LastPos];
else
return letters[LastPos] + "";
}
Caching really does cut the runtime of 10,000,000 random calls to 1/3 its value though:
static Dictionary<int, string> LetterDict = new Dictionary<int, string>(676);
public static string LetterWithCaching(int index)
{
int intCol = index - 1;
if (LetterDict.ContainsKey(intCol)) return LetterDict[intCol];
int intFirstLetter = ((intCol) / 676) + 64;
int intSecondLetter = ((intCol % 676) / 26) + 64;
int intThirdLetter = (intCol % 26) + 65;
char FirstLetter = (intFirstLetter > 64) ? (char)intFirstLetter : ' ';
char SecondLetter = (intSecondLetter > 64) ? (char)intSecondLetter : ' ';
char ThirdLetter = (char)intThirdLetter;
String s = string.Concat(FirstLetter, SecondLetter, ThirdLetter).Trim();
LetterDict.Add(intCol, s);
return s;
}
I think caching in the worst-case (hit every value) couldn't take up more than 250kb (17576 possible values * (sizeof(int)=4 + sizeof(char)*3 + string overhead=2)
It is recursive. Fast, and right :
class ToolSheet
{
//Not the prettyest but surely the fastest :
static string[] ColName = new string[676];
public ToolSheet()
{
ColName[0] = "A";
for (int index = 1; index < 676; ++index) Recurse(index, index);
}
private int Recurse(int i, int index)
{
if (i < 1) return 0;
ColName[index] = ((char)(65 + i % 26)).ToString() + ColName[index];
return Recurse(i / 26, index);
}
public string GetColName(int i)
{
return ColName[i - 1];
}
}
sorry there was a shift. corrected.
class ToolSheet
{
//Not the prettyest but surely the fastest :
static string[] ColName = new string[676];
public ToolSheet()
{
for (int index = 0; index < 676; ++index)
{
Recurse(index, index);
}
}
private int Recurse(int i, int index)
{
if (i < 1)
{
if (index % 26 == 0 && index > 0) ColName[index] = ColName[index - 1].Substring(0, ColName[index - 1].Length - 1) + "Z";
return 0;
}
ColName[index] = ((char)(64 + i % 26)).ToString() + ColName[index];
return Recurse(i / 26, index);
}
public string GetColName(int i)
{
return ColName[i - 1];
}
}
My solution:
static class ExcelHeaderHelper
{
public static string[] GetHeaderLetters(uint max)
{
var result = new List<string>();
int i = 0;
var columnPrefix = new Queue<string>();
string prefix = null;
int prevRoundNo = 0;
uint maxPrefix = max / 26;
while (i < max)
{
int roundNo = i / 26;
if (prevRoundNo < roundNo)
{
prefix = columnPrefix.Dequeue();
prevRoundNo = roundNo;
}
string item = prefix + ((char)(65 + (i % 26))).ToString(CultureInfo.InvariantCulture);
if (i <= maxPrefix)
{
columnPrefix.Enqueue(item);
}
result.Add(item);
i++;
}
return result.ToArray();
}
}
barrowc's idea is much more convenient and fastest than any conversion function! i have converted his ideas to actual c# code that i use:
var start = m_xlApp.Cells[nRow1_P, nCol1_P];
var end = m_xlApp.Cells[nRow2_P, nCol2_P];
// cast as Range to prevent binding errors
m_arrRange = m_xlApp.get_Range(start as Range, end as Range);
object[] values = (object[])m_arrRange.Value2;
private String columnLetter(int column) {
if (column <= 0)
return "";
if (column <= 26){
return (char) (column + 64) + "";
}
if (column%26 == 0){
return columnLetter((column/26)-1) + columnLetter(26) ;
}
return columnLetter(column/26) + columnLetter(column%26) ;
}
Just use an Excel formula instead of a user-defined function (UDF) or other program, per Allen Wyatt (https://excel.tips.net/T003254_Alphabetic_Column_Designation.html):
=SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),"")
(In my organization, using UDFs would be very painful.)
The code I'm providing is NOT C# (instead is python) but the logic can be used for any language.
Most of previous answers are correct. Here is one more way of converting column number to excel columns.
solution is rather simple if we think about this as a base conversion. Simply, convert the column number to base 26 since there is 26 letters only.
Here is how you can do this:
steps:
set the column as a quotient
subtract one from quotient variable (from previous step) because we need to end up on ascii table with 97 being a.
divide by 26 and get the remainder.
add +97 to remainder and convert to char (since 97 is "a" in ASCII table)
quotient becomes the new quotient/ 26 (since we might go over 26 column)
continue to do this until quotient is greater than 0 and then return the result
here is the code that does this :)
def convert_num_to_column(column_num):
result = ""
quotient = column_num
remainder = 0
while (quotient >0):
quotient = quotient -1
remainder = quotient%26
result = chr(int(remainder)+97)+result
quotient = int(quotient/26)
return result
print("--",convert_num_to_column(1).upper())
If you need to generate letters not starting only from A1
private static string GenerateCellReference(int n, int startIndex = 65)
{
string name = "";
n += startIndex - 65;
while (n > 0)
{
n--;
name = (char)((n % 26) + 65) + name;
n /= 26;
}
return name + 1;
}

Categories

Resources