【问题标题】:a regular expression generator for number ranges数字范围的正则表达式生成器
【发布时间】:2015-11-04 01:09:14
【问题描述】:

我检查了 stackExchange 的描述,算法问题是允许的主题之一。就这样吧。

给定一个范围的输入,其中开始和结束数字具有相同的位数(例如,2、3 或 4),我想编写代码来生成一组正则表达式,当检查时依次告诉我这个数字是否在原来的范围内。

例如:如果范围是 145-387,则 146、200 和 280 都将匹配生成的正则表达式之一,以及 144、390(过去说 290)和 445(过去说 345)不会。

我一直认为结果将是一个正则表达式列表,例如:

14[5-9]             // match 145-149
1[5-9]0-9]          // 150-199
2[0-9][0-9]         // 200-299
3[0-7][0-9]         // 300-379
38[0-7]             // 380-387

然后检查该号码的软件将测试所测试的 3 位数代码是否与其中任何一个匹配。

那么生成表达式集的最佳方法是什么?

我想出的最新(系列)是:

  1. 确定两个范围数不同的第一个数字(1145-1158,第一个不同的数字是第三个)
  2. 对于不同的数字,确定它们的第一个数字是否相差一个以上 - 如果是,则该范围有自己的正则表达式(在我们的示例中为 200-299)
  3. 获得较低的范围:对于每个其他数字:从范围开始的第一个数字作为前缀,将数字加一,用 0 填充到相同的长度,并与具有 9 的数字配对在数字位置和所有填充位置。在我们的示例中,将 4 递增到 5,填充为 150,生成正则表达式以处理 150-199。
  4. 获得更高的范围:对于每个其他数字:从范围末尾开始以第一个数字为前缀,将数字减一,用 0 填充其余部分,在所有填充的 0 位置与一个带有 9 的数字配对,并减少数字。在我们的示例中,正则表达式处理 300-379。

我错过了什么吗?即使在上面我也有一些细节,我正在掩饰,这似乎会受益于一把算法剑削减细节。但是我想出的其他事情比这更混乱。

【问题讨论】:

  • 正则表达式并不是解决所有问题的灵丹妙药,在这种情况下,您最好只检查应用程序代码中的范围。
  • @user3386109 是的,这就是我的意思。我会修改并更正,谢谢。
  • @TimBiegeleisen 如果你仔细阅读这篇文章,你可能会注意到我从未说过正则表达式是任何一种灵丹妙药,我也没有问这是否是检查范围的最佳方法。一般问题包括对数字进行一般检查的要求,不仅是范围,还有其他东西,可以在数据库值中设置,这样就不需要更改代码。当然还有其他方法可以做到这一点,但我问的是这个。 “有一个计算机科学专业的学生遇到了一个问题,他决定用正则表达式来解决它。现在他有 2 个问题。”
  • @willywonka_dailyblah 感谢您的链接;不幸的是,这篇文章没有解决我的问题,并且大部分时间都花在了比较长度不相等的数字字符串的问题上(我的问题没有)。它指出了一两件有用的事情,但我已经确定了这些。还在寻找那颗灵丹妙药!

标签: regex algorithm


【解决方案1】:

这是我的解决方案和一个复杂的算法O(log n)(n 是范围的结尾)。我相信这是这里最简单的一个:

基本上,将您的任务分为以下步骤:

  1. 逐渐“削弱”范围的start
  2. 逐渐“削弱”范围内的end
  3. 合并这两者。

“弱化”是指找到可以用简单正则表达式表示此特定数字的范围的结尾,例如:

145 -> 149,150 -> 199,200 -> 999,1000 -> etc.

这是一个倒退的,对于范围的end

387 -> 380,379 -> 300,299 -> 0

合并是注意到 299->0 和 200->999 的重叠并将它们组合成 200->299 的过程。

