Получить имена столбцов в стиле Excel из номера столбца

Это код для предоставления имени COLUMN, когда указывается идентификатор строки и столбца, но когда я даю такие значения, как row = 1 and col = 104должно вернуться CZ, но это возвращает D@

row = 1
col = 104
div = col
column_label = str()
while div:
    (div, mod) = divmod(div, 26)
    column_label = chr(mod + 64) + column_label

print column_label

Что не так с тем, что я делаю?

(Этот код относится к столбцам EXCEL, где я предоставляю значение Row,Column ID и ожидаю, что значение ALPHABETIC будет тем же.)

11 ответов

Решение

РЕДАКТИРОВАТЬ: я чувствую, что должен признать, как указали некоторые другие - кто никогда не оставлял мне комментарии - что предыдущая версия моего ответа (которую вы приняли) содержала ошибку, которая не позволяла ему правильно обрабатывать номера столбцов больше, чем 702 (соответствует столбцу Excel 'ZZ'). Итак, в интересах правильности это было исправлено в приведенном ниже коде, который теперь содержит цикл, как и многие другие ответы.

Вполне вероятно, что вы никогда не использовали предыдущую версию с достаточно большими номерами столбцов, чтобы столкнуться с проблемой. FWIW, спецификации MS для текущей версии Excel говорят, что он поддерживает рабочие листы до 16 384 столбцов (столбец Excel 'XFD').

LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

def excel_style(row, col):
    """ Convert given row and column number to an Excel-style cell name. """
    result = []
    while col:
        col, rem = divmod(col-1, 26)
        result[:0] = LETTERS[rem]
    return ''.join(result) + str(row)

if __name__ == '__main__':
    addresses = [(1,  1), (1, 26),
                 (1, 27), (1, 52),
                 (1, 53), (1, 78),
                 (1, 79), (1, 104),
                 (1, 18253), (1, 18278),
                 (1, 702),  # -> 'ZZ1'
                 (1, 703),  # -> 'AAA1'
                 (1, 16384), # -> 'XFD1'
                 (1, 35277039)]

    print('({:3}, {:>10}) --> {}'.format('row', 'col', 'Excel'))
    print('==========================')
    for row, col in addresses:
        print('({:3}, {:10,}) --> {!r}'.format(row, col, excel_style(row, col)))

Выход:

(row,       col) --> Excel
========================
(  1,         1) --> 'A1'
(  1,        26) --> 'Z1'
(  1,        27) --> 'AA1'
(  1,        52) --> 'AZ1'
(  1,        53) --> 'BA1'
(  1,        78) --> 'BZ1'
(  1,        79) --> 'CA1'
(  1,       104) --> 'CZ1'
(  1,     18253) --> 'ZZA1'
(  1,     18278) --> 'ZZZ1'
(  1,       702) --> 'ZZ1'
(  1,       703) --> 'AAA1'
(  1,     16384) --> 'XFD1'
(  1,  35277039) --> 'BYEBYE1'

У вас есть пара проблем с индексами:

Итак, чтобы решить вашу проблему, вам нужно, чтобы все ваши индексы совпадали:

def colToExcel(col): # col is 1 based
    excelCol = str()
    div = col 
    while div:
        (div, mod) = divmod(div-1, 26) # will return (x, 0 .. 25)
        excelCol = chr(mod + 65) + excelCol

    return excelCol

print colToExcel(1) # => A
print colToExcel(26) # => Z
print colToExcel(27) # => AA
print colToExcel(104) # => CZ
print colToExcel(26**3+26**2+26) # => ZZZ

Мне нравится ответ Маритино, поскольку его код выглядит простым и понятным. Но он не может обработать номер столбца, который больше, чем 26**2 + 26. Поэтому я модифицирую его часть.

def excel_col(col):
    """Covert 1-relative column number to excel-style column label."""
    quot, rem = divmod(col-1,26)
    return excel_col(quot) + chr(rem+ord('A')) if col!=0 else ''



if __name__=='__main__':
    for i in [1, 26, 27, 26**3+26**2+26]:
        print 'excel_col({0}) -> {1}'.format(i, excel_col(i))

Результаты

