Если мы предположим, что начальный и конечный диапазоны будут следовать одному и тому же знакопеременному образцу и ограничить диапазон цифр 0-9
и A-Z
, мы можем рассматривать каждую группу цифр как компонент в многомерном координат. Например, 1A
будет соответствовать двумерной координате (1,A)
(это то, что Excel использует для обозначения своей двумерной сетки строк и столбцов); тогда как AA1BB2
будет четырехмерной координатой (AA,1,BB,2)
.
Поскольку каждый компонент независим, для расширения диапазона между двумя координатами мы просто возвращаем все комбинации расширения каждого компонента. Ниже приведена краткая реализация, которую я приготовил сегодня днем. Он работает для произвольного числа чередований нормальных и буквенных чисел и обрабатывает большие буквенные диапазоны (то есть от AB
до CDE
, а не только AB
до CD
).
Примечание: Это предназначено как черновик фактической реализации (я завтра сниму, так что она еще менее отточена, чем обычно;). Применяются все обычные предостережения, касающиеся обработки ошибок, надежности (удобочитаемости;) и т. Д.
IEnumerable<string> ExpandRange( string start, string end ) {
// Split coordinates into component parts.
string[] startParts = GetRangeParts( start );
string[] endParts = GetRangeParts( end );
// Expand range between parts
// (i.e. 1->3 becomes 1,2,3; A->C becomes A,B,C).
int length = startParts.Length;
int[] lengths = new int[length];
string[][] expandedParts = new string[length][];
for( int i = 0; i < length; ++i ) {
expandedParts[i] = ExpandRangeParts( startParts[i], endParts[i] );
lengths[i] = expandedParts[i].Length;
}
// Return all combinations of expanded parts.
int[] indexes = new int[length];
do {
var sb = new StringBuilder( );
for( int i = 0; i < length; ++i ) {
int partIndex = indexes[i];
sb.Append( expandedParts[i][partIndex] );
}
yield return sb.ToString( );
} while( IncrementIndexes( indexes, lengths ) );
}
readonly Regex RangeRegex = new Regex( "([0-9]*)([A-Z]*)" );
string[] GetRangeParts( string range ) {
// Match all alternating digit-letter components of coordinate.
var matches = RangeRegex.Matches( range );
var parts =
from match in matches.Cast<Match>( )
from matchGroup in match.Groups.Cast<Group>( ).Skip( 1 )
let value = matchGroup.Value
where value.Length > 0
select value;
return parts.ToArray( );
}
string[] ExpandRangeParts( string startPart, string endPart ) {
int start, end;
Func<int, string> toString;
bool isNumeric = char.IsDigit( startPart, 0 );
if( isNumeric ) {
// Parse regular integers directly.
start = int.Parse( startPart );
end = int.Parse( endPart );
toString = ( i ) => i.ToString( );
}
else {
// Convert alphabetic numbers to integers for expansion,
// then convert back for display.
start = AlphaNumberToInt( startPart );
end = AlphaNumberToInt( endPart );
toString = IntToAlphaNumber;
}
int count = end - start + 1;
return Enumerable.Range( start, count )
.Select( toString )
.Where( s => s.Length > 0 )
.ToArray( );
}
bool IncrementIndexes( int[] indexes, int[] lengths ) {
// Increment indexes from right to left (i.e. Arabic numeral order).
bool carry = true;
for( int i = lengths.Length; carry && i > 0; --i ) {
int index = i - 1;
int incrementedValue = (indexes[index] + 1) % lengths[index];
indexes[index] = incrementedValue;
carry = (incrementedValue == 0);
}
return !carry;
}
// Alphabetic numbers are 1-based (i.e. A = 1, AA = 11, etc, mod base-26).
const char AlphaDigitZero = (char)('A' - 1);
const int AlphaNumberBase = 'Z' - AlphaDigitZero + 1;
int AlphaNumberToInt( string number ) {
int sum = 0;
int place = 1;
foreach( char c in number.Cast<char>( ).Reverse( ) ) {
int digit = c - AlphaDigitZero;
sum += digit * place;
place *= AlphaNumberBase;
}
return sum;
}
string IntToAlphaNumber( int number ) {
List<char> digits = new List<char>( );
while( number > 0 ) {
int digit = number % AlphaNumberBase;
if( digit == 0 ) // Compensate for 1-based alphabetic numbers.
return "";
char c = (char)(AlphaDigitZero + digit);
digits.Add( c );
number /= AlphaNumberBase;
}
digits.Reverse( );
return new string( digits.ToArray( ) );
}