Оказывается, вы можете сделать это с помощью регулярного выражения ванили. Это просто не красиво.
^((cc|bb)*((bc|cb)(bb|cc)*(bc|cb))*)*(b|c((cc|bb)*((bc|cb)(bb|cc)*(bc|cb))*)*b((cc|bb)*((bc|cb)(bb|cc)*(bc|cb))*)*c)((cc|bb)*((bc|cb)(bb|cc)*(bc|cb))*)*$
Чтобы понять регулярное выражение, нарисуйте DFA с четырьмя состояниями, расположенными в квадрате, связанном вперед и назад по периметру квадрата. Горизонтальные ссылки представляют потребление B, в то время как вертикальные ссылки представляют потребление C. В верхнем левом углу находится начальное состояние, представляющее наличие четного числа Cs и четного числа Bs. В верхнем правом углу находится состояние принятия, достигаемое путем использования B. Нижние состояния достигаются из верхних состояний (и наоборот) путем использования C. Теперь мы можем сделать любое количество переходов, которые сохраняют паритет наших C и Bs, и мы вернемся в исходное состояние. Затем мы потребляем B, приводя нас в наше состояние принятия. Тогда оттуда, пока мы поддерживаем паритеты, у нас все хорошо. Два C поддерживают паритет, как и два B. Это бит (cc|bb)*
.
Но вы также можете сохранить паритет, перейдя в противоположный угол (через C и B в любом порядке), выполнив столько BB / CC, сколько захотите, а затем вернувшись в тот угол, в котором вы были (опять же, в любом случае). ). Вот этот бит: ((bc|cb)(bb|cc)*(bc|cb))*
Итак, у нас есть ((cc|bb)*((bc|cb)(bb|cc)*(bc|cb))*)*
, представляющий собой набор переходов, которые возвращают нас туда, где мы начали (назовем это noop). Вы можете сделать свой нечетный переход B на вершине, в этом случае подойдет b
, или внизу, и в этом случае вам нужно спуститься на дно с помощью c
, сделать еще один noop, затем сделать ваш b
, затем еще один noop, затем c
обратно наверх.
Следует отметить, что вы всегда можете взять два регулярных выражения и сгенерировать регулярное выражение, которое соответствует только строкам, совпадающим с обоими выражениями.