Вычисление размера возможностей UID
в спецификации DICOM, UID определяется:9.1 ЮИД правила кодирования. Другими словами, действительны следующие идентификаторы DICOM UIDs:
- "1.2.3.4.5"
- "1.3.6.1.4.35045.103501438824148998807202626810206788999"
- "1.2.826.0.1.3680043.2.1143.5028470438645158236649541857909059554"
при следующих незаконны Дицом UIDs:
- ".1.2.3.4.5"
- "1..2.3.4.5"
- "1.2.3.4.5."
- "1.2.3.4.05"
- "12345"
- "1.2.826.0.1.3680043.2.1143.50284704386451582366495418579090595540"
поэтому я знаю, что строка не более 64 байт и должна соответствовать следующему регулярному выражению [0-9.]+
. Однако это регулярное выражение действительно суперсет, так как их намного меньше, чем (10+1)^64 (=4457915684525902395869512133369841539490161434991526715513934826241L)
возможностей.
как бы точно вычисляется количество возможностей соблюдать правила DICOM UID ?
чтение правила корня / суффикса организации ясно указывает на то, что мне нужна хотя бы одна точка ('.'). В этом случае комбинация составляет не менее 3 байт (char) в виде: [0-9].[0-9]. В этом случае есть 10x10=100
возможности для UID длины 3.
глядя на первый ответ, кажется, что-то неясное о:
первая цифра каждого компонент не должен быть равен нулю, если компонент представляет собой одну цифру.
это значит, что:
- "0.0" является действительным
- "00.0" или "1.01" являются недопустимыми
таким образом, я бы сказал, что правильным выражением было бы:
(([1-9][0-9]*)|0)(.([1-9][0-9]*|0))+
используя простой код C, я мог бы найти:
- f (0) = 0
- f (1) = 0
- f (2) = 0
- f (3) = 100
- f (4) = 1800
- f (5) = 27100
- f (6) = 369000
- f (7) = 4753000
- f (8) = 59049000
проверка корневой части UID выходит за рамки этого вопроса. Второй шаг проверки может позаботиться об отклонении некоторого OID, который невозможно зарегистрировать (некоторые люди упоминают ограничение на первую и вторую дугу, например). Для простоты мы примем все возможные (допустимые) корневые UID.
2 ответов
в то время как мой другой ответ хорошо заботится об этом конкретном приложении, вот более общий подход. Он заботится о ситуациях, когда у вас есть другое регулярное выражение, описывающее рассматриваемый язык. Он также позволяет значительно увеличить длину строки, так как для этого требуется только O (log n) арифметические операции для вычисления количества комбинаций для строк длиной до n. В этом случае количество строк растет так быстро, что стоимость из этих арифметических операций резко вырастет, но это может быть не так для других, в противном случае подобных ситуаций.
построить конечный автомат
начать с обычный описание выражения вашего языка. Перевести регулярное выражение в конечный автомат. В вашем случае регулярное выражение может быть задано как
(([1-9][0-9]*)|0)(\.([1-9][0-9]*|0))+
автомат может выглядеть как это:
исключить ε-переходы
этот автомат обычно содержит ε-переходы (т. е. переходы состояний, которые не соответствуют никакому входному символу). Удалите их, чтобы один переход соответствовал одному символу ввода. Затем добавьте ε-переход в принимающее состояние(состояния). Если принимающие состояния имеют другие исходящие переходы, не добавляйте к ним ε-петли, а вместо этого добавьте ε-переход в принимающее состояние без исходящих ребер, а затем добавьте цикл к этому. Это можно рассматривать как заполнение входа ε на его конце, не допуская ε в середине. Взятые вместе, это преобразование гарантирует, что выполнение точно n переходы состояния соответствуют обработке ввода n символов или меньше. Модифицированный автомат может выглядеть так:
обратите внимание, что оба конструкция первого автомата из регулярного выражения и устранение ε-переходов может выполняться автоматически (и, возможно, даже в один шаг. Полученные автоматы могут быть более сложными, чем то, что я построил здесь вручную, но принцип тот же.
обеспечение уникальных путей
вам не нужно делать автомат детерминированные в том смысле, что для каждой комбинации исходного состояния и входного символа есть только одно целевое состояние. Это не так в моем вручную построенном. Но вы должны убедиться, что каждый полный ввод имеет только один возможный путь к принимающему состоянию, так как вы по существу будете считать пути. делает автомат детерминированным также обеспечит это более слабое свойство, поэтому перейдите к этому, если вы не можете обеспечить уникальные пути без этого. В моем примере длина каждого компонента четко определяет, какой путь использовать, поэтому я не сделал этого детерминированный. Но я включил пример с детерминированным подходом в конце этого поста.
построить матрицу перехода
Далее запишите матрицу перехода. Свяжите строки и столбцы с вашими состояниями (в порядке 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), что означает только один способ закончить в начале state) и суммировать все записи, соответствующие принимающим состояниям (только последний в этом случае). Нижний левый элемент 64-й степени этой матрицы -
1474472506836676237371358967075549167865631190000000000000000000000
что подтверждает мой ответ.
вычислить мощности матрицы эффективно
чтобы фактически вычислить 64-ю степень этой матрицы, самым простым подходом было бы повторное возведение в квадрат: После возведения в квадрат матрицы 6 раз у вас есть показатель 26 = 64. Если в некоторых другой сценарий ваш показатель (т. е. максимальная длина строки) не является степенью двух, вы все равно можете выполнить возведение в квадрат путем умножения соответствующих квадратов в соответствии с битовым шаблоном экспоненты. Вот что заставляет этот подход принимать 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⎠
и смогл суммировать последние 3 элемента первого столбца своего 64-я степень, чтобы получить тот же результат, что и выше.
один компонент
начните с поиска способов формирования одного компонента. Соответствующее регулярное выражение для одного компонента
0|[1-9][0-9]*
таким образом, это либо ноль, либо ненулевая цифра, за которой следует произвольное множество нулевых цифр. (Сначала я пропустил возможный случай единственного нуля, но комментарий Малата заставил меня осознать это.) Если общая длина такого компонента должна быть 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) - это количество способов написать UID длины n. Тогда у вас есть
f(n) = h(n) + sum h(i) * f(n-i-1) for i from 1 to n-2
первый член описывает случай одного компонента, в то время как сумма заботится о случае, когда она состоит из более чем одного компонента. В этом случае у вас есть первый компонент length я, затем точка, которая учитывает для -1 в Формуле, а затем оставшиеся цифры образуют один или несколько компонентов, которые выражаются рекурсивным использованием f.
два или более компонентов
как указано в комментарии cneller, часть раздела 9 перед подразделом 9.1 указывает, что должно быть по крайней мере два компонента. Таким образом, правильное регулярное выражение было бы больше похоже на
(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
если суммировать все g(n) для n от 3 (минимально возможная длина UID) до 64 вы получаете количество возможных uid как
1474472506836676237371358967075549167865631190000000000000000000000
или около 1.5e66
. Что значительно меньше, чем 4.5e66
вы получаете от вашего вычисление, с точки зрения абсолютной разницы, хотя это определенно на том же порядке величины. Кстати, ваша оценка явно не упоминает UIDs короче 64, но вы всегда можете рассмотреть возможность заполнения их точками в вашей настройке. Я сделал вычисление, используя несколько строк кода Python:
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)