web-dev-qa-db-fra.com

Fonction de copie rapide Excel en C #

Quelle est la fonction c # la plus rapide qui prend et int et renvoie une chaîne contenant une lettre ou des lettres à utiliser dans une fonction Excel? Par exemple, 1 renvoie "A", 26 renvoie "Z", 27 renvoie "AA", etc.

Cette opération s'appelle des dizaines de milliers de fois et prend 25% du temps nécessaire pour générer un grand tableur contenant de nombreuses formules. 

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

Je l'utilise actuellement avec 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;
        }

et 

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

Comme mentionné dans d'autres articles, les résultats peuvent être mis en cache.

48
Adriaan Stander

Je peux vous dire que la fonction la plus rapide ne sera pas la plus jolie. C'est ici:

private string[] map = new string[]
    { 
        "A", "B", "C", "D", "E" .............
    };

public string getColumn(int number)
{
    return map[number];
}
19
womp

Ne le convertis pas du tout. Excel peut fonctionner aussi bien en notation R1C1 qu'en notation A1.

Donc (excuses pour utiliser VBA plutôt que C #):

Application.Worksheets("Sheet1").Range("B1").Font.Bold = True

peut tout aussi bien s’écrire:

Application.Worksheets("Sheet1").Cells(1, 2).Font.Bold = True

La propriété Range prend la notation A1 alors que la propriété Cells prend (numéro de ligne, numéro de colonne).

Pour sélectionner plusieurs cellules: Range(Cells(1, 1), Cells(4, 6)) (NB aurait besoin d'un qualificatif d'objet s'il n'utilisait pas la feuille de calcul active) plutôt que Range("A1:F4")

La propriété Columns peut prendre une lettre (par exemple F) ou un nombre (par exemple 6)

13
barrowc

Voici ma version: Il n'y a pas de limitation en tant que telle de 2 lettres ou de 3 lettres. Entrez simplement le nombre requis (en commençant à 0) Renverra l'en-tête de colonne Excel comme une séquence alphabétique pour la transmission. nombre:

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

Je n'ai pas testé cela pour la performance, si quelqu'un peut le faire, ce sera formidable pour les autres. (Désolé d'être paresseux) :)

À votre santé!

5
Vijay Wadnere

Vous pouvez pré-générer toutes les valeurs dans un tableau de chaînes. Cela prendrait très peu de mémoire et pourrait être calculé dès le premier appel.

4
JDunkerley

Ceci est écrit en Java, mais c'est fondamentalement la même chose.

Voici le code pour calculer le libellé de la colonne, en majuscule, avec un index basé sur 0:

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

Voici le code pour calculer un index de base 0 pour la colonne à partir de l'étiquette en majuscule:

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

Le plus RAPIDE absolu serait de capitaliser sur le fait que la feuille de calcul Excel ne contient qu'un nombre fixe de colonnes, de sorte que vous feriez une table de correspondance. Déclarez un tableau de chaînes constantes de 256 entrées et pré-remplissez-le avec les chaînes de "A" à "IV". Ensuite, vous effectuez simplement une recherche directe dans l'index.

2
Doug

Essayez cette fonction.

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

Maintenant, il ne faut pas utiliser that beaucoup de mémoire pour pré-générer chaque nom de colonne pour chaque index et les stocker dans un seul grand tableau, vous n'avez donc pas besoin de rechercher le nom d'une colonne deux fois .

Si je peux penser à d'autres optimisations, je les ajouterai plus tard, mais je pense que cette fonction devrait être assez rapide, et je doute que vous ayez besoin de cette vitesse si vous faites la pré-génération.

2
Noldorin

Une fois votre fonction exécutée, laissez-la en cache les résultats dans un dictionnaire. Donc, cela ne nécessitera plus de calcul.

par exemple. Convert (27) vérifiera si 27 est mappé/stocké dans le dictionnaire. Sinon, effectuez le calcul et enregistrez "AA" contre 27 dans le dictionnaire.

2
shahkalpesh

Votre premier problème est que vous déclarez 6 variables dans la méthode. Si un methd doit être appelé des milliers de fois, le simple fait de déplacer ceux-ci dans la portée de classe au lieu de portée de fonction réduira probablement votre temps de traitement de plus de la moitié dès le départ.

2
Neil N

Voici une implémentation concise utilisant 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;
}

Ceci génère A à Z, puis AA à ZZ, puis AAA à ZZZ.

Sur mon PC, appeler GetExcelStrings().ToArray() prend environ 30 ms. Par la suite, vous pouvez vous référer à ce tableau de chaînes si vous en avez besoin des milliers de fois.

1
Matthew Strawbridge

@Neil N - Beau code Je pense que la troisième lettre devrait avoir un +64 plutôt que +65? ai-je raison?

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

Ma 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();
    }
}
0
jaccso
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) ;        
}
0
Ali

désolé il y avait un changement. corrigée.

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



}
0
Bison

La mise en cache réduit réellement le temps d'exécution de 10 000 000 d'appels aléatoires à un tiers de sa valeur, même si:

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

Je pense que la mise en cache dans le pire des cas (frapper toutes les valeurs) ne pourrait pas prendre plus de 250 Ko (17576 valeurs possibles * (sizeof (int) = 4 + sizeof (car)) * 3 + surcharge de chaîne = 2) 

0
foson

l'idée de barrowc est beaucoup plus pratique et rapide que toute fonction de conversion! J'ai converti ses idées en code c # réel que j'utilise:

  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;
0
k3nn

Utilisez simplement une formule Excel à la place d'une fonction définie par l'utilisateur (UDF) ou d'un autre programme, comme le dit Allen Wyatt ( https://Excel.tips.net/T003254_Alphabetic_Column_Designation.html ):

=SUBSTITUTE(ADDRESS(ROW(),COLUMN(),4),ROW(),"")

(Dans mon organisation, utiliser des FDU serait très pénible.)

0
A.C. Wilson

Pourquoi n'essayons-nous pas factorielle?

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] + "";
}
0

Le code que je fournis n'est PAS C # (à la place, c'est python), mais la logique peut être utilisée pour n'importe quel langage.

La plupart des réponses précédentes sont correctes. Voici un autre moyen de convertir le numéro de colonne en colonnes Excel. La solution Est plutôt simple si nous considérons cela comme une conversion de base. Convertissez simplement le numéro de colonne en base 26 car il n’ya que 26 lettres. Voici comment procéder: 

étapes:

  • définir la colonne comme un quotient

  • soustrayez-en une à la variable quotient (de l'étape précédente) car nous devons nous retrouver sur table ascii avec 97 étant a.

  • diviser par 26 et obtenir le reste.

  • ajoutez +97 au reste et convertissez-le en caractère (puisque 97 est "a" dans la table ASCII)
  • quotient devient le nouveau quotient/26 (puisque nous pourrions dépasser 26 colonnes)
  • continuez jusqu'à ce que le quotient soit supérieur à 0, puis renvoyez le résultat

voici le code qui fait ça :)

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())
0
grepit

C'est récursif. Rapide et à droite:

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



}
0
Bison