Итерация по алфавиту — C# a-caz

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

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

Спасибо

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


person subprime    schedule 18.06.2009    source источник
comment
Это домашнее задание? Звучит как домашняя работа. . .   -  person Binary Worrier    schedule 18.06.2009
comment
И описание получше не помешало бы. Как у вас работает эта петля? У вас есть источник, которым вы можете поделиться?   -  person David Johnstone    schedule 18.06.2009
comment
вам это нужно только от a-zz или нескольких неопределенных уровней?   -  person AlexDrenea    schedule 18.06.2009
comment
@subprime обновил мой ответ, чтобы показать вам, как именно это сделать. вы можете использовать его как последовательность или использовать предоставленную функцию, чтобы делать именно то, что вы заявляете.   -  person TheSoftwareJedi    schedule 18.06.2009


Ответы (10)


Редактировать: сделано именно так, как того требует последняя редакция OP

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

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;
}
person TheSoftwareJedi    schedule 18.06.2009
comment
Лучшее решение, как и то, которое должно быть помечено как ответ. - person Wolf5; 18.06.2009
comment
Теперь, когда он работает, нет причин его отрицать. (Я не понизил голос, даже когда он был немного сломан.) Лично я бы отказался от части «до» - отделите проблемы построения бесконечной последовательности от проблем ее усечения - LINQ может справиться с последним битом. Но разнообразие это хорошо :) - person Jon Skeet; 18.06.2009
comment
@ Джон, я согласен. Я просто последовал его примеру, потому что ему это, похоже, понравилось. Теперь его последняя редакция начинает решать настоящую проблему. Еще не совсем там... Я думаю, он пытается найти первый неиспользованный ключ из какого-то Сета, используя эту последовательность. Но я не могу сказать. - person TheSoftwareJedi; 18.06.2009
comment
извините TheSoftwareJedi за мое описание, но это ответ :) Всем остальным спасибо! - person subprime; 18.06.2009
comment
@TSJ: Учитывая довольно простое представление, которое использует ваш итератор (например, длинное), должно быть довольно легко преобразовать обратно из base26 в длинное, а затем создать итератор, который начинается в нужном месте. - person Jon Skeet; 18.06.2009
comment
@Jon Skeet: Почему всякий раз, когда мы оба прыгаем на ТАК вопрос, происходит королевская битва? Ты просто расстроен тем, что я джедай? Я могу встретиться с адвокатом и узнать, проведут ли они специальное заседание, чтобы назначить вас на должность. ржу не могу... - person TheSoftwareJedi; 18.06.2009

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

person Jon Skeet    schedule 18.06.2009
comment
Блин, у меня была такая же идея, но слишком поздно :) - person Idan K; 18.06.2009
comment
хорошо спасибо работает! я должен перепрограммировать для проверки следующего символа, и если он не указан, он должен начинаться с a. большое спасибо :) - person subprime; 18.06.2009
comment
NB. Это предполагает ASCII (или надмножество ASCII, которое включает Unicode). Этот подход не будет работать в EBCDIC и других подобных кодировках (буквы не идут подряд). Однако, поскольку внутри .NET используется Unicode, эта конкретная реализация подходит. - person Richard; 18.06.2009
comment
Решение TheSoftwareJedi намного лучше и ОЧЕНЬ чистое. Это общий шаблон для использования. Возможно, поэтому вас заминусовали. - person Wolf5; 18.06.2009
comment
@Richard: Каким образом это предполагается? Вы даете ему любой алфавит, какой хотите. Вы можете дать ему agx, если хотите, и он даст a, g, x, aa, ag, ax, ga и т. д. - person Jon Skeet; 18.06.2009
comment
@Wolf5: В какой-то степени я согласен. С другой стороны, мое решение может давать результаты навсегда — оно не связано с Int64. Это также более эффективно при создании строк :) - person Jon Skeet; 18.06.2009
comment
(Он также жестко кодирует используемый алфавит... хотя это можно достаточно легко исправить.) - person Jon Skeet; 18.06.2009
comment
@Jon: можно найти только рядом. Образец: у меня есть agh, а следующий - agi ? и если ничего в префиксе, то вернуть? - person subprime; 18.06.2009
comment
@unknown: Это должно быть возможно, но это будет немного другое решение. По сути, вам нужно будет перевести текущее значение (agh) в составные индексы (0, 6, 7), а затем соответствующим образом обновить эти индексы. Несколько сумбурно. Я могу посмотреть на него, если хочешь, но это будет не очень приятно. - person Jon Skeet; 18.06.2009
comment
проблема у меня в том, что я создаю шаблон. Первая часть шаблона получает символ a, но если часть с функциями не существует, я должен создать новый символ b. Чтобы удерживать это динамически, мне нужна эта функция. Вы понимаете, что я имею в виду? - person subprime; 18.06.2009
comment
Просто сохраните строку IEnumerator и вызовите MoveNext вручную. - person Jon Skeet; 18.06.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());
    }
}
person Patrick McDonald    schedule 18.06.2009
comment
Мне нравится, что наши ответы так похожи - они просто отличаются тем, строят ли они список или лениво уступают :) - person Jon Skeet; 18.06.2009
comment
Я родом из VB, поэтому обычно никогда не запоминаю доходность, в этом случае я сделал это сразу после публикации, но к тому времени, когда я попробовал, вы уже опубликовали решение доходности :) - person Patrick McDonald; 18.06.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, эти функции не проверяют свои входные данные. Оба дают вам мусор, если это то, что они получают.

person P Daddy    schedule 18.06.2009

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

/// <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();
}
person Julian    schedule 21.01.2011

просто интересно, а почему бы и нет

    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];
         }
    }
person vittore    schedule 24.03.2010
comment
Когда c = 26, это возвращает a. Вы можете изменить if/else следующим образом: } еще {возврат + алфавит[c]; } - person Brent Barbata; 30.05.2013
comment
@BrentBarbata да, это должно было быть if (c >= alphabet.Length) {... - person vittore; 30.05.2013
comment
Тем не менее отличное решение. Я просто использовал его, чтобы связать буквенные булавки на карте Google со строками таблицы, подсчитываемыми в числовом виде. Спасибо! - person Brent Barbata; 30.05.2013

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

person Martijn    schedule 18.06.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;
        }
    }
}
person Charlie    schedule 18.06.2009
comment
программа только от a до zz. нет ответа извините :( - person subprime; 18.06.2009

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

Я знал, что каждый раз, когда я добавляю альфа-символ, это увеличивает возможности в 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;  
    }
person da_jokker    schedule 03.08.2017

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

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

person dariom    schedule 18.06.2009