扩展字母数字范围的库或者算法

时间:2020-03-05 18:48:10  来源:igfitidea点击:

我想知道是否有可以扩展非数字范围的开源库或者算法。例如,如果我们具有从1A到9A的信息,则应该得到

1A, 2A, 3A, 4A, 5A, 6A, 7A, 8A, 9A.

我已经尝试使用Googling,而我能想到的最好的方法是Regex,它会用破折号来扩展数字(1-3变成1,2,3)。

解决方案

回答

我试图将其开放一些,因为可能性之多令人st舌。我认为,如果不通过大量技术细节就无法在这里100%回答的问题中的一个被认为是"好"或者"坏"的范围。我只是想找到一个关于其他人如何解决此问题的想法的切入点。我希望有人写一篇博客文章来解释他们如何解决这个问题,或者创建一个完整的库来处理这个问题。

回答

我要说的是,解决方案的第一步将是定义字符和数字如何相互作用并形成一个序列。给定的示例不清楚,因为我们至少假设它运行1A,1B...。8Y,8Z,9A假定输入限制为小数点后跟单个字符。

如果我们可以为字符和小数定义连续的序列,那么我们只需进行一些递归/循环就可以生成该序列的一部分。

例如,我们可以假设输入中的每个字符都是(1-9A-Z)中的一个,因此我们可以通过获取字母字符的十进制ascii值并减去55来轻松地使其连续,实际上可以为我们提供范围(1-35)

回答

如果我们假设开始和结束范围将遵循相同的交替模式,并且将数字范围限制为" 0-9"和" AZ",那么我们可以将每组数字视为多维坐标中的一个组成部分。例如," 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( ) );
}

回答

正如其他人所指出的,更加具体是很有用的。我不认为我们会期望有一个库可以根据我们想出的字符串的任意顺序生成范围。

如果我们可以简单地定义任何给定字符串的后继者,那么解决方案将非常简单。也就是说,如果我们在字符串上具有后继函数" S"(例如,具有" S('3A')='4A'`),则可以使用以下内容:

s = initial_string
while s != final_string do
  output s
  s = S(s)
output s

下面的(伪)代码是我过去用来生成给定长度" l"且字符范围从" b"到" e"的所有字符串的某种东西。它可以轻松适应各种变化。

// initialise s with b at every position
for i in [0..l) do
  s[i] = b
done = false
while not done do
  output s
  j = 0
  // if s[j] is e, reset it to b and "add carry"
  while j < l and s[j] == e do
    s[j] = b
    j = j + 1
    if j == l then
      done = true
  if not done then
    s[j] = s[j] + 1

例如,要从特定字符串开始,只需要更改初始化即可。要设置结束,我们只需要更改内部while的行为即可单独处理位置" l"(限制为该位置的结束字符串中的字符,如果到达则减小" l")。