excel_col(1) -> A
excel_col(26) -> Z
excel_col(27) -> AA
excel_col(18278) -> ZZZ
def ColNum2ColName(n):
   convertString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
   base = 26
   i = n - 1

   if i < base:
      return convertString[i]
   else:
      return ColNum2ColName(i//base) + convertString[i%base]

РЕДАКТИРОВАТЬ: Верно, верно, Зондо.

Я только что подошел A, B, .. AA, AB, ... в качестве числовой базы с цифрами A-Z,

A = 1
B = 2
 .
 .
X = 24
Y = 25
Z = 26
 .
 .
 .

Это простой способ без всякого цикла и т. Д., Который работает для любого числа > 0,

Я думаю, что-то вроде этого:

def get_col(col):
    """Get excel-style column names"""
    (div, mod) = divmod(col, 26)
    if div == 0:
        return str(unichr(mod+64))
    elif mod == 0:
        return str(unichr(div+64-1)+'Z')
    else:
        return str(unichr(div+64)+unichr(mod+64))

Некоторые тесты:

>>> def get_col(col):
...     (div, mod) = divmod(col, 26)
...     if div == 0:
...         return str(unichr(mod+64))
...     elif mod == 0:
...         return str(unichr(div+64-1)+'Z')
...     else:
...         return str(unichr(div+64)+unichr(mod+64))
... 
>>> get_col(105)
'DA'
>>> get_col(104)
'CZ'
>>> get_col(1)
'A'
>>> get_col(55)
'BC'

Другим решением для получения столбца в стиле Excel является использование существующей библиотеки, например, xlsxwriter имеет для этого вспомогательную функцию:

      from xlsxwriter.utility import xl_col_to_name

xl_col_to_name(0)  # A
xl_col_to_name(25)  # Z
xl_col_to_name(26)  # AA
xl_col_to_name(18277)  # ZZZ

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

Вот ответ, который я ожидал, используя itertools.

      import string
import itertools

def gen_labels(seq=string.ascii_uppercase):
   for n in itertools.count(1):
     for p in itertools.product(seq, repeat=n):
       yield("".join(p))

Вы можете использовать короткую последовательность ввода и увидеть результаты.

      >>> labels = gen_labels("ABC")
>>> [next(labels) for i in range(26)]
['A', 'B', 'C', 'AA', 'AB', 'AC', 'BA', 'BB', 'BC', 'CA', 'CB', 'CC', 
'AAA', 'AAB', 'AAC', 'ABA', 'ABB', 'ABC', 'ACA', 'ACB', 'ACC', 'BAA', 
'BAB', 'BAC', 'BBA', 'BBB']

Я думаю, что я понял это. divmod (104,26) дает mod=0, что делает chr(0+64) = 64, т. е. "@".

если я добавлю эту строку перед column_label "mod=26 if mod==0 else mod" я думаю, что это должно работать нормально

column_label=''
div=104
while div:
    (div, mod) = divmod(div, 26)
    mod=26 if mod==0 else mod
    column_label = chr(mod + 64) + column_label

print column_label

Используйте этот код:

def xlscol(colnum):
    a = []
    while colnum:
        colnum, remainder = divmod(colnum - 1, 26)
        a.append(remainder)
    a.reverse()
    return ''.join([chr(n + ord('A')) for n in a])

Все текущие ответы используют старые соглашения Python. Вот ответ, использующий обработку списка Python 3.6+ и аннотации типов.

      import string

def excel_style(col: int) -> str:
    """Convert given row and column number to an Excel-style cell name."""
    result = []
    while col:
        col, rem = divmod(col - 1, 26)
        result.append(string.ascii_uppercase[rem])
    result.reverse()

    return "".join(result)

Вот еще один способ получить имена столбцов Excel без цикла, используя мультииндексный фрейм данных pandas. Это модификация обобщенного преобразователя базы в базу, поэтому код немного длиннее, чем некоторые другие варианты, но я думаю, что он эффективен:

def xlcolumn(num):
    base = ["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"]
    baseln = len(base)
    idx = [""]+base
    num = num-1

    # create pandas multiindex using idx, base
    # current excel version has 16384 columns (A --> XFD), so multiindex needs to have a minimum of 3 levels:
    #   (26x26x26 = 17576 > 16384 columns) 

    index = pd.MultiIndex.from_product([idx, idx, idx],names=['level 1', 'level 2', 'level 3'])
    df = pd.DataFrame(index = index)
    df = df.drop("",level = 'level 3')
    df = df.iloc[:baseln].append(df.drop("",level = 'level 2'))
    df['val']=1

    if num < baseln:
        xlcol = str(df.iloc[num].name[2])
    elif num >= baseln and num < baseln**2:
        xlcol = str(df.iloc[num].name[1])+str(df.iloc[num].name[2])
    else:
        xlcol = str(df.iloc[num].name[0])+str(df.iloc[num].name[1])+str(df.iloc[num].name[2])

    return xlcol

Другие вопросы по тегам