Algorytm uzyskiwania nazwy kolumny podobnej do programu Excel dla liczby


95

Pracuję nad skryptem, który generuje dokumenty Excela i muszę przekonwertować liczbę na odpowiednik nazwy kolumny. Na przykład:

1 => A
2 => B
27 => AA
28 => AB
14558 => UMX

Napisałem już algorytm, aby to zrobić, ale chciałbym wiedzieć, czy można to zrobić prostszym lub szybszym:

function numberToColumnName($number){
    $abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $abc_len = strlen($abc);

    $result_len = 1; // how much characters the column's name will have
    $pow = 0;
    while( ( $pow += pow($abc_len, $result_len) ) < $number ){
        $result_len++;
    }

    $result = "";
    $next = false;
    // add each character to the result...
    for($i = 1; $i<=$result_len; $i++){
        $index = ($number % $abc_len) - 1; // calculate the module

        // sometimes the index should be decreased by 1
        if( $next || $next = false ){
            $index--;
        }

        // this is the point that will be calculated in the next iteration
        $number = floor($number / strlen($abc));

        // if the index is negative, convert it to positive
        if( $next = ($index < 0) ) {
            $index = $abc_len + $index;
        }

        $result = $abc[$index].$result; // concatenate the letter
    }
    return $result;
}

Czy znasz lepszy sposób na zrobienie tego? Może coś, co będzie prostsze? czy poprawa wydajności?

Edytować

Implementacja ircmaxell działa całkiem dobrze. Ale dodam ten fajny krótki:

function num2alpha($n)
{
    for($r = ""; $n >= 0; $n = intval($n / 26) - 1)
        $r = chr($n%26 + 0x41) . $r;
    return $r;
}

3
pierwszy komentarz na tej stronie podręcznika
Sergey Eremin

Łał! To krótka realizacja. Dziękuję Ci!
Cristian

Czy spojrzałeś na którąś z istniejących bibliotek do generowania dokumentów Excela z PHP?
Mark Baker,

Tak oczywiście. Używam twojej niesamowitej biblioteki, Mark. Po prostu lubię doskonalić swoje umiejętności pisania algorytmów ... fajne w tym jest to, że po skończeniu jednego można znaleźć inne algorytmy, które robią dokładnie to samo, ale są krótsze.
Cristian,

2
Twój krótki algorytm używa 0 -> A zamiast 1 -> A w żądaniu, nieco różniąc się od żądania ... jeśli tego chcesz, spójrz na metodę PHPExcel_Cell :: stringFromColumnIndex (), chociaż twoja implementacja num2alpha () może bądź szybszy ... Przeprowadzę kilka testów i mogę go „pożyczyć” (za zgodą)
Mark Baker,

Odpowiedzi:


155

Oto ładna prosta funkcja rekurencyjna (oparta na zerowych liczbach indeksowanych, co oznacza 0 == A, 1 == B itd.) ...

function getNameFromNumber($num) {
    $numeric = $num % 26;
    $letter = chr(65 + $numeric);
    $num2 = intval($num / 26);
    if ($num2 > 0) {
        return getNameFromNumber($num2 - 1) . $letter;
    } else {
        return $letter;
    }
}

A jeśli chcesz go zindeksować (1 == A, itd.):

function getNameFromNumber($num) {
    $numeric = ($num - 1) % 26;
    $letter = chr(65 + $numeric);
    $num2 = intval(($num - 1) / 26);
    if ($num2 > 0) {
        return getNameFromNumber($num2) . $letter;
    } else {
        return $letter;
    }
}

Testowany z liczbami od 0 do 10000 ...


Dzięki! Zaoszczędzili mi pół godziny!
Darryl Hein,

Przetłumaczyłem ten skrypt PHP na JS: gist.github.com/terox/161db6259e8ddb56dd77
terox

Wątpię, czy funkcja recusive jest dobra, czy nie, jeśli jest używana w pętli? Czy może to spowodować problem z miejscem na stosie?
Scott Chu

92

Korzystanie z PhpSpreadsheet ( PHPExcel jest przestarzałe )

// result = 'A'
\PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex(1);

Indeks notatek 0 wyników w „Z”

https://phpspreadsheet.readthedocs.io/en/develop/


Prawidłowa odpowiedź (jeśli korzystasz z biblioteki PHPExcel ) to:

// result = 'A'
$columnLetter = PHPExcel_Cell::stringFromColumnIndex(0); // ZERO-based! 

i do tyłu:

// result = 1
$colIndex = PHPExcel_Cell::columnIndexFromString('A');

12
stringFromColumnIndex (1) zwraca „B”
Ulterior

1
Doskonały! Tak się składa, że ​​używam PHPExcel. To fajna odpowiedź.
Scott Chu

PHPExcel_Cell::stringFromColumnIndex(1)w istocie zwraca 'B', edytuj swoją odpowiedź.
MarthyM

13

Indeksowane dla 1 -> A, 2 -> B itd

function numToExcelAlpha($n) {
    $r = 'A';
    while ($n-- > 1) {
        $r++;
    }
    return $r;
}

Indeksowane dla 0 -> A, 1 -> B itd

function numToExcelAlpha($n) {
    $r = 'A';
    while ($n-- >= 1) {
        $r++;
    }
    return $r;
}

Wykorzystuje fakt, że PHP postępuje zgodnie z konwencją Perla, gdy ma do czynienia z operacjami arytmetycznymi na zmiennych znakowych, a nie na C. Zwróć uwagę, że zmienne znakowe mogą być zwiększane, ale nie zmniejszane.


