algorithm - una - Enigma de programación: ¿Cómo puedes traducir un nombre de columna de Excel a un número?
separar texto en excel formula (28)
Recientemente, en una entrevista de trabajo, se me pidió que resolviera un problema de programación que pensé que sería interesante compartir. Se trata de traducir las letras de las columnas de Excel a los números reales; si recuerdas, Excel nombra sus columnas con letras de la A a la Z, y luego la secuencia es AA, AB, AC ... AZ, BA, BB, etc.
Debe escribir una función que acepte una cadena como parámetro (como "AABCCE") y devuelva el número de columna real.
La solución puede estar en cualquier idioma.
¿Ayuda a pensar en la cadena como el reverso del número de columna en la base 26 con dígitos representados por A, B, ... Z?
... solo necesitaba una solución para PHP . Esto es lo que se me ocurrió:
/**
* 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;
}
Por supuesto, el script se puede acortar un poco simplemente combinando algunas cosas en una línea de código dentro del bucle foreach:
// …
$columnNumber = $columnNumber * 26 + ord($character) - ord(''a'') + 1;
// …
Advertencia: ambas versiones asumen solo las letras mayúsculas de la A a la Z. Cualquier otra cosa causa un error de cálculo. No sería difícil agregar un poco de verificación de errores y / o mayúsculas para mejorarlos.
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
Aquí hay otra versión de este código en 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.
Aquí hay uno de CFML:
<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>
Y porque estoy de un humor extraño, aquí hay una versión de 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'') );
Casualmente he resuelto el mismo 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));
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<form id="form">
<input type="text" id="number"></input>
<button>submit</button>
</form>
<p id="answer"></p>
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;
};
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;
-Alabama.
En Mathematica:
FromDigits[ToCharacterCode@# - 64, 26] &
En Python, sin reducir:
def transform(column_string):
return sum((ascii_uppercase.index(letter)+1) * 26**position for position, letter in enumerate(column_string[::-1]))
Escribí esto hace años para un script de 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
Esta versión es puramente funcional y permite secuencias de ''código'' alternativas, por ejemplo, si solo quiere usar las letras ''A'' a ''C''. En Scala, con una sugerencia de dcsobral.
def columnNumber(name: String) = {
val code = ''A'' to ''Z''
name.foldLeft(0) { (sum, letter) =>
(sum * code.length) + (code.indexOf(letter) + 1)
}
}
Este es básicamente un número en la base 26, con la diferencia de que el número no usa 0-9 y luego letras, solo letras.
Hah, ya lo escribí en nuestra base de código, aproximadamente 3 veces diferentes :(
%% @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).
El enigma es que en realidad no es una representación de Base26 de un número (nos estamos mintiendo en nuestro nombre de función aquí) porque no hay un 0 en él.
La secuencia es: A, B, C ... Z, AA, AB, AC
y no: A, B, C ... Z, BA, BB, BC
(el idioma es erlang, mais oui).
Lea un nombre de columna de STDIN e imprima su número correspondiente:
perl -le ''$x = $x * 26 - 64 + ord for <> =~ /./g; print $x''
Advertencias: Asume ASCII.
EDITAR: Se reemplazó "
con ''
para que su shell no interpole $x
en la cadena.
Lisp Común:
(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))))
Edición: la operación 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))))))
Obtenga el número de columna de su nombre
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;
}
Obtenga un nombre de columna de un int en Java ( lea más aquí ):
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;
}
Otra 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;
Otro 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;
}
Otro ejemplo de erlang [más críptico]:
col2int(String) -> col2int(0,String).
col2int(X,[A|L]) when A >= 65, A =< 90 ->
col2int(26 * X + A - 65 + 1, L);
col2int(X,[]) -> X.
y función 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]).
Puedes hacer esto en C así:
unsigned int coltonum(char * string)
{
unsigned result = 0;
char ch;
while(ch = *string++)
result = result * 26 + ch - ''A'' + 1;
return result;
}
Sin comprobación de errores, solo funciona con cadenas en mayúsculas, la cadena debe estar terminada en nulo.
Solución Java sencilla ->
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"));
}
Suena como una reducción estándar para mí:
Pitón:
def excel2num(x):
return reduce(lambda s,a:s*26+ord(a)-ord(''A'')+1, x, 0)
DO#:
int ExcelToNumber(string x) {
return x.Aggregate(0, (s, c) => s * 26 + c - ''A'' + 1 );
}
Suponiendo la columna 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<exponent; i++)
power *= number;
return power;
}
Un poco relacionado, el mejor desafío es al revés: dado el número de columna, encuentre la etiqueta de la columna como una cadena.
La versión Qt como lo implementé para 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;
}
Usando el asombroso código Mathematica de Mr. Wizard, ¡pero deshaciéndose de la función críptica pura!
columnNumber[name_String] := FromDigits[ToCharacterCode[name] - 64, 26]
Wikipedia tiene buenas explicaciones y 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;
}
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
ps mi primer script en Python!