Programmazione Riddle: come potresti tradurre un nome di colonna di Excel in un numero?

Recentemente mi è stato chiesto in un colloquio di lavoro per risolvere un puzzle di programmazione che pensavo sarebbe stato interessante condividere. Si tratta di tradurre le lettere delle colonne di Excel in numeri reali, se si richiama, Excel nomina le sue colonne con lettere dalla A alla Z, e quindi la sequenza va AA, AB, AC … AZ, BA, BB, ecc.

Devi scrivere una funzione che accetta una stringa come parametro (come “AABCCE”) e restituisce il numero effettivo della colonna.

La soluzione può essere in qualsiasi lingua.

Ho scritto questa età fa per alcuni script Python:

 def index_to_int(index): s = 0 pow = 1 for letter in index[::-1]: d = int(letter,36) - 9 s += pow * d pow *= 26 # excel starts column numeration from 1 return s 

Mi sembra uno standard riduttivo:

Pitone:

 def excel2num(x): return reduce(lambda s,a:s*26+ord(a)-ord('A')+1, x, 0) 

C #:

 int ExcelToNumber(string x) { return x.Aggregate(0, (s, c) => s * 26 + c - 'A' + 1 ); } 

Leggere il nome di una colonna da STDIN e stampare il numero corrispondente:

 perl -le '$x = $x * 26 - 64 + ord for <> =~ /./g; print $x' 

Avvertenze: assume ASCII.

EDIT: sostituito " con ' modo che la shell non interpola $x nella stringa.

Per coincidenza ho risolto lo stesso problema usando javascript

 $(function() { //shorthand document.ready function var getNumber = function(x) { var result = 0; var multiplier = 1; for ( var i = x.length-1; i >= 0; i--) { var value = ((x[i].charCodeAt(0) - "A".charCodeAt(0)) + 1); result = result + value * multiplier; multiplier = multiplier * 26; } return result; }; $('#form').on('submit', function(e) { //use on if jQuery 1.7+ e.preventDefault(); //prevent form from submitting var data = $("#number").val(); $('#answer').text(getNumber(data)); }); }); 
  

Hah – scritto già nella nostra base di codice – circa 3 diverse volte 🙁

 %% @doc Convert an string to a decimal integer %% @spec b26_to_i(string()) -> integer() b26_to_i(List) when is_list(List) -> b26_to_i(string:to_lower(lists:reverse(List)),0,0). %% private functions b26_to_i([], _Power, Value) -> Value; b26_to_i([H|T],Power,Value)-> NewValue = case (H > 96) andalso (H < 123) of true -> round((H - 96) * math:pow(26, Power)); _ -> exit([H | T] ++ " is not a valid base 26 number") end, b26_to_i(T, Power + 1, NewValue + Value). 

L’indovinello è che non è in realtà una rappresentazione Base26 di un numero (stiamo mentendo a noi stessi nel nostro nome di funzione qui) perché non ci sono 0 in esso.

La sequenza è: A, B, C … Z, AA, AB, AC

e non: A, B, C … Z, BA, BB, BC

(la lingua è Erlang, più oui).

Puoi farlo in C in questo modo:

 unsigned int coltonum(char * string) { unsigned result = 0; char ch; while(ch = *string++) result = result * 26 + ch - 'A' + 1; return result; } 

Nessun controllo degli errori, funziona solo con le stringhe maiuscole, la stringa deve essere terminata con null.

Ottieni il numero della colonna dal suo nome

Giava:

 public int getColNum (String colName) { //remove any whitespace colName = colName.trim(); StringBuffer buff = new StringBuffer(colName); //string to lower case, reverse then place in char array char chars[] = buff.reverse().toString().toLowerCase().toCharArray(); int retVal=0, multiplier=0; for(int i = 0; i < chars.length;i++){ //retrieve ascii value of character, subtract 96 so number corresponds to place in alphabet. ascii 'a' = 97 multiplier = (int)chars[i]-96; //mult the number by 26^(position in array) retVal += multiplier * Math.pow(26, i); } return retVal; } 

Supponendo la colonna A = 1

 int GetColumnNumber(string columnName) { int sum = 0; int exponent = 0; for(int i = columnName.Length - 1; i>=0; i--) { sum += (columnName[i] - 'A' + 1) * (GetPower(26, exponent)); exponent++; } return sum; } int GetPower(int number, int exponent) { int power = 1; for(int i=0; i 

Ottieni un nome di colonna da un int in Java ( leggi di più qui ):

 public String getColName (int colNum) { String res = ""; int quot = colNum; int rem; /*1. Subtract one from number. *2. Save the mod 26 value. *3. Divide the number by 26, save result. *4. Convert the remainder to a letter. *5. Repeat until the number is zero. *6. Return that bitch... */ while(quot > 0) { quot = quot - 1; rem = quot % 26; quot = quot / 26; //cast to a char and add to the beginning of the string //add 97 to convert to the correct ascii number res = (char)(rem+97) + res; } return res; } 

Avvertenza: entrambe le versioni assumono solo lettere maiuscole dalla A alla Z. Qualsiasi altra cosa causa un errore di calcolo. Non sarebbe difficile aggiungere un po ‘di controllo degli errori e / o uppercasing per migliorarli.

Scala

 def excel2Number(excel : String) : Int = (0 /: excel) ((accum, ch) => accum * 26 + ch - 'A' + 1) 

Haskell

 excel2Number :: String -> Int excel2Number = flip foldl 0 $ \accum ch -> accum * 26 + fromEnum ch - fromEnum 'A' + 1 

Un altro Delphi:

 function ExcelColumnNumberToLetter(col: Integer): string; begin if (col <= 26) then begin Result := Chr(col + 64); end else begin col := col-1; Result := ExcelColumnNumberToLetter(col div 26) + ExcelColumnNumberToLetter((col mod 26) + 1); end; end; 

Un altro Java:

 public static int convertNameToIndex(String columnName) { int index = 0; char[] name = columnName.toUpperCase().toCharArray(); for(int i = 0; i < name.length; i++) { index *= 26; index += name[i] - 'A' + 1; } return index; } 

Soluzione Java semplice ->

 public class ColumnName { public static int colIndex(String col) { int index=0; int mul=0; for(int i=col.length()-1;i>=0;i--) { index += (col.charAt(i)-64) * Math.pow(26, mul); mul++; } return index; } public static void main(String[] args) { System.out.println(colIndex("AAA")); } 

Aiuta a pensare alla stringa come al contrario del numero di colonna nella base 26 con cifre rappresentate da A, B, … Z?

Questo è fondamentalmente un numero in base 26, con la differenza che il numero non usa 0-9 e quindi lettere ma solo lettere.

Ecco un CFML:

           #ColToNum('AABCCE')#  

E poiché sono di umore strano, ecco una versione di CFScript:

 function ColToNum ( Input ) { var Total = 0; for ( var Pos = 1 ; Pos <= Len(Arguments.Input) ; Pos++ ) { Total += 26^(Pos-1) * ( Asc( UCase( Mid(Arguments.Input,Pos,1) ) ) - 64 ); } return Total; } WriteOutput( ColToNum('AABCCE') ); 

un altro esempio di erlang [più criptico]:

 col2int(String) -> col2int(0,String). col2int(X,[A|L]) when A >= 65, A =< 90 -> col2int(26 * X + A - 65 + 1, L); col2int(X,[]) -> X. 

e funzione inversa:

 int2col(Y) when Y > 0 -> int2col(Y,[]). int2col(0,L) -> L; int2col(Y,L) when Y rem 26 == 0 -> int2col(Y div 26 - 1,[(26+65-1)|L]); int2col(Y,L) -> P = Y rem 26, int2col((Y - P) div 26,[P + 65-1|L]). 

Delphi:

 // convert EXcel column name to column number 1..256 // case-sensitive; returns 0 for illegal column name function cmColmAlfaToNumb( const qSRC : string ) : integer; var II : integer; begin result := 0; for II := 1 to length(qSRC) do begin if (qSRC[II]<'A')or(qSRC[II]>'Z') then begin result := 0; exit; end; result := result*26+ord(qSRC[II])-ord('A')+1; end; if result>256 then result := 0; end; 

-Al.

Leggermente correlati, la sfida migliore è il contrario: dato il numero della colonna, trova l’etichetta della colonna come stringa.

Versione di Qt come quella che ho implementato per KOffice:

 QString columnLabel( unsigned column ) { QString str; unsigned digits = 1; unsigned offset = 0; column--; for( unsigned limit = 26; column >= limit+offset; limit *= 26, digits++ ) offset += limit; for( unsigned c = column - offset; digits; --digits, c/=26 ) str.prepend( QChar( 'A' + (c%26) ) ); return str; } 

Lisp comune:

 (defun excel->number (string) "Converts an Excel column name to a column number." (reduce (lambda (ab) (+ (* a 26) b)) string :key (lambda (x) (- (char-int x) 64)))) 

modifica: l’operazione inversa:

 (defun number->excel (number &optional acc) "Converts a column number to Excel column name." (if (zerop number) (concatenate 'string acc) (multiple-value-bind (rest current) (floor number 26) (if (zerop current) (number->excel (- rest 1) (cons #\Z acc)) (number->excel rest (cons (code-char (+ current 64)) acc)))))) 

Questa versione è puramente funzionale e consente sequenze di “codice” alternative, ad esempio se si desidera utilizzare solo le lettere da “A” a “C”. In Scala, con un suggerimento di dcsobral.

 def columnNumber(name: String) = { val code = 'A' to 'Z' name.foldLeft(0) { (sum, letter) => (sum * code.length) + (code.indexOf(letter) + 1) } } 
 def ExcelColumnToNumber(ColumnName): ColNum = 0 for i in range(0, len(ColumnName)): # Easier once formula determined: 'PositionValue * Base^Position' # ie AA=(1*26^1)+(1*26^0) or 792=(7*10^2)+(9*10^1)+(2*10^0) ColNum += (int(ColumnName[i],36) -9) * (pow(26, len(ColumnName)-i-1)) return ColNum 

ps Il mio primo script Python!

In Mathematica:

 FromDigits[[email protected]# - 64, 26] & 

Usando il fantastico codice Mathematica di Mr. Wizard, ma liberandosi della pura funzione criptica!

 columnNumber[name_String] := FromDigits[ToCharacterCode[name] - 64, 26] 

Wikipedia ha buone spiegazioni e algos

http://en.wikipedia.org/wiki/Hexavigesimal

 public static String toBase26(int value){ // Note: This is a slightly modified version of the Alphabet-only conversion algorithm value = Math.abs(value); String converted = ""; boolean iteration = false; // Repeatedly divide the number by 26 and convert the // remainder into the appropriate letter. do { int remainder = value % 26; // Compensate for the last letter of the series being corrected on 2 or more iterations. if (iteration && value < 25) { remainder--; } converted = (char)(remainder + 'A') + converted; value = (value - remainder) / 26; iteration = true; } while (value > 0); return converted; } 

… avevo solo bisogno di una soluzione per PHP . Questo è quello che mi è venuto in mente:

 /** * Calculates the column number for a given column name. * * @param string $columnName the column name: "A", "B", …, "Y", "Z", "AA", "AB" … "AZ", "BA", … "ZZ", "AAA", … * * @return int the column number for the given column name: 1 for "A", 2 for "B", …, 25 for "Y", 26 for "Z", 27 for "AA", … 52 for "AZ", 53 for "BA", … 703 for "AAA", … */ function getColumnNumber($columnName){ // the function's result $columnNumber = 0; // at first we need to lower-case the string because we calculate with the ASCII value of (lower-case) "a" $columnName = strtolower($columnName); // ASCII value of letter "a" $aAsciiValue = ord('a') - 1; // iterate all characters by splitting the column name foreach (str_split($columnName) as $character) { // determine ASCII value of current character and substract with that one from letter "a" $characterNumberValue = ord($character) - $aAsciiValue; // through iteration and multiplying we finally get the previous letters' values on base 26 // then we just add the current character's number value $columnNumber = $columnNumber * 26 + $characterNumberValue; } // return the result return $columnNumber; } 

Ovviamente lo script può essere accorciato un po ‘semplicemente combinando alcune cose in una riga di codice all’interno del ciclo foreach:

 // … $columnNumber = $columnNumber * 26 + ord($character) - ord('a') + 1; // … 

In Python, senza ridurre:

 def transform(column_string): return sum((ascii_uppercase.index(letter)+1) * 26**position for position, letter in enumerate(column_string[::-1])) 

Ecco un’altra versione di questo codice in Python:

 keycode=1 for i in range (1,len(word)): numtest[i]=word[i-1] keycode = keycode*26*int(wordtest[numtest[i]]) last=word[-1:] keycode=keycode+int(wordtest[last]) print(keycode) print(bin(keycode)) #Numtest and wordtest are dictionaries.