根据DICOM规范,UID由以下内容定义:9.1 UID Encoding Rules。换句话说,以下是有效的DICOM UID:
以下是非法的DICOM UID:
因此我知道该字符串最多为64个字节,并且应该与以下正则表达式[0-9\.]+
匹配。然而,这个正则表达式实际上是超集,因为有很多(10+1)^64 (=4457915684525902395869512133369841539490161434991526715513934826241L)
种可能性。
如何精确计算尊重DICOM UID规则的可能性数量?
读取组织根/后缀规则清楚地表明我至少需要一个点('。')。在这种情况下,组合至少为3个字节(字符),格式为:[0-9]。[0-9]。在这种情况下,长度为3的UID有10x10=100
种可能性。
看一下第一个答案,似乎有些不清楚的地方:
每个组件的第一个数字不得为零,除非 组件是一个数字。
这意味着:
因此,我会说一个恰当的表达方式:
(([1-9][0-9]*)|0)(\.([1-9][0-9]*|0))+
使用简单的C代码,我可以找到:
Root UID部分的验证超出了本问题的范围。第二个验证步骤可以处理拒绝一些不可能注册的OID(例如,有些人提到对第一和第二弧的限制)。为简单起见,我们将接受所有可能的(有效的)Root UID。
答案 0 :(得分:9)
首先寻找形成单个组件的方法。单个组件的相应正则表达式是
0|[1-9][0-9]*
所以它是零或非零数字后跟任意多个零数字。 (我最初错过了可能唯一的零案例,但是malat的评论让我意识到这一点。)如果这样一个组件的总长度是 n ,那么你写 h ( n )表示形成这样一个长度为 n 的组件的方式的数量,然后你可以计算 h ( n )为
h(n) = if n = 1 then 10 else 9 * 10^(n - 1)
其中 n = 1的情况允许所有可能的数字,而其他情况确保第一个数字不为零。
第9.1小节仅写道UID是一串点分隔数字组件,如上所述。所以在正则表达式中
(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))*
假设 f ( n )是写入长度 n 的UID的方法数。那你有
f(n) = h(n) + sum h(i) * f(n-i-1) for i from 1 to n-2
第一个术语描述了单个组件的情况,而总和则考虑了由多个组件组成的情况。在这种情况下,你有一个长度为 i 的第一个组件,然后是一个代表公式中-1的点,然后剩下的数字组成一个或多个组件,通过递归使用˚F
正如cneller的评论所指出的那样,第9.1节之前的第9节中的部分表明必须至少有两个组成部分。所以正确的正则表达式更像是
(0|[1-9][0-9]*)(\.(0|[1-9][0-9]*))+
在末尾有一个+
,表示我们希望至少重复一次带括号的表达式。为此得出一个表达式只意味着在 f 的定义中省略了仅一个组件的情况:
g(n) = sum h(i) * f(n-i-1) for i from 1 to n-2
如果您将 n 的所有 g ( n )从3(最小可能的UID长度)加到64,那么您将获得该数字可能的UID为
1474472506836676237371358967075549167865631190000000000000000000000
或大约1.5e66
。从绝对差异的角度来看,这比计算得到的4.5e66
小得多,尽管它绝对是相同的数量级。顺便说一句,你的估计并没有明确提到短于64的UID,但你总是可以考虑在你的设置中用点填充它们。我使用a few lines of Python code进行了计算:
f = [0]
g = [0]
h = [0, 10] + [9 * (10**(n-1)) for n in range(2, 65)]
s = 0
for n in range(1, 65):
x = 0
if n >= 3:
for i in range(1, n - 1):
x += h[i] * f[n-i-1]
g.append(x)
f.append(x + h[n])
s += x
print(h)
print(f)
print(g)
print(s)
答案 1 :(得分:9)
虽然我的其他答案很好地照顾了这个特定的应用程序,但这是一个更通用的方法。它会处理您使用不同的正则表达式来描述相关语言的情况。它还允许相当长的字符串长度,因为它只需要O(log n )算术运算来计算长度最大为 n 的字符串的组合数。在这种情况下,字符串的数量增长得如此之快,以至于这些算术运算的成本将急剧增长,但对于其他类似的情况可能并非如此。
从相关语言的正则表达式描述开始。将该正则表达式转换为有限状态自动机。在您的情况下,正则表达式可以作为
给出(([1-9][0-9]*)|0)(\.([1-9][0-9]*|0))+
自动机可能如下所示:
该自动机通常包含ε-转换(即,不对应于任何输入字符的状态转换)。删除它们,以便一个转换对应于一个输入字符。然后将ε-转换添加到接受状态。如果接受状态具有其他传出转换,则不要向它们添加ε-循环,而是将ε-转换添加到没有传出边缘的接受状态,然后将循环添加到该状态。这可以看作是在其末端用ε填充输入,而不允许中间的ε。总之,这种转换可确保执行完全 n 状态转换对应于处理 n 字符或更少字符的输入。修改后的自动机可能如下所示:
请注意,the construction of the first automaton from the regular expression和the elimination of ε-transitions都可以自动执行(甚至可能in a single step。生成的自动机可能比我手动构建的更复杂,但原则是相同。
对于源状态和输入字符的每个组合,只有一个目标状态,您不必创建自动机deterministic。我手动构建的情况也不是这样。但是你必须确保每个完整的输入只有一条通往接受状态的可能路径,因为你基本上是在计算路径。 Making the automaton deterministic也会确保这个较弱的属性,所以除非你可以确保没有这个的独特路径。在我的例子中,每个组件的长度明确规定了使用哪条路径,所以我没有确定它。但是我在本文末尾列举了一个确定性方法的例子。
接下来,记下转换矩阵。将行和列与您的状态相关联(在我的示例中按顺序 a,b,c,d,e,f )。对于自动机中的每个箭头,请在与源状态关联的列中以及与该箭头的目标状态关联的行中写入该箭头标签中包含的字符数。
⎛ 0 0 0 0 0 0⎞
⎜ 9 10 0 0 0 0⎟
⎜10 10 0 10 10 0⎟
⎜ 0 0 1 0 0 0⎟
⎜ 0 0 0 9 10 0⎟
⎝ 0 0 0 10 10 1⎠
现在将此矩阵与列向量一起应用具有以下含义:如果在输入向量中编码到达给定状态的可能方式的数量,则输出向量将为您提供稍后转换的方式的数量。取该矩阵的64次幂,集中于第一列(因为ste start情境被编码为(1,0,0,0,0,0),意味着只有一种方式结束于开始状态)并总结所有与接受状态相对应的条目(在这种情况下只是最后一个)。该矩阵的64次幂的左下角元素是
1474472506836676237371358967075549167865631190000000000000000000000
这证实了我的另一个答案。
为了实际计算该矩阵的第64次方,最简单的方法是重复平方:将矩阵平方6次后,指数为2 6 = 64.如果在其他方面方案你的指数(即最大字符串长度)不是2的幂,你仍然可以通过根据指数的位模式乘以相关的方格来执行exponentiation by squaring。这使得这种方法采用O(log n )算术运算来计算字符串长度 n 的结果,假设固定数量的状态,因此每个矩阵的固定成本平方。
如果你使用通常的powerset构造使我的自动机确定性,你最终会
并将状态排序为 a , bc , c , d , cf , cef , f 可以获得转换矩阵
⎛ 0 0 0 0 0 0 0⎞
⎜ 9 10 0 0 0 0 0⎟
⎜ 1 0 0 0 0 0 0⎟
⎜ 0 1 1 0 1 1 0⎟
⎜ 0 0 0 1 0 0 0⎟
⎜ 0 0 0 9 0 10 0⎟
⎝ 0 0 0 0 1 1 1⎠
并且可以将其第64个幂的第一列的最后三个元素相加,以获得与上面相同的结果。