Перебор алфавита - C # a-caz - PullRequest
16 голосов
/ 18 июня 2009

У меня есть вопрос об итерации по алфавиту. Я хотел бы иметь цикл, который начинается с «а» и заканчивается «z». После этого цикл начинается с «aa» и считается «az». после этого начинается с "ba" до "bz" и так далее ...

Кто-нибудь знает какое-нибудь решение?

Спасибо

РЕДАКТИРОВАТЬ: я забыл, что я даю символ "a" для функции, то функция должна вернуть b. если вы дадите «bnc», то функция должна вернуть «bnd»

Ответы [ 10 ]

29 голосов
/ 18 июня 2009

Первое усилие, просто 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, просто выделив первое значение, которое вы действительно можете использовать.

22 голосов
/ 18 июня 2009

Редактировать: Сделано так, как хочет последнее редактирование ОП

Это самое простое и проверенное решение:

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;
}
3 голосов
/ 21 января 2011

Вот что я придумал.

/// <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();
}
3 голосов
/ 18 июня 2009

Я знаю, что здесь есть много ответов, и один из них принят, но ИМО все они усложняют, чем должны быть. Я думаю, что следующее проще и чище:

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;
}

Эти две функции начинаются с нуля. То есть «A» = 0, «Z» = 25, «AA» = 26 и т. Д. Чтобы сделать их одностадийными (как интерфейс COM в Excel), удалите строку над строкой с комментариями в каждой функции и раскомментируйте те линии.

Как и в случае функции NextColumn, эти функции не проверяют свои входные данные. Оба дают вам мусор, если это то, что они получают.

3 голосов
/ 18 июня 2009

Следующее заполняет список необходимыми строками:

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());
    }
}
1 голос
/ 25 марта 2010

просто любопытно, почему бы просто

    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];
         }
    }
0 голосов
/ 03 августа 2017

Вот кое-что, что я приготовил, может быть похоже. Я экспериментировал с количеством итераций, чтобы разработать схему нумерации, которая была бы как можно меньше, но в то же время дала мне достаточно уникальности.

Я знал, что каждый раз, когда добавляется символ Альфа, это увеличивает возможности в 26 раз, но я не был уверен, сколько букв, цифр или шаблон я хотел бы использовать.

Это привело меня к приведенному ниже коду. По сути, вы передаете ей строку AlphaNumber, и каждая позиция, в которой есть буква, будет постепенно увеличиваться до «z \ Z», а каждая позиция, имеющая число, в конечном итоге будет увеличиваться до «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;  
    }
0 голосов
/ 18 июня 2009

Я попробовал и придумал:

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;
        }
    }
}
0 голосов
/ 18 июня 2009

Вот моя попытка с использованием рекурсии:

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", "");

0 голосов
/ 18 июня 2009

Это похоже на отображение целого числа, используя только базу 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 записей, это не самое эффективное решение. В этом случае попробуйте что-нибудь вроде решения Даниэля.

...