如何确定包含简并子字符串的字母的可能组合数

时间:2018-10-15 18:54:15

标签: r regex probability combinatorics longest-substring

我已经花了几天的时间来解决以下问题的级数或封闭形式的方程式:

具体来说:给定所有长度为 N 的字符串,这些字符串均取自​​ L 字母的字母(以'A'开头,例如{A,B},{A ,B,C},...),那么其中多少个字符串包含与模式匹配的子字符串:“ A”,超过1个非“ A”,“ A”。该模式的标准正则表达式为git revert <B-sha1>

可能的字符串数很简单: L ^ N 。对于 N L 的较小值,简单地创建所有可能的组合并使用正则表达式查找与模式匹配的子字符串也非常实用。在R中:

A[^A][^A]+A

我想出了以下方法,它适用于N <7:

all.combinations <- function(N, L) {
    apply(
        expand.grid(rep(list(LETTERS[1:L]), N)),
        1,
        paste,
        collapse = ''
    )
}

matching.pattern <- function(N, L, pattern = 'A[^A][^A]+A') {
    sum(grepl(pattern, all.combinations(N, L)))
}

all.combinations(4, 2)
matching.pattern(4, 2)

不幸的是,这仅在N <7时有效,因为它只是添加具有子字符串A..A,A ... A,A .... A等的组合,并且某些组合显然具有多个匹配的子字符串(例如A..A..A,A..A ... A),它们被计数两次。

有什么建议吗?我也对程序解决方案持开放态度,只要它们不会增加组合数量(就像上面的代码一样)。我希望能够计算N的值从15到25,L的值从2到10。

对于它的价值,这里是组合的数量,以及一些N和L值的匹配组合,这些值可以通过生成所有组合并进行正则表达式匹配来确定:

M <- function(N, L) {
    sum(
        sapply(
            2:(N-2),
            function(g) {
                (N - g - 1) * (L - 1) ** g * L ** (N - g - 2)
            }
        )
    )
}

2 个答案:

答案 0 :(得分:0)

可以使用动态编程方法。

对于固定的L,令X(n)为包含给定模式的长度为n的字符串数,令A(n)为长度为{{1}的字符串数},其中包含给定的模式并以A开头。

首先导出n的递归公式。通过按前2-3个字母分组来计算A(n)中的所有字符串。 A(n)中的字符串数,其中:

  • “第二个字母A”为A(n)
  • “第二个字母非A,第三个字母为A”是A(n-1)
  • “第二个和第三个字母非A”为A(n-2)。这是因为字符串“需要”在剩余的字母中至少计算一个A。

有了这个

(L^(n-3) - (L-1)^(n-3))

长度为A(n) = A(n-1) + (L-1) * (A(n-2) + (L-1) * (L^(n-3) - (L-1)^(n-3))) 的字符串可以以A或非A开头:

n+1

Python实现:

X(n+1) = A(n+1) + (L-1) * X(n)
X(i) = A(i) = 0, for i <= 3

答案 1 :(得分:0)

这可以描述为状态机。 (为简单起见,xA以外的任何字母。)

S0 := 'A' S1 | 'x' S0     // ""
S1 := 'A' S1 | 'x' S2     // A
S2 := 'A' S1 | 'x' S3     // Ax
S3 := 'A' S4 | 'x' S3     // Axx+
S4 := 'A' S4 | 'x' S4 | $ // AxxA

计算长度为n的匹配字符串的数量

S0(n) = S1(n-1) + (L-1)*S0(n-1); S0(0) = 0
S1(n) = S1(n-1) + (L-1)*S2(n-1); S1(0) = 0
S2(n) = S1(n-1) + (L-1)*S3(n-1); S2(0) = 0
S3(n) = S4(n-1) + (L-1)*S3(n-1); S3(0) = 0
S4(n) = S4(n-1) + (L-1)*S4(n-1); S4(0) = 1

尝试将S0(n)简化为nL会得到一个很长的表达式,因此按原样计算递归函数将是最简单的。

对于非常大的n,它可以表示为矩阵表达式,并可以有效地进行计算。

                                   n
              [L-1  1   0   0   0 ]
              [ 0   1  L-1  0   0 ]              T
[0 0 0 0 1] × [ 0   1   0  L-1  0 ] × [1 0 0 0 0]
              [ 0   0   0  L-1  1 ]
              [ 0   0   0   0   L ]

在JavaScript中:

function f(n, L) {
  var S0 = 0, S1 = 0, S2 = 0, S3 = 0, S4 = 1;
  var S1_tmp;
  while (n-- > 0) {
    S0 = S1 + (L - 1) * S0;
    S1_tmp = S1 + (L - 1) * S2;
    S2 = S1 + (L - 1) * S3;
    S3 = S4 + (L - 1) * S3;
    S4 = S4 + (L - 1) * S4;
    S1 = S1_tmp;
  }
  return S0;
}

var $tbody = $('#resulttable > tbody');
for (var L = 2; L <= 4; L++) {
  for (var n = 4; n <= 10; n++) {
    $('<tr>').append([
      $('<td>').text(n),
      $('<td>').text(L),
      $('<td>').text(f(n,L))
    ]).appendTo($tbody);
  }
}
#resulttable td {
  text-align: right;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="resulttable">
<thead>
<tr>
<th>N</th>
<th>L</th>
<th>matches</th>
</tr>
</thead>
<tbody>
</tbody>
</table>