J'ai récemment été demandé dans un entretien d'embauche de résoudre un puzzle de programmation que je pensais qu'il serait intéressant de partager. Il s'agit de traduire des lettres de colonne Excel aux nombres réels, si vous vous rappelez, Excel nomme ses colonnes avec des lettres de A à Z, puis la séquence va aa, AB, AC ... AZ, BA, BB, etc.
Vous devez écrire une fonction qui accepte une chaîne en tant que paramètre (comme "AABCCE") et renvoie le numéro de colonne réel.
La solution peut être dans n'importe quelle langue.
J'ai écrit cela il y a un peu Python script:
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
Lisez un nom de colonne de STDIN et imprimez son numéro correspondant:
Perl -le '$x = $x * 26 - 64 + ord for <> =~ /./g; print $x'
CAVEZIERS: suppose ASCII.
Edit : Remplacé "
avec '
Pour que votre coquille ne interpolait pas $x
dans la chaîne.
HAH - écrit déjà dans notre base de code - environ 3 fois différents :(
%% @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'énigme est que ce n'est pas réellement une représentation de base26 d'un nombre (nous nous mentons dans notre nom de la fonction ici) car il n'y a pas de 0.
La séquence est: A, B, C ... Z, AA, AB, AC
et non: a, b, c ... z, ba, bb, bc
(La langue est Erlang, Mais Oui).
Vous pouvez le faire en C comme ceci:
unsigned int coltonum(char * string)
{
unsigned result = 0;
char ch;
while(ch = *string++)
result = result * 26 + ch - 'A' + 1;
return result;
}
Aucune vérification des erreurs, fonctionne uniquement pour les chaînes majuscules, la chaîne doit être terminée null.
Obtenez un nom de colonne d'un int in java ( en savoir plus ici ):
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;
}
Un autre 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;
}
Obtenez le numéro de colonne de son nom
Java:
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;
}
Easy Java solution ->
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"));
}
CAVEZIER: Ces deux versions n'assument que des lettres majuscules A à Z. Tout ce que d'autre provoque une erreur de calcul. Il ne serait pas difficile d'ajouter un peu de vérification des erreurs et/ou de la majuscule pour les améliorer.
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 autre 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;
En Mathematica:
FromDigits[ToCharacterCode@# - 64, 26] &
Aide-t-il à penser à la chaîne comme inverse du numéro de colonne dans la base 26 avec des chiffres représentés par A, B, ... Z?
Ceci est fondamentalement un nombre dans la base 26, avec la différence que le nombre n'utilise pas 0-9, puis des lettres mais niquement lettres.
def ExcelColumnToNumber(ColumnName):
ColNum = 0
for i in range(0, len(ColumnName)):
# Easier once formula determined: 'PositionValue * Base^Position'
# i.e. 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
p.s. Mon premier Python script!
Voici un CFML One:
<cffunction name="ColToNum" returntype="Numeric">
<cfargument name="Input" type="String" />
<cfset var Total = 0 />
<cfset var Pos = 0 />
<cfloop index="Pos" from="1" to="#Len(Arguments.Input)#">
<cfset Total += 26^(Pos-1) * ( Asc( UCase( Mid(Arguments.Input,Pos,1) ) ) - 64 ) />
</cfloop>
<cfreturn Total />
</cffunction>
<cfoutput>
#ColToNum('AABCCE')#
</cfoutput>
Et parce que je suis d'une humeur étrange, voici une version 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') );
Lisp commun:
(defun Excel->number (string)
"Converts an Excel column name to a column number."
(reduce (lambda (a b) (+ (* a 26) b))
string
:key (lambda (x) (- (char-int x) 64))))
Edit : l'opération inverse:
(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))))))
En python, sans réduction:
def transform(column_string):
return sum((ascii_uppercase.index(letter)+1) * 26**position for position, letter in enumerate(column_string[::-1]))
... Il suffit d'avoir besoin d'une solution pour PHP . C'est ce que je suis venu avec:
/**
* 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;
}
Bien sûr, le script peut être raccourci un peu en combinant des trucs dans une ligne de code dans la boucle de Foreach:
// …
$columnNumber = $columnNumber * 26 + ord($character) - ord('a') + 1;
// …
Delphes:
// 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.
un autre exemple [plus cryptique] Erlang:
col2int(String) -> col2int(0,String).
col2int(X,[A|L]) when A >= 65, A =< 90 ->
col2int(26 * X + A - 65 + 1, L);
col2int(X,[]) -> X.
et fonction inverse:
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]).
Wikipedia a de bonnes explications et des algues
http://fr.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;
}
Utilisation du code de Mathematica génial de M. Wizard, mais de vous débarrasser de la fonction pure cryptique!
columnNumber[name_String] := FromDigits[ToCharacterCode[name] - 64, 26]
Légèrement connexe, le meilleur défi est l'inverse: étant donné le numéro de colonne, trouvez l'étiquette de colonne en tant que chaîne.
QT Version comme ce que j'ai mis en œuvre pour 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;
}