结果,你会得到这组数字(第一个列表完整,第二个倒置:

145, 149, 150, 199, 200, 299, 300, 379, 380, 387

现在,有趣的部分来了。成对取数字,并将它们转换为范围:

145-149, 150-199, 200-299, 300-379, 380-387

或者在正则表达式中:

14[5-9], 1[5-9][0-9], 2[0-9][0-9], 3[0-7][0-9], 38[0-7]

weakening 的代码如下所示:

public static int next(int num) {
    //Convert to String for easier operations
    final char[] chars = String.valueOf(num).toCharArray();
    //Go through all digits backwards
    for (int i=chars.length-1; i>=0;i--) {
        //Skip the 0 changing it to 9. For example, for 190->199
        if (chars[i]=='0') {
            chars[i] = '9';
        } else { //If any other digit is encountered, change that to 9, for example, 195->199, or with both rules: 150->199
            chars[i] = '9';
            break;
        }
    }

    return Integer.parseInt(String.valueOf(chars));
}

//Same thing, but reversed. 387 -> 380, 379 -> 300, etc
public static int prev(int num) {
    final char[] chars = String.valueOf(num).toCharArray();
    for (int i=chars.length-1; i>=0;i--) {
        if (chars[i] == '9') {
            chars[i] = '0';
        } else {
            chars[i] = '0';
            break;
        }
    }

    return Integer.parseInt(String.valueOf(chars));
}

剩下的是技术细节,很容易实现。这是O(log n)算法的一个实现:https://ideone.com/3SCvZf

哦,顺便说一句,它也适用于其他范围,例如范围 1-321654 结果是:

[1-9]
[1-9][0-9]
[1-9][0-9][0-9]
[1-9][0-9][0-9][0-9]
[1-9][0-9][0-9][0-9][0-9]
[1-2][0-9][0-9][0-9][0-9][0-9]
3[0-1][0-9][0-9][0-9][0-9]
320[0-9][0-9][0-9]
321[0-5][0-9][0-9]
3216[0-4][0-9]
32165[0-4]

对于129-131,它是:

129
13[0-1]

【讨论】:

  • 我自己终于明白了这一点,我同意这是理想的解决方案。我已经用伪代码解决了这个问题,一直等到今天才可以为它发布实际的 Java。谢谢。
  • 好吧,让我更正一下——现在我已经进一步研究了你的代码,我已经得出了这个通用算法,并用伪代码解决了其中的一部分。我还没有弄清楚你是如何将我的代码中的多个正则表达式折叠成单个的;我将不得不研究更多。这很优雅;谢谢。
  • 基本上,我从范围的开头开始从末尾用零一个一个地替换数字,直到结果数字 > 范围的末尾。然后我在范围结束时向后做同样的事情,但现在向后直到结果数字是
  • 有一个错误:1765-1898 产生了“1[8-7][0-9][0-9]”的“额外”和不正确的正则表达式。我认为这是因为rightBounds() 使用范围开始,我认为它应该使用已经由leftBounds() 确定的范围的结束。正在寻找替代方案,会回来的。
  • 你是对的,在main方法中,rightBounds方法调用应该改为pairs.addAll(rightBounds(pairs.get(pairs.size()-1), end));。我还在 ideone 链接上更新了代码。
【解决方案2】:

我终于明白了。总体思路是从范围的开头开始,生成一个正则表达式,该表达式将从该范围匹配,但不包括下一个 10 的倍数,然后匹配数百个,等等,直到您匹配到该范围的末尾范围;然后从范围的末尾开始向下工作,用 0 替换越来越多的数字以匹配相似的 9,以匹配特定的范围结束。然后为范围的一部分生成一个正则表达式,如果它们还没有覆盖它。

应该特别注意 bezmax 的例程将两个数字转换为将匹配它们的正则表达式 - 我认为比直接处理字符串或字符数组要容易得多。

不管怎样,这里是:

package numbers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Has methods for generating regular expressions to match ranges of numbers.
 */
public class RangeRegexGenerator
{
  public static void main(String[] args)
  {
    RangeRegexGenerator rrg = new RangeRegexGenerator();

//    do
//    {
//      Scanner scanner = new Scanner(System.in);
//      System.out.println("enter start, <return>, then end and <return>");
//      int start = scanner.nextInt();
//      int end = scanner.nextInt();
//      System.out.println(String.format("for %d-%d", start, end));

      List<String> regexes = rrg.getRegex("0015", "0213");
      for (String s: regexes) { System.out.println(s); }
//    } 
//    while(true);
  }

  /**
   * Return a list of regular expressions that match the numbers
   * that fall within the range of the given numbers, inclusive.
   * Assumes the given strings are numbers of the the same length,
   * and 0-left-pads the resulting expressions, if necessary, to the
   * same length. 
   * @param begStr
   * @param endStr
   * @return
   */
  public List<String> getRegex(String begStr, String endStr)
  {
      int start = Integer.parseInt(begStr);
      int end   = Integer.parseInt(endStr);
      int stringLength = begStr.length();
      List<Integer> pairs = getRegexPairs(start, end);
      List<String> regexes = toRegex(pairs, stringLength);
      return regexes;
  }

  /**
   * Return a list of regular expressions that match the numbers
   * that fall within the range of the given numbers, inclusive.
   * @param beg
   * @param end
   * @return
   */
  public List<String> getRegex(int beg, int end)
  {
    List<Integer> pairs = getRegexPairs(beg, end);
    List<String> regexes = toRegex(pairs);
    return regexes;
  }

  /**
   * return the list of integers that are the paired integers
   * used to generate the regular expressions for the given
   * range. Each pair of integers in the list -- 0,1, then 2,3,
   * etc., represents a range for which a single regular expression
   * is generated.
   * @param start
   * @param end
   * @return
   */
  private List<Integer> getRegexPairs(int start, int end)
  {
      List<Integer> pairs = new ArrayList<>();

      ArrayList<Integer> leftPairs = new ArrayList<>();
      int middleStartPoint = fillLeftPairs(leftPairs, start, end);
      ArrayList<Integer> rightPairs = new ArrayList<>();
      int middleEndPoint = fillRightPairs(rightPairs, middleStartPoint, end);

      pairs.addAll(leftPairs);
      if (middleEndPoint > middleStartPoint)
      {
        pairs.add(middleStartPoint);
        pairs.add(middleEndPoint);
      }
      pairs.addAll(rightPairs);
      return pairs;
  }

  /**
   * print the given list of integer pairs - used for debugging.
   * @param list
   */
  @SuppressWarnings("unused")
  private void printPairList(List<Integer> list)
  {
    if (list.size() > 0)
    {
      System.out.print(String.format("%d-%d", list.get(0), list.get(1)));
      int i = 2;
      while (i < list.size())
      {
        System.out.print(String.format(", %d-%d", list.get(i), list.get(i + 1)));
        i = i + 2;
      }
      System.out.println();
    }
  }

  /**
   * return the regular expressions that match the ranges in the given
   * list of integers. The list is in the form firstRangeStart, firstRangeEnd, 
   * secondRangeStart, secondRangeEnd, etc.
   * @param pairs
   * @return
   */
  private List<String> toRegex(List<Integer> pairs)
  {
    return toRegex(pairs, 0);
  }

  /**
   * return the regular expressions that match the ranges in the given
   * list of integers. The list is in the form firstRangeStart, firstRangeEnd, 
   * secondRangeStart, secondRangeEnd, etc. Each regular expression is 0-left-padded,
   * if necessary, to match strings of the given width.
   * @param pairs
   * @param minWidth
   * @return
   */
  private List<String> toRegex(List<Integer> pairs, int minWidth)
  {
    List<String> list = new ArrayList<>();
    String numberWithWidth = String.format("%%0%dd", minWidth);
    for (Iterator<Integer> iterator = pairs.iterator(); iterator.hasNext();)
    {
      String start = String.format(numberWithWidth, iterator.next()); // String.valueOf(iterator.next());
      String end = String.format(numberWithWidth, iterator.next());

      list.add(toRegex(start, end));
    }
    return list;
  }

  /**
   * return a regular expression string that matches the range
   * with the given start and end strings.
   * @param start
   * @param end
   * @return
   */
  private String toRegex(String start, String end)
  {
    assert start.length() == end.length();

    StringBuilder result = new StringBuilder();

    for (int pos = 0; pos < start.length(); pos++)
    {
      if (start.charAt(pos) == end.charAt(pos))
      {
        result.append(start.charAt(pos));
      } else
      {
        result.append('[').append(start.charAt(pos)).append('-')
            .append(end.charAt(pos)).append(']');
      }
    }
    return result.toString();
  }

  /**
   * Return the integer at the end of the range that is not covered
   * by any pairs added to the list.
   * @param rightPairs
   * @param start
   * @param end
   * @return
   */
  private int fillRightPairs(List<Integer> rightPairs, int start, int end)
  {
    int firstBeginRange = end;    // the end of the range not covered by pairs
                                  // from this routine.
    int y = end;
    int x = getPreviousBeginRange(y);

    while (x >= start)
    {
      rightPairs.add(y);
      rightPairs.add(x);
      y = x - 1;
      firstBeginRange = y;
      x = getPreviousBeginRange(y);
    }
    Collections.reverse(rightPairs);
    return firstBeginRange;
  }

  /**
   * Return the integer at the start of the range that is not covered
   * by any pairs added to its list. 
   * @param leftInts
   * @param start
   * @param end
   * @return
   */
  private int fillLeftPairs(ArrayList<Integer> leftInts, int start, int end)
  {
    int x = start;
    int y = getNextLeftEndRange(x);

    while (y < end)
    {
      leftInts.add(x);
      leftInts.add(y);
      x = y + 1;
      y = getNextLeftEndRange(x);
    }
    return x;
  }

  /**
   * given a number, return the number altered such
   * that any 9s at the end of the number remain, and
   * one more 9 replaces the number before the other
   * 9s.
   * @param num
   * @return
   */
  private int getNextLeftEndRange(int num)
  {
    char[] chars = String.valueOf(num).toCharArray();
    for (int i = chars.length - 1; i >= 0; i--)
    {
      if (chars[i] == '0')
      {
        chars[i] = '9';
      } else
      {
        chars[i] = '9';
        break;
      }
    }

    return Integer.parseInt(String.valueOf(chars));
  }

  /**
   * given a number, return the number altered such that
   * any 9 at the end of the number is replaced by a 0,
   * and the number preceding any 9s is also replaced by
   * a 0.
   * @param num
   * @return
   */
  private int getPreviousBeginRange(int num)
  {
    char[] chars = String.valueOf(num).toCharArray();
    for (int i = chars.length - 1; i >= 0; i--)
    {
      if (chars[i] == '9')
      {
        chars[i] = '0';
      } else
      {
        chars[i] = '0';
        break;
      }
    }

    return Integer.parseInt(String.valueOf(chars));
  }
}

据我测试,这个是正确的; bezmax 发布的那个不太奏效,尽管他对整体算法有正确的想法(我也想出了),以及一个或两个有帮助的主要实现细节,所以我留下了“答案”复选标记关于他的回应。

我对由此产生的如此多的兴趣感到有点惊讶,尽管不如问题的复杂程度那么大。

【讨论】:

  • 我已经在 Python 中调整了我的递归解决方案,以匹配您/bezmax 的实现。我认为,getRegexPairs 的最终替代实现需要更少的代码并且更容易证明是正确的。 (见我的第二个答案)
  • for (String s: regexes) { System.out.print(s+"|"); }
  • 复杂程度很多,几乎无法理解regexformat.com/scrn8/MegNrange.jpg
  • 您的链接中的示例集包括负数(原问题中没有提到),并且它们中的大多数都有范围,其中范围内的开始和结束数字具有不同的位数,原始问题明确说明不是输入的一部分,这使问题变得更加困难。
  • 该工具有一个复选框,如果它是浮点范围或不是。如果未选中该框,则会找到整数范围。此外,对于浮点数,输出默认为 min/max 范围内的最大小数位数。实现这一点的技术是分阶段完成的,包括分解。只是提醒一下完成全部工作所需的复杂性。可能还有其他工具可以做到这一点,但我还没有找到。
【解决方案3】:

您不能仅使用字符组来满足您的要求。想象一下范围129-131。模式1[2-3][1-9] 也将匹配超出范围的139

因此,在本例中,您需要将最后一组更改为其他内容:1[2-3](1|9)。您现在也可以发现对于十位和一百位的这种效果,这导致了一个问题,即基本上将每个有效数字表示为固定数字序列的模式是唯一可行的解​​决方案。 (如果您不想要一个需要跟踪溢出以决定是否应该使用[2-8](8,9,0,1,2) 的算法)

如果你还是自动生成模式 - 保持简单:

128-132

可以写成(为了更好的可读性,我省略了不匹配的组添加?:

(128|129|130|131|132)

算法应该是ovious、for、数组、字符串拼接和join。

这已经可以按预期工作了,但是如果您希望它更紧凑,也可以对此进行一些“优化”:

(128|129|130|131|132) <=>
1(28|29|30|31|32) <=>
1(2(8|9)|3(0|1|2))

更多优化

1(2([8-9])|3([0-2]))

最后一步的算法已经存在,寻找分解。一种简单的方法是将所有数字推送到树上,具体取决于字符位置:

1
  2
    8
    9
  3
    0
    1
    2

最后遍历这三个并形成模式1(2(8|9)|3(0|1|2))。最后一步,将(a|(b|)*?c) 模式中的任何内容替换为[a-c]

11-29 也是如此:

11-29 <=>
(11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29) <=>   
(1(1|2|3|4|5|7|8|9)|2(1|2|3|4|5|7|8|9)) <=>
(1([1-9])|2([1-9]) 

作为补充,您现在可以继续分解:

(1([1-9])|2([1-9]) <=>
(1|2)[1-9] <=>
[1-2][1-9]

【讨论】:

  • 据我了解,129-131 是一个合适的情况,应该产生两个正则表达式的列表:“129”、“13[0-1]”。
  • @bezmax 是的,它应该是有效的并且在示例中。根据我无法得出的要求,应该生成 2 个表达式?
  • 是的,请查看原帖的这一部分:I have been thinking the result would be a list of regular expressions like: ...,注意list 部分
  • @bezmax 我一直在想并不意味着必须是
【解决方案4】:

这是 python 中的递归解决方案,适用于任意范围的正数。想法是将范围划分为三个子范围:

  • 从 start 到下一个 10 的倍数(如果 start 还不是 10 的倍数)
  • 从最后一个 10 的倍数到 end(如果 end 还不是 10 的倍数)
  • 这两个 10 的倍数之间的范围可以递归处理,方法是去掉最后一个数字,然后将正则表达式 [0-9] 添加到所有生成的正则表达式中

下面的代码甚至优化了单个值的范围,例如 [1-1]1。要调用的函数是genRangeRegex(开始是包含,结束是排除):

def regexRangeDigits(start,stop):
  if start == stop:
    return str(start)
  return '[%d-%d]' % (start,stop)

# generate list of regular expressions for the number range [start,end[
def genRangeRegex(start, end):
  if start <= 0:
    raise ValueError('only ranges of positive numbers supported')

  print 'getting regex list for range [%d,%d[' % (start,end)
  if start >= end:
    return []

  digitsStart = str(start)
  digitsEnd   = str(end)
  lastDigitStart = start%10

  if start//10 == (end-1)//10: # integer division
    lastDigitStop = (end-1)%10
    regexAll = digitsStart[:-1] + regexRangeDigits(lastDigitStart,lastDigitStop)
    print '  regexAll   = %s' % regexAll
    return [regexAll]

  regexListStart = [] # at most one regular expression for going up to first multiple of 10
  if lastDigitStart != 0:
    regexStart = digitsStart[:-1] + regexRangeDigits(lastDigitStart,9)
    print '  regexStart = %s' % regexStart
    regexListStart.append(regexStart)

  regexListEnd = [] # at most one regular expression for going up from last multiple of 10
  lastDigitEnd = end%10
  if lastDigitEnd != 0:
    regexEnd = digitsEnd[:-1] + regexRangeDigits(0,lastDigitEnd-1)
    print '  regexEnd   = %s' % regexEnd
    regexListEnd.append(regexEnd)

  regexListMidTrunc = genRangeRegex((start+9)//10, end//10)
  regexListMid = [r+'[0-9]' for r in regexListMidTrunc]

  return regexListStart + regexListMid + regexListEnd

这里有一个例子输出函数是如何工作的:

>>> genRangeRegex(12,231)
getting regex list for range [12,231[
  regexStart = 1[2-9]
  regexEnd   = 230
getting regex list for range [2,23[
  regexStart = [2-9]
  regexEnd   = 2[0-2]
getting regex list for range [1,2[
  regexAll   = 1
['1[2-9]', '[2-9][0-9]', '1[0-9][0-9]', '2[0-2][0-9]', '230']

【讨论】:

  • genRangeRegex(129,131); 失败(根本没有输出)
  • @dognose 当我使用 Python 2.7.10 执行上述代码并调用 genRangeRegex(129,131) 我得到输出 ['129', '130'];请注意,我将函数设计为具有独占上限。
【解决方案5】:

[提示:不知何故,在我的第一个答案(使用 Python)中提出的应用递归的想法没有到达 OP,所以这里又是在 Java 中。请注意,对于递归解决方案,通常更容易证明正确性。]

使用递归的关键观察是,以0 结尾的数字开头和以9 结尾的数字结尾的范围被全部以[0-9] 结尾的数字模式覆盖。

20-239 is covered by [2-9][0-9], 1[0-9][0-9], 2[0-3][0-9]

当去掉范围开始和结束的最后一位数字时,结果范围被相同的数字模式覆盖,除了缺少尾随[0-9]

20-239 is covered by [2-9][0-9], 1[0-9][0-9], 2[0-3][0-9]
2 -23  is covered by [2-9],      1[0-9],      2[0-3]

因此,当我们寻找覆盖某个范围的数字模式时(例如13-247),我们将第一个以0 结尾的数字之前的范围和最后一个以9 结尾的数字之后的范围分开(请注意,这些拆分范围可以为空),例如

13-247 = 13-19, 20-239, 240-247
20-247 =        20-239, 240-247
13-239 = 13-19, 20-239
20-239 =        20-239

剩余范围通过删除最后一个数字并将[0-9]附加到缩小范围的所有数字模式来递归处理。

当为可以被一位数模式覆盖的子范围生成对 start,end 时(如 bezmax 和 OP 所做的那样),缩小范围的子范围必须相应地“放大”。

范围内没有以0结尾的数字或范围内没有以9结尾的数字的特殊情况只有在开始和结束仅在最后一位不同时才会发生;在这种情况下,一个数字模式可以覆盖整个范围。

所以这里有一个基于递归原理的getRegexPairs 的替代实现:

private static List<Integer> getRegexPairs(int start, int end)
{
  List<Integer> pairs = new ArrayList<>();   
  if (start > end) return pairs; // empty range
  int firstEndingWith0 = 10*((start+9)/10); // first number ending with 0
  if (firstEndingWith0 > end) // not in range?
  {
    // start and end differ only at last digit
    pairs.add(start);
    pairs.add(end);
    return pairs;
  }

  if (start < firstEndingWith0) // start is not ending in 0
  {
    pairs.add(start);
    pairs.add(firstEndingWith0-1);
  }

  int lastEndingWith9 = 10*(end/10)-1; // last number in range ending with 9
  // all regex for the range [firstEndingWith0,lastEndingWith9] end with [0-9]
  List<Integer> pairsMiddle = getRegexPairs(firstEndingWith0/10, lastEndingWith9/10);
  for (int i=0; i<pairsMiddle.size(); i+=2)
  {
    // blow up each pair by adding all possibilities for appended digit
    pairs.add(pairsMiddle.get(i)  *10+0);
    pairs.add(pairsMiddle.get(i+1)*10+9);
  }

  if (lastEndingWith9 < end) // end is not ending in 9
  {
    pairs.add(lastEndingWith9+1);
    pairs.add(end);
  }

  return pairs;
}

【讨论】:

    【解决方案6】:

    一种选择是(对于范围 [n, m])生成正则表达式 n|n+1|...|m-1|m。但是,我认为您正在获得更优化的东西。您仍然可以做同样的事情,使用通过状态机的不同路径生成一个匹配每个数字的 FSM,然后使用任何著名的 FSM 最小化算法生成一个更小的机器,然后将其转换为更简洁的正则表达式(因为没有 Perl 扩展的“正则表达式”与有限状态机同构)。

    假设我们正在查看范围 [107, 112]:

    state1:
      1 -> state2
      * -> NotOK
    state2:
      0 -> state2.0
      1 -> state2.1
      * -> NotOK
    state2.0:
      7 -> OK
      8 -> OK
      9 -> OK
      * -> NotOK
    state2.1:
      0 -> OK
      1 -> OK
      2 -> OK
      * -> NotOK
    

    我们不能再进一步减少这台机器了。我们可以看到 state2.0 对应于 RE [789],2.1 对应于[012]。然后我们可以看到state2.0是(0[789])|(1[012]),整体是1(0[789])|(1[012])

    关于DFA minimization 的进一步阅读可以在维基百科上找到(以及从那里链接的页面)。

    【讨论】:

      【解决方案7】:

      如果您发现 5 到 300 之间的正则表达式模式范围也支持浮点数;有我创造的最佳答案...

      ^0*(([5-9]([.][0-9]{1,2})?)|[1-9][0-9]{1}?([.][0-9]{1,2})?|[12][0-9][0-9]([.][0-9]{1,2})?|300([.]0{1,2})?)$
      

      适用于 1 到 300 范围

      ^0*([1-9][0-9]?([.][0-9]{1,2})?|[12][0-9][0-9]([.][0-9]{1,2})?|300([.]0{1,2})?)$
      

      【讨论】:

        【解决方案8】:

        Bezmax 的答案很接近,但并不能完全正确地解决问题。我相信它有一些不正确的细节。我已经解决了这些问题并用 C++ 编写了算法。 Bezmax算法的主要问题如下:

        prev 函数应产生以下内容: 387 -> 380,379 -> 300,299 -> 100, 99->10, 9->0 而 Bezmax 有: 387 -> 380,379 -> 300,299 -> 0

        Bezmax 有 299 “弱化”至 0,这可能会在某些情况下留下部分范围。基本上你想弱化到最低的数字,但永远不要改变数字的数量。完整的解决方案是太多代码,无法在此处发布,但这里是重要部分。希望这对某人有所帮助。

            // Find the next number that is advantageous for regular expressions.
            //
            // Starting at the right most decimal digit convert all zeros to nines. Upon
            // encountering the first non-zero convert it to a nine and stop. The output
            // always has the number of digits as the input.
            // examples: 100->999, 0->9, 5->9, 9->9, 14->19, 120->199, 10010->10099
            static int Next(int val)
            {
               assert(val >= 0);
        
               // keep track of how many nines to add to val.
               int addNines = 0;
        
               do {
                  auto res = std::div(val, 10);
                  val = res.quot;
                  ++addNines;
                  if (res.rem != 0) {
                     break;
                  }
               } while (val != 0);
        
               // add the nines
               for (int i = 0; i < addNines; ++i) {
                  val = val * 10 + 9;
               }
        
               return val;
            }
        
            // Find the previous number that is advantageous for regular expressions.
            //
            // If the number is a single digit number convert it to zero and stop. Else...
            // Starting at the right most decimal digit convert all trailing 9's to 0's
            // unless the digit is the most significant digit - change that 9 to a 1. Upon
            // encounter with first non-nine digit convert it to a zero (or 1 if most
            // significant digit) and stop. The output always has the same number of digits
            // as the input.
            // examples: 0->0, 1->0, 29->10, 999->100, 10199->10000, 10->10, 399->100
            static int Prev(int val)
            {
               assert(val >= 0);
        
               // special case all single digit numbers reduce to 0
               if (val < 10) {
                  return 0;
               }
        
               // keep track of how many zeros to add to val.
               int addZeros = 0;
        
               for (;;) {
                  auto res = std::div(val, 10);
                  val = res.quot;
                  ++addZeros;
                  if (res.rem != 9) {
                     break;
                  }
        
                  if (val < 10) {
                     val = 1;
                     break;
                  }
               }
        
               // add the zeros
               for (int i = 0; i < addZeros; ++i) {
                  val *= 10;
               }
        
               return val;
            }
        
            // Create a vector of ranges that covers [start, end] that is advantageous for
            // regular expression creation. Must satisfy end>=start>=0.
            static std::vector<std::pair<int, int>> MakeRegexRangeVector(const int start,
                                                                         const int end)
            {
               assert(start <= end);
               assert(start >= 0);
        
               // keep track of the remaining portion of the range not yet placed into
               // the forward and reverse vectors.
               int remainingStart = start;
               int remainingEnd = end;
        
               std::vector<std::pair<int, int>> forward;
               while (remainingStart <= remainingEnd) {
                  auto nextNum = Next(remainingStart);
                  // is the next number within the range still needed.
                  if (nextNum <= remainingEnd) {
                     forward.emplace_back(remainingStart, nextNum);
                     // increase remainingStart as portions of the numeric range are
                     // transfered to the forward vector.
                     remainingStart = nextNum + 1;
                  } else {
                     break;
                  }
               }
               std::vector<std::pair<int, int>> reverse;
               while (remainingEnd >= remainingStart) {
                  auto prevNum = Prev(remainingEnd);
                  // is the previous number within the range still needed.
                  if (prevNum >= remainingStart) {
                     reverse.emplace_back(prevNum, remainingEnd);
                     // reduce remainingEnd as portions of the numeric range are transfered
                     // to the reverse vector.
                     remainingEnd = prevNum - 1;
                  } else {
                     break;
                  }
               }
        
               // is there any part of the range not accounted for in the forward and
               // reverse vectors?
               if (remainingStart <= remainingEnd) {
                  // add the unaccounted for part - this is guaranteed to be expressable
                  // as a single regex substring.
                  forward.emplace_back(remainingStart, remainingEnd);
               }
        
               // Concatenate, in reverse order, the reverse vector to forward.
               forward.insert(forward.end(), reverse.rbegin(), reverse.rend());
        
               // Some sanity checks.
               // size must be non zero.
               assert(forward.size() > 0);
        
               // verify starting and ending points of the range
               assert(forward.front().first == start);
               assert(forward.back().second == end);
        
               return forward;
            }
        

        【讨论】:

          【解决方案9】:

          我最近有一个需求,我需要使用 Java 开发一个“数字范围的正则表达式生成器”,我开发了一个完整的解决方案,发布在 IdeOne 上,类名为 ideone.java -- Source Code on ideone.com。 ideone 上的代码被大量评论,它使用与其他用户发布的相同算法,所以我只会强调添加的更改和功能或修复的问题。我使用了 Bezmax(概念)、arcy(将 RegEx 范围生成为对的整体代码和想法)和coproc(使用递归生成 RegEx 对,而不是 arcy 使用的方法)在答案中提供的部分解决方案。感谢这三个人。

          Ideone.java 提供了两个公共方法,它们实现了 RegExGenerator 逻辑——一个接受字符串数字范围,另一个接受整数范围。

          generateRegEx(String begStr, String endStr)
          generateRegEx(int beg, int end)
          

          上述两个公共方法都调用 generateRegExCommon 方法,该方法又调用 getRegExPairsRecursion 方法(与 coproc 的答案提供的实现相同),以生成一个包含成对数字的列表,这些数字代表有效 RegEx 范围的下限和上限。然后它调用 formatPairsToRegEx 方法将 RegEx 对转换为实际的 RegEx 范围,如果输入中有零,则该范围可能包含零前缀以匹配输入长度。如果输入不包含前导零或使用整数输入,则不会将前导零添加到输出范围。输出可作为字符串数组列表使用,其中每个条目/元素都是有效的正则表达式数值范围:

          regexArray - String Array where each element is a valid regular expression range.
          regexList  - List of String elements where each element is a valid regular expression range.
          

          下图显示了 Java 代码 (ideone.java) 示例执行的序列图,每个阶段都有输入和输出。使用的示例具有带前导零的数字字符串的输入范围,其中下限值为“0006”,上限为“0977”。如下图输出为:

          000[6-9]
          00[1-9][0-9]
          0[1-8][0-9][0-9]
          09[0-6][0-9]
          097[0-7]
          

          代码提供以下好处:

          • 结合所有最佳答案和算法的单一综合解决方案。
          • 打印有助于调试代码的语句,并且可以轻松转换为任何日志框架跟踪/调试语句,而不是 System.out。
          • 修复了低范围 0 不适用于其他答案的问题。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-05-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多