jest to rozwiązanie dobre, ale myślę, że będzie limit czasu serwera na dużych liczbach jak 1234567789.
Tahir Raza

5

Spowoduje to konwersję (zakładając arytmetykę liczb całkowitych), ale zgadzam się z innymi plakatami; po prostu użyjbase_convert

function numberToColumnName($number)
{
    $abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $len = strlen($abc);

    $result = "";
    while ($number > 0) {
       $index  = $number % $len;
       $result = $abc[$index] . $result;
       $number = floor($number / $len);
    }

    return $result;
}

Będzie to powrót Bdo $number = 1zamiast Aod 1 % 26Is1
Jalal

5

Późna odpowiedź, ale oto co zrobiłem (dla 1 == A zindeksowane):

function num_to_letters($num, $uppercase = true) {
    $letters = '';
    while ($num > 0) {
        $code = ($num % 26 == 0) ? 26 : $num % 26;
        $letters .= chr($code + 64);
        $num = ($num - $code) / 26;
    }
    return ($uppercase) ? strtoupper(strrev($letters)) : strrev($letters);
}

Następnie, jeśli chcesz dokonać konwersji w inny sposób:

function letters_to_num($letters) {
    $num = 0;
    $arr = array_reverse(str_split($letters));

    for ($i = 0; $i < count($arr); $i++) {
        $num += (ord(strtolower($arr[$i])) - 96) * (pow(26,$i));
    }
    return $num;
}

3

Liczba konwertowana na litery w kolumnach programu Excel:

/**
 * Number convert to Excel column letters
 * 
 * 1 = A
 * 2 = B
 * 3 = C
 * 27 = AA
 * 1234567789 = CYWOQRM
 * 
 * @link https://vector.cool/php-number-convert-to-excel-column-letters-2
 * 
 * @param int  $num       欄數
 * @param bool $uppercase 大小寫
 * @return void
 */
function num_to_letters($n)
{
    $n -= 1;
    for ($r = ""; $n >= 0; $n = intval($n / 26) - 1)
        $r = chr($n % 26 + 0x41) . $r;
    return $r;
}

dawny:

echo num_to_letters(1);          // A
echo num_to_letters(2);          // B
echo num_to_letters(3);          // C
echo num_to_letters(27);         // AA
echo num_to_letters(1234567789); // CYWOQRM

Litery kolumn programu Excel są konwertowane na liczbę:

/**
 * Excel column letters convert to Number
 *
 * A = 1
 * B = 2
 * C = 3
 * AA = 27
 * CYWOQRM = 1234567789
 * 
 * @link https://vector.cool/php-number-convert-to-excel-column-letters-2
 * 
 * @param string $letters
 * @return mixed
 */
function letters_to_num($a)
{
    $l = strlen($a);
    $n = 0;
    for ($i = 0; $i < $l; $i++)
        $n = $n * 26 + ord($a[$i]) - 0x40;
    return $n;
}

dawny:

echo letters_to_num('A');       // 1
echo letters_to_num('B');       // 2
echo letters_to_num('C');       // 3
echo letters_to_num('AA');      // 27
echo letters_to_num('CYWOQRM'); // 1234567789

2
<?php
function numberToColumnName($number){
    $abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $abc_len = strlen($abc);

    $result = "";
    $tmp = $number;

    while($number > $abc_len) {
        $remainder = $number % $abc_len;
        $result = $abc[$remainder-1].$result;
        $number = floor($number / $abc_len);
    }
    return $abc[$number-1].$result;
}

echo numberToColumnName(1)."\n";
echo numberToColumnName(25)."\n";
echo numberToColumnName(26)."\n";
echo numberToColumnName(27)."\n";
echo numberToColumnName(28)."\n";
echo numberToColumnName(14558)."\n";
?>

Niezła próba! To się jednak nie uda ... spróbuj z 2049, a zobaczysz: D
Cristian

2

Łącząc rekurencyjną odpowiedź ircmaxell, mam to:

    function getNameFromNumber ($ num, $ index = 0) {
        $ indeks = abs ($ indeks * 1); // upewnij się, że indeks jest dodatnią liczbą całkowitą
        $ numeric = ($ num - $ indeks)% 26; 
        $ litera = chr (65 + $ numeryczna);
        $ num2 = intval (($ num - $ index) / 26);
        if ($ num2> 0) {
            return getNameFromNumber ($ num2 - 1 + $ index). $ list;
        } else {
            return $ letter;
        }
    }

Używam domyślnego indeksowania opartego na 0, ale może to być dowolna dodatnia liczba całkowita do żonglowania tablicami w PHP.


2

Nigdy nie użyłbym tego w produkcji, ponieważ jest nieczytelny, ale dla zabawy ... Tylko do ZZ.

<?php
    $col = 55;
    print (($n = (int)(($col - 1) / 26)) ? chr($n + 64) : '') . chr((($col - 1) % 26) + 65);
?>

0

Dla każdego, kto szuka implementacji tego w JavaScript , oto odpowiedź @ ircmaxell w Javascript ..

function getNameFromNumber(num){
    let numeric = num%26;
    let letter = String.fromCharCode(65+numeric);
    let num2 = parseInt(num/26);
    if(num2 > 0) {
      return getNameFromNumber(num2 - 1)+letter;
    } else {
      return letter;
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.