Перебираем алфавит - C# a-caz
Вопрос
У меня есть вопрос о переборе по алфавиту.Я хотел бы иметь цикл, который начинается с "a" и заканчивается на "z".После этого цикл начинается с "aa" и отсчитывается до "az".после этого начинается с "ba" до "bz" и так далее...
Кто-нибудь знает какое-нибудь решение?
Спасибо
Редактировать:Я забыл, что я присваиваю функции символ "a", тогда функция должна возвращать b.если вы даете "bnc", то функция должна возвращать "bnd".
Решение
Правка. Сделано так, как того требует последнее правление ОП
Это самое простое и проверенное решение:
static void Main(string[] args)
{
Console.WriteLine(GetNextBase26("a"));
Console.WriteLine(GetNextBase26("bnc"));
}
private static string GetNextBase26(string a)
{
return Base26Sequence().SkipWhile(x => x != a).Skip(1).First();
}
private static IEnumerable<string> Base26Sequence()
{
long i = 0L;
while (true)
yield return Base26Encode(i++);
}
private static char[] base26Chars = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
private static string Base26Encode(Int64 value)
{
string returnValue = null;
do
{
returnValue = base26Chars[value % 26] + returnValue;
value /= 26;
} while (value-- != 0);
return returnValue;
}
Другие советы
Первая попытка, только с a-z, затем aa-zz
public static IEnumerable<string> GetExcelColumns()
{
for (char c = 'a'; c <= 'z'; c++)
{
yield return c.ToString();
}
char[] chars = new char[2];
for (char high = 'a'; high <= 'z'; high++)
{
chars[0] = high;
for (char low = 'a'; low <= 'z'; low++)
{
chars[1] = low;
yield return new string(chars);
}
}
}
Обратите внимание, что это остановится на "zz".Конечно, здесь есть некоторое уродливое дублирование с точки зрения циклов.К счастью, это легко исправить - и к тому же это может быть еще более гибким:
Вторая попытка:более гибкий алфавит
private const string Alphabet = "abcdefghijklmnopqrstuvwxyz";
public static IEnumerable<string> GetExcelColumns()
{
return GetExcelColumns(Alphabet);
}
public static IEnumerable<string> GetExcelColumns(string alphabet)
{
foreach(char c in alphabet)
{
yield return c.ToString();
}
char[] chars = new char[2];
foreach(char high in alphabet)
{
chars[0] = high;
foreach(char low in alphabet)
{
chars[1] = low;
yield return new string(chars);
}
}
}
Теперь, если вы хотите сгенерировать только a, b, c, d, aa, ab, ac, ad, ba, ...ты бы позвонил GetExcelColumns("abcd")
.
Третья попытка (доработана дополнительно) - бесконечная последовательность
public static IEnumerable<string> GetExcelColumns(string alphabet)
{
int length = 0;
char[] chars = null;
int[] indexes = null;
while (true)
{
int position = length-1;
// Try to increment the least significant
// value.
while (position >= 0)
{
indexes[position]++;
if (indexes[position] == alphabet.Length)
{
for (int i=position; i < length; i++)
{
indexes[i] = 0;
chars[i] = alphabet[0];
}
position--;
}
else
{
chars[position] = alphabet[indexes[position]];
break;
}
}
// If we got all the way to the start of the array,
// we need an extra value
if (position == -1)
{
length++;
chars = new char[length];
indexes = new int[length];
for (int i=0; i < length; i++)
{
chars[i] = alphabet[0];
}
}
yield return new string(chars);
}
}
Вполне возможно, что это был бы более чистый код, использующий рекурсию, но это было бы не так эффективно.
Обратите внимание, что если вы хотите остановиться в определенной точке, вы можете просто использовать LINQ:
var query = GetExcelColumns().TakeWhile(x => x != "zzz");
"Перезапуск" итератора
Чтобы перезапустить итератор с заданной точки, вы действительно могли бы использовать SkipWhile
как было предложено thesoftwarejedi.Конечно, это довольно неэффективно.Если вы можете сохранить любое состояние между вызовами, вы можете просто сохранить итератор (для любого решения):
using (IEnumerator<string> iterator = GetExcelColumns())
{
iterator.MoveNext();
string firstAttempt = iterator.Current;
if (someCondition)
{
iterator.MoveNext();
string secondAttempt = iterator.Current;
// etc
}
}
В качестве альтернативы, вы вполне можете структурировать свой код таким образом, чтобы использовать foreach
в любом случае, просто остановитесь на первом значении, которое вы действительно можете использовать.
Следующее заполняет список необходимыми строками:
List<string> result = new List<string>();
for (char ch = 'a'; ch <= 'z'; ch++){
result.Add (ch.ToString());
}
for (char i = 'a'; i <= 'z'; i++)
{
for (char j = 'a'; j <= 'z'; j++)
{
result.Add (i.ToString() + j.ToString());
}
}
Я знаю, что здесь есть много ответов, и один из них принят, но IMO все они делают его сложнее, чем нужно. Я думаю, что следующее проще и чище:
static string NextColumn(string column){
char[] c = column.ToCharArray();
for(int i = c.Length - 1; i >= 0; i--){
if(char.ToUpper(c[i]++) < 'Z')
break;
c[i] -= (char)26;
if(i == 0)
return "A" + new string(c);
}
return new string(c);
}
Обратите внимание, что это не делает никакой проверки ввода. Если вы не доверяете своим абонентам, вы должны добавить проверку IsNullOrEmpty
в начале и проверку c[i] >= 'A' && c[i] <= 'Z' || c[i] >= 'a' && c[i] <= 'z'
в верхней части цикла. Или просто оставьте это, и пусть это будет GIGO .
Вы также можете найти использование для этих сопутствующих функций:
static string GetColumnName(int index){
StringBuilder txt = new StringBuilder();
txt.Append((char)('A' + index % 26));
//txt.Append((char)('A' + --index % 26));
while((index /= 26) > 0)
txt.Insert(0, (char)('A' + --index % 26));
return txt.ToString();
}
static int GetColumnIndex(string name){
int rtn = 0;
foreach(char c in name)
rtn = rtn * 26 + (char.ToUpper(c) - '@');
return rtn - 1;
//return rtn;
}
Эти две функции начинаются с нуля. То есть & Quot; A & Quot; = 0, & Quot; Z & Quot; = 25, & Quot; AA & Quot; = 26 и т. Д. Чтобы сделать их одностадийными (например, интерфейс COM в Excel), удалите строку над строкой с комментариями в каждой функции и раскомментируйте эти строки.
Как и в случае функции NextColumn
, эти функции не проверяют свои входные данные. Оба дают вам мусор, если это то, что они получают.
Вот & # 8217; вот что я придумала.
/// <summary>
/// Return an incremented alphabtical string
/// </summary>
/// <param name="letter">The string to be incremented</param>
/// <returns>the incremented string</returns>
public static string NextLetter(string letter)
{
const string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (!string.IsNullOrEmpty(letter))
{
char lastLetterInString = letter[letter.Length - 1];
// if the last letter in the string is the last letter of the alphabet
if (alphabet.IndexOf(lastLetterInString) == alphabet.Length - 1)
{
//replace the last letter in the string with the first leter of the alphbat and get the next letter for the rest of the string
return NextLetter(letter.Substring(0, letter.Length - 1)) + alphabet[0];
}
else
{
// replace the last letter in the string with the proceeding letter of the alphabet
return letter.Remove(letter.Length-1).Insert(letter.Length-1, (alphabet[alphabet.IndexOf(letter[letter.Length-1])+1]).ToString() );
}
}
//return the first letter of the alphabet
return alphabet[0].ToString();
}
просто любопытно, почему бы просто
private string alphRecursive(int c) {
var alphabet = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
if (c >= alphabet.Length) {
return alphRecursive(c/alphabet.Length) + alphabet[c%alphabet.Length];
} else {
return "" + alphabet[c%alphabet.Length];
}
}
Это похоже на отображение целого числа, использующего только основание 26 вместо базового 10. Попробуйте следующий алгоритм, чтобы найти n-ую запись массива
q = n div 26;
r = n mod 26;
s = '';
while (q > 0 || r > 0) {
s = alphabet[r] + s;
q = q div 26;
r = q mod 26;
}
Конечно, если вы хотите первые n записей, это не самое эффективное решение. В этом случае попробуйте что-то вроде решения Даниэля.
Я попробовал и придумал это:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Alphabetty
{
class Program
{
const string alphabet = "abcdefghijklmnopqrstuvwxyz";
static int cursor = 0;
static int prefixCursor;
static string prefix = string.Empty;
static bool done = false;
static void Main(string[] args)
{
string s = string.Empty;
while (s != "Done")
{
s = GetNextString();
Console.WriteLine(s);
}
Console.ReadKey();
}
static string GetNextString()
{
if (done) return "Done";
char? nextLetter = GetNextLetter(ref cursor);
if (nextLetter == null)
{
char? nextPrefixLetter = GetNextLetter(ref prefixCursor);
if(nextPrefixLetter == null)
{
done = true;
return "Done";
}
prefix = nextPrefixLetter.Value.ToString();
nextLetter = GetNextLetter(ref cursor);
}
return prefix + nextLetter;
}
static char? GetNextLetter(ref int letterCursor)
{
if (letterCursor == alphabet.Length)
{
letterCursor = 0;
return null;
}
char c = alphabet[letterCursor];
letterCursor++;
return c;
}
}
}
Вот кое-что, что я приготовил, может быть похожим. Я экспериментировал с количеством итераций, чтобы разработать схему нумерации, которая была бы как можно меньше, но в то же время дала мне достаточно уникальности.
Я знал, что каждый раз, когда добавляется символ Альфа, это увеличивает возможности в 26 раз, но я не был уверен, сколько букв, цифр или шаблон я хотел бы использовать.
Это привело меня к приведенному ниже коду. По сути, вы передаете ей строку AlphaNumber, и каждая позиция, в которой есть буква, в конечном итоге будет увеличиваться до & Quot; z \ Z & Quot; и каждая позиция, имеющая номер, в конечном итоге будет увеличиваться до " 9 ".
Таким образом, вы можете назвать это одним из двух способов.
//This would give you the next Itteration... (H3reIsaStup4dExamplf)
string myNextValue = IncrementAlphaNumericValue("H3reIsaStup4dExample")
//Or Loop it resulting eventually as "Z9zzZzzZzzz9zZzzzzzz"
string myNextValue = "H3reIsaStup4dExample"
while (myNextValue != null)
{
myNextValue = IncrementAlphaNumericValue(myNextValue)
//And of course do something with this like write it out
}
(Для меня я делал что-то вроде " 1AA000 ")
public string IncrementAlphaNumericValue(string Value)
{
//We only allow Characters a-b, A-Z, 0-9
if (System.Text.RegularExpressions.Regex.IsMatch(Value, "^[a-zA-Z0-9]+$") == false)
{
throw new Exception("Invalid Character: Must be a-Z or 0-9");
}
//We work with each Character so it's best to convert the string to a char array for incrementing
char[] myCharacterArray = Value.ToCharArray();
//So what we do here is step backwards through the Characters and increment the first one we can.
for (Int32 myCharIndex = myCharacterArray.Length - 1; myCharIndex >= 0; myCharIndex--)
{
//Converts the Character to it's ASCII value
Int32 myCharValue = Convert.ToInt32(myCharacterArray[myCharIndex]);
//We only Increment this Character Position, if it is not already at it's Max value (Z = 90, z = 122, 57 = 9)
if (myCharValue != 57 && myCharValue != 90 && myCharValue != 122)
{
myCharacterArray[myCharIndex]++;
//Now that we have Incremented the Character, we "reset" all the values to the right of it
for (Int32 myResetIndex = myCharIndex + 1; myResetIndex < myCharacterArray.Length; myResetIndex++)
{
myCharValue = Convert.ToInt32(myCharacterArray[myResetIndex]);
if (myCharValue >= 65 && myCharValue <= 90)
{
myCharacterArray[myResetIndex] = 'A';
}
else if (myCharValue >= 97 && myCharValue <= 122)
{
myCharacterArray[myResetIndex] = 'a';
}
else if (myCharValue >= 48 && myCharValue <= 57)
{
myCharacterArray[myResetIndex] = '0';
}
}
//Now we just return an new Value
return new string(myCharacterArray);
}
}
//If we got through the Character Loop and were not able to increment anything, we retun a NULL.
return null;
}
Вот моя попытка использовать рекурсию:
public static void PrintAlphabet(string alphabet, string prefix)
{
for (int i = 0; i < alphabet.Length; i++) {
Console.WriteLine(prefix + alphabet[i].ToString());
}
if (prefix.Length < alphabet.Length - 1) {
for (int i = 0; i < alphabet.Length; i++) {
PrintAlphabet(alphabet, prefix + alphabet[i]);
}
}
}
Тогда просто позвоните PrintAlphabet("abcd", "")
;