编程之谜:如何将Excel列名转换为数字?
最近我在求职面试中被问到解决一个编程难题,我认为这将是有趣的分享。 它是关于将Excel列字母翻译为实际数字的,如果您记得,Excel会用从A到Z的字母命名其列,然后顺序为AA,AB,AC … AZ,BA,BB等。
你必须写一个接受一个string作为参数的函数(比如“AABCCE”)并返回实际的列号。
解决scheme可以是任何语言。
我很久以前写了一些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
听起来像一个标准减less我:
python:
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 ); }
从STDIN读取一个列名并打印出相应的数字:
perl -le '$x = $x * 26 - 64 + ord for <> =~ /./g; print $x'
注意事项:假设ASCII。
编辑:取代"
与'
以便您的shell不会在string中插入$x
。
巧合的是,我用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>
哈 – 已经写在我们的代码库 – 约3个不同的时间:(
%% @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).
谜语是,它实际上并不是一个数字的Base26表示(我们在这里用自己的函数名称来说谎),因为它没有0。
顺序是:A,B,C … Z,AA,AB,AC
而不是:A,B,C … Z,BA,BB,BC
(语言是Erlang,mais oui)。
你可以用C来做到这一点:
unsigned int coltonum(char * string) { unsigned result = 0; char ch; while(ch = *string++) result = result * 26 + ch - 'A' + 1; return result; }
没有错误检查,只适用于大写string,string必须为空。
从名字中获取列号
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; }
假设列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; }
从Java中获取一个int的列名( 在这里阅读更多 ):
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; }
注意:这两个版本都只能使用大写字母A到Z,否则会导致错误计算。 添加一些错误检查和/或大写来改进它们并不难。
斯卡拉
def excel2Number(excel : String) : Int = (0 /: excel) ((accum, ch) => accum * 26 + ch - 'A' + 1)
哈斯克尔
excel2Number :: String -> Int excel2Number = flip foldl 0 $ \accum ch -> accum * 26 + fromEnum ch - fromEnum 'A' + 1
另一个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;
另一个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; }
简单的Java解决scheme – >
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")); }
这是否有助于将string视为基数为26的列号的反转,其中数字由A,B,… Z表示?
这基本上是以26为基数的数字,不同之处在于数字不使用0-9,而是字母,但只使用字母。
这是一个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>
而且因为我心情很奇怪,所以这是一个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') );
另一个[更神秘的] 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.
和反函数:
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。
稍有关系,更好的挑战是相反的:给出列号,find列标签作为string。
Qt版本与我为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; }
Common Lisp:
(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))))
编辑:逆操作:
(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))))))
这个版本是纯粹的function,允许替代的“代码”序列,例如,如果你只想使用字母“A”到“C”。 在斯卡拉,来自大脑的build议。
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我的第一个Python脚本!
在Mathematica中:
FromDigits[ToCharacterCode@# - 64, 26] &
使用Mr. Wizard的真棒Mathematica代码,但摆脱了神秘的纯function!
columnNumber[name_String] := FromDigits[ToCharacterCode[name] - 64, 26]
维基百科有很好的解释和algorithm
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; }
…只需要PHP的解决scheme。 这就是我想到的:
/** * 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; }
当然,通过在foreach循环中将一些东西合并到一行代码中,脚本可以缩短一点:
// … $columnNumber = $columnNumber * 26 + ord($character) - ord('a') + 1; // …
在Python中,不减less:
def transform(column_string): return sum((ascii_uppercase.index(letter)+1) * 26**position for position, letter in enumerate(column_string[::-1]))
这里是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.