簡體   English   中英

如何生成與模式匹配的數字序列?

[英]How to generate sequence of numbers matching a pattern?

我需要生成所有數字的序列,匹配特定數字范圍內的模式。 例如:

  • 范圍開始:1
  • 范圍結束:100
  • 模式:* 9 *(即在正則表達式[0-9]*9[0-9]*
  • 預期結果:[9,19,29,39,49,59,69,79,89,90,...,99]

當然,我可以使用蠻力方法,我遍歷所有數量的范圍,並根據模式測試每個數字。 這是在Python中完成的示例實現:

def brute_force(start, end, limit, pattern):
    if start < pattern:
        start = pattern
    if pattern > end:
        return []

    pattern_str = str(pattern)        
    generator = (n for n in range(start, end) if pattern_str in str(n))
    return list(next(generator) for _ in range(limit))

不幸的是,范圍非常大(10 ^ 7個數字),我必須在程序中經常更改模式。 因此,我需要一種更有效的方法。

重要的是要注意,我需要有一個排序列表,我經常只需要X個匹配的數字(參見上面例子中的limit參數)。

我想這種問題有某種標准算法,但我不知道要搜索什么。 有任何想法嗎?

這是一個通用解決方案,可生成最大固定大小的數字*(pat)*其中pat是任何數字序列。

它按遞增順序生成數字,沒有重復。 因為它是一個生成器,所以可以很容易地使用它生成前幾個結果而不生成整個列表。 (見下面的最后一個例子)。

def numbers(pat, k, matched=1):
    if (matched >> len(pat)) & 1:
        for x in xrange(10 ** k):
            yield x
        return
    if not ((matched << k) >> len(pat)):
        return
    for d in xrange(10):
        nm = 1
        for i, pd in enumerate(pat):
            if pd == str(d) and (matched >> i) & 1:
                nm |= 1 << (i+1)
        for n in numbers(pat, k - 1, nm):
            yield 10 ** (k - 1) * d + n

它的工作原理是記錄到目前為止匹配了多少pat 為了避免當pat已經部分匹配並且下一個數字不匹配時回溯,它保持到目前為止所有可能的部分匹配的位圖,這與(非回溯)正則表達式匹配器的方式非常相似。

這里有一些測試代碼,可以根據緩慢但明顯正確的實現進行檢查。

def numbers_slow(pat, k):
    for i in xrange(10 ** k):
        if pat in str(i):
            yield i

test_cases = [
    ('9', 3),
    ('9', 4),
    ('123', 4),
    ('22', 4),
]
for pat, k in test_cases:
    got = list(numbers(pat, k))
    want = list(numbers_slow(pat, k))
    if got != want:
        print 'numbers(%s, %d) = %s, want %s' % (pat, k, got, want)

以下是問題中給出的示例:

print list(numbers('9', 3))

[9, 19, 29, 39, 49, 59, 69, 79, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 109,
119, 129, 139, 149, 159, 169, 179, 189, 190, 191, 192, 193, 194, 195, 196, 197,
198, 199, 209, 219, 229, 239, 249, 259, 269, 279, 289, 290, 291, 292, 293, 294,
295, 296, 297, 298, 299, 309, 319, 329, 339, 349, 359, 369, 379, 389, 390, 391,
392, 393, 394, 395, 396, 397, 398, 399, 409, 419, 429, 439, 449, 459, 469, 479,
489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 509, 519, 529, 539, 549,
559, 569, 579, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 609, 619,
629, 639, 649, 659, 669, 679, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698,
699, 709, 719, 729, 739, 749, 759, 769, 779, 789, 790, 791, 792, 793, 794, 795,
796, 797, 798, 799, 809, 819, 829, 839, 849, 859, 869, 879, 889, 890, 891, 892,
893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908,
909, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924,
925, 926, 927, 928, 929, 930, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940,
941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956,
957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972,
973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988,
989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999]

這是一個不太容易的案例:

print list(numbers('11', 3))

[11, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 211, 311, 411, 511, 611,
711, 811, 911]

並且顯示該方法的情況即使對於大量數字也是有效的:

print list(numbers('121212121', 10))

[121212121, 1121212121, 1212121210, 1212121211, 1212121212, 1212121213,
1212121214, 1212121215, 1212121216, 1212121217, 1212121218, 1212121219,
2121212121, 3121212121, 4121212121, 5121212121, 6121212121, 7121212121,
8121212121, 9121212121]

為了證明限制生成結果的能力,這里是包含'100000'的前21個長達20位的數字:

print itertools.islice(numbers('100000', 20), 21)

[100000, 1000000, 1000001, 1000002, 1000003, 1000004, 1000005, 1000006, 1000007,
1000008, 1000009, 1100000, 2100000, 3100000, 4100000, 5100000, 6100000, 7100000,
8100000, 9100000, 10000000]

由於您只需要前x數字,讓我們從數字的數量的角度來看它。

一位數很容易。
對於兩位數,請獲取所需的其他數字的所有選項(8種可能性)。 然后,從最小到最大的工作,獲得最小值的組合。 然后使用更大的值組合再次迭代。 三位數或更多位的情況與兩位數的情況相同。

示例 - 三位數:

counter = 0;
options = generate_all_combinations_for_k_digits_in_order(2) //for the 3 digit case.
//options = [10, 11, 12, ..., 20, 21, ..., 91, 92, ..., 99]
for (int i = 0; i < number_of_digits - 1; i++)
{
    for (int j = 0; j < options.length; j++)
    {
        print_9_in_position_i_of_number_j(i,j);
        counter++;
        if (counter == x)
            break; // End the loop somehow... 
    }
}

這是模式:

1009
  ^-- 0-8
1090
   ^-- 0-8

1109
  ^-- 0-8
1190
   ^-- 0-8
 ...
1209
 ^-- 2-8 and the rightmost two digit pattern repeats till 1900
1900
  01-99 this is different
 --------------------------------------------------------------
^-- 2-8 Now the whole previous structure repeats for each thousand until 9000

9000
 ^-- 001-999

我們可以為n < 10^m and n has m digits制定一個遞歸n < 10^m and n has m digits

f(n = 10^m - 1)
   => 9*10^(m-1) + 1..10^(m-2)-1  // that's like the 9000 + (001..999)
   => 8*10^(m-1) + f(n-1)         // now for each leftmost digit down to 1, 
   => ...                         // we append all the results from f(n-1)
   => 1*10^(m-1) + f(n-1)

Ruby代碼:

def f m    
  if m == 1
   return [9]
  end

  result = []

  ((10**m).to_i - 1).downto(9 * 10**(m-1).to_i).each do |i|
    result << i
  end

  temp = 10**(m-1)

  8.downto(0).each do |i|
    f(m-1).each do |j|
      result << (i * temp + j)
    end
  end

  result
end

結果:

ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux]

=> :f
 > f 3
=> [999, 998, 997, 996, 995, 994, 993, 992, 991, 990, 989, 988, 987, 986, 985, 984, 983
   , 982, 981, 980, 979, 978, 977, 976, 975, 974, 973, 972, 971, 970, 969, 968, 967, 966
   , 965, 964, 963, 962, 961, 960, 959, 958, 957, 956, 955, 954, 953, 952, 951, 950, 949
   , 948, 947, 946, 945, 944, 943, 942, 941, 940, 939, 938, 937, 936, 935, 934, 933, 932
   , 931, 930, 929, 928, 927, 926, 925, 924, 923, 922, 921, 920, 919, 918, 917, 916, 915
   , 914, 913, 912, 911, 910, 909, 908, 907, 906, 905, 904, 903, 902, 901, 900, 899, 898
   , 897, 896, 895, 894, 893, 892, 891, 890, 889, 879, 869, 859, 849, 839, 829, 819, 809
   , 799, 798, 797, 796, 795, 794, 793, 792, 791, 790, 789, 779, 769, 759, 749, 739, 729
   , 719, 709, 699, 698, 697, 696, 695, 694, 693, 692, 691, 690, 689, 679, 669, 659, 649
   , 639, 629, 619, 609, 599, 598, 597, 596, 595, 594, 593, 592, 591, 590, 589, 579, 569
   , 559, 549, 539, 529, 519, 509, 499, 498, 497, 496, 495, 494, 493, 492, 491, 490, 489
   , 479, 469, 459, 449, 439, 429, 419, 409, 399, 398, 397, 396, 395, 394, 393, 392, 391
   , 390, 389, 379, 369, 359, 349, 339, 329, 319, 309, 299, 298, 297, 296, 295, 294, 293
   , 292, 291, 290, 289, 279, 269, 259, 249, 239, 229, 219, 209, 199, 198, 197, 196, 195
   , 194, 193, 192, 191, 190, 189, 179, 169, 159, 149, 139, 129, 119, 109, 99, 98, 97
   , 96, 95, 94, 93, 92, 91, 90, 89, 79, 69, 59, 49, 39, 29, 19, 9]
 > f(4).length
=> 3439

我嘗試了一個固定長度字符串的“下一個詞典”算法。

JavaScript代碼:

function f(str,pat){
  if (str[0] == 0 
   || isNaN(Number(str))
   || isNaN(Number(pat))
   || str.length == pat.length 
   || str == String(Math.pow(10,str.length - pat.length) - 1 + pat) ){
    return str;
  }

  var AP = "",
      i = 0;

  while (!AP.match(pat) && i < str.length){
    AP += str[i++];
  }

  // if there are digits to the right of the pattern
  if (AP.length < str.length){

    // increment the string if the pattern won't break
    if (String(Number(str) + 1).match(pat)){
      return Number(str) + 1;

    // otherwise, find first number that may be incremented
    } else {
      var i = str.length - pat.length - 1
            + (pat.match(/^9+/) ? pat.match(/9+/)[0].length : 0)

      while (str[i] == 9 && i-- >= 0){
      }

      // increment at i, move pattern to the right, set zeros in between
      return (Number(str.substr(0,i + 1)) + 1) 
           * Math.pow(10,str.length - pat.length) 
           + Number(pat)
    }

  // if the pattern is all the way on the right 
  } else {

    // find rightmost placement for the pattern along an adjacent
    // match where the string could be incremented
    i = 1;

    for (;i<pat.length; i++){
      var j = str.length - pat.length - i,
          patShifted = Number(pat) * Math.pow(10,i);

      if (str.substr(j,i) == pat.substr(0,i) 
       && patShifted > Number(str.substr(j))){

        return Number(str.substr(0,j) + String(patShifted));
      }
    }

    // if no left-shifted placement was found
    return (Number(str.substr(0,str.length - pat.length)) + 1)
          * Math.pow(10,pat.length) + Number(pat);
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM