Pergunta

Eu tenho uma pergunta sobre iterate através do alfabeto. Eu gostaria de ter um loop que começa com "A" e termina com "z". Depois disso, o ciclo começa "aa" e contar até "az". depois que começa com "ba" até "BZ" e assim por diante ...

Alguém sabe alguma solução?

Agradecimentos

EDIT: eu esqueci que eu dou um char "a" para a função, em seguida, a função deve retornar b. se u dar "BNC", em seguida, a função deve retornar "BND"

Foi útil?

Solução

Editar: Feito isso não exatamente como mais recente edição do OP quer

Esta é a solução mais simples, e testado:

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

Outras dicas

Primeiro esforço, com apenas um-z, em seguida, 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);
        }
    }
}

Note que esta vai parar em 'zz'. Naturalmente, há alguma duplicação feio aqui em termos de loops. Felizmente, isso é fácil de resolver - e que pode ser ainda mais flexível, também:

Segunda tentativa: Alfabeto mais flexível

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

Agora, se você deseja gerar apenas um, b, c, d, aa, ab, ac, ad, ba ... você chamaria GetExcelColumns("abcd").

terceira tentativa (revista ainda) - sequência infinita

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

É possível que seria um código mais limpo usando recursão, mas não seria tão eficiente.

Note que, se você quiser parar em um certo ponto, você pode apenas usar LINQ:

var query = GetExcelColumns().TakeWhile(x => x != "zzz");

"Reiniciar" o iterador

Para reiniciar o iterador a partir de um determinado ponto, você poderia realmente usar SkipWhile como sugerido por TheSoftwareJedi. Isso é bastante ineficiente, é claro. Se você é capaz de manter qualquer estado entre chamada, você pode apenas manter o iterador (para qualquer solução):

using (IEnumerator<string> iterator = GetExcelColumns())
{
    iterator.MoveNext();
    string firstAttempt = iterator.Current;

    if (someCondition)
    {
        iterator.MoveNext();
        string secondAttempt = iterator.Current;
        // etc
    }
}

Como alternativa, você pode muito bem ser capaz de estruturar o seu código para usar um foreach qualquer maneira, só sair no primeiro valor que você pode realmente usar.

A seguir preenche uma lista com as cordas necessárias:

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

Eu sei que há uma abundância de respostas aqui, e um foi aceito, mas IMO todos eles tornam mais difícil do que precisa ser. Eu acho que o seguinte é mais simples e mais limpa:

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

Note que este não faz qualquer validação de entrada. Se você não confia em seus chamadores, você deve adicionar uma verificação IsNullOrEmpty no início, e um cheque c[i] >= 'A' && c[i] <= 'Z' || c[i] >= 'a' && c[i] <= 'z' no topo do loop. Ou apenas deixá-lo ser e deixá-lo ser GIGO .

Você também pode encontrar uso para essas funções complementares:

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

Estas duas funções são baseadas em zero. Ou seja, "A" = 0, "Z" = 25, "AA" = 26, etc. Para torná-los uma base (como o Excel COM interface), remova a linha acima da linha comentada em cada função, e uncomment as linhas.

Tal como acontece com a função NextColumn, essas funções não validar suas entradas. Ambos com dar-lhe lixo se é isso que eles recebem.

Aqui está o que eu vim acima com.

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

apenas curioso, porque não basta

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

Isto é como exibir um int, usando apenas base 26 em vez de base 10. Tente o seguinte algoritmo para encontrar a entrada nth da matriz

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

É claro, se você quiser as primeiras entradas n, esta não é a solução mais eficiente. Neste caso, tente algo como solução de Daniel.

Eu dei este um ir e vim com essa:

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

Aqui está algo que eu tinha inventado que podem ser semelhantes. Eu estava experimentando com iteração contagem, a fim de criar um esquema de numeração que era tão pequeno quanto possível, ainda me deu singularidade suficiente.

Eu sabia que cada vez que um adicionado um caráter Alpha, que aumentaria as possibilidades 26x, mas eu não tinha certeza de quantas letras, números ou o padrão que eu queria usar.

Que levam-me para o código abaixo. Basicamente você passar uma string AlphaNumber, e todas as posições que tem uma letra, acabaria por aumentar a "z \ Z" e todas as posições que tinha um número, acabaria por aumentar a "9".

Assim, você pode chamá-lo de uma das duas maneiras ..

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

(Para mim, eu estava fazendo algo como "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;  
    }

Aqui está minha tentativa usando recursão:

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

Em seguida, basta chamar PrintAlphabet("abcd", "");

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top