Разделение числа на разряды с помощью регулярного выражения

Разделение числа на разряды с помощью регулярного выражения

Интуитивно понятно как решить эту задачу вручную, на бумаге. Нужно просто идти справа налево и через каждые три цифры ставить запятую.

А можно ли сформулировать это решение на языке регулярных выражений?

Сразу же возникает затык - регулярные выражения не умеют ходить справа налево. Значит, нужно переформулировать решение так, чтобы задача решалась в обычном для регулярных выражений направлении - слева направо.

Но так вот, запросто, идти слева и отсчитывать тройки цифр не получится. Ведь разряды по определению отсчитываются справа, супротив математики не попрешь. Значит, идти, как велят регулярные выражения, нужно слева, а разряды считать - справа.

Что эта фигня значит?

Щас разберемся. Для простоты возьмем число 1234.

Идем слева направо. Первая цифра слева - 1. Следует ли после этой цифры поставить запятую? Как понять, является ли место за цифрой 1 местом разделения разрядов?

Посчитаем цифры справа, от конца числа. От конца числа насчитывается три цифры до места после цифры 1. Три цифры - это разряд, значит, после цифры 1 нужно поставить запятую:

Здесь разряд обозначен квадратными скобками.

Но высказывание "от конца числа насчитывается три цифры до места после цифры 1" направлено справа налево, а для написания регулярного выражения нам нужно высказывание, направленное слева направо. Для получения такого высказывания просто развернем предыдущее высказывание задом наперед.

Как несложно догадаться, результатом будет:

"от места после цифры 1 насчитывается три цифры до конца числа".

Значит, если от места после цифры 1 насчитывается три цифры до конца числа, значит, место после цифры 1 является местом разделения разрядов.

Разберем это высказывание по частям и запишем на языке регулярных выражений.

Видно, что высказывание состоит из следующих частей:

1. "от места после цифры 1" 2. "насчитывается три цифры" 3. "до конца числа"

"от места после цифры 1" - это, очевидно, заглядывание вперед. На языке регулярных выражений заглядывание вперед записывается так:

Впереди "насчитываются три цифры". Три цифры записываются так:

Плюс, впереди находится "конец числа". Конец числа записывается так:

Тут требуется пояснение. Дословно эта конструкция означает "место, после которого нет цифры", и, вообще говоря, эта конструкция не является концом числа в общем случае. Но в нашем конкретном случае эта конструкция подходит. За подробностями отсылаю к Фридлу.

Соединим все три части в одно выражение:

Получившееся выражение находит место разделения разрядов. Ну, а вставить на это место запятую проще простого, для этого у нас есть оператор s/// - стандартный оператор замены.

Однострочник для проверки:

$ perl -e '$a=1234; $a=

Результатом выполнения будет 1,234.

Регулярное выражение, однако, пока еще не закончено. Если вместо числа 1234 взять число подлиннее, например, 1234567890, то результатом выполнения будет 1234567,890.

А где остальные запятые? Почему выделен только один разряд?

Легко догадаться, что один разряд выделен потому, что регулярное выражение находит ровно три цифры - \d . Три цифры соответствуют одному разряду. Чтобы выделить все разряды, нужно найти все имеющиеся тройки цифр.

Высказывание "все имеющиеся" - это квантификатор +. Соответственно, высказывание "все имеющиеся тройки цифр" записывается так:

А полностью регулярное выражение теперь будет выглядеть так:

Запомним это выражение, оно пригодится дальше.

$ perl -e '$a=1234567890; $a=

Опаньки. Результат 1,234567890.

В первую очередь возникает мысль о том, что жадный квантификатор + захватил все возможные тройки. Это действительно так, но причина, все-таки, не в жадности квантификатора. Причина в том, что по умолчанию регулярное выражение ищет только одно совпадение из всех возможных. Ну, а то, что это совпадение из-за жадности квантификатора оказывается максимально длинным, это уже не существенно.

А какие вообще есть возможные совпадения? Давайте разберемся. Запишем в квадратных скобках тройки цифр:

1, [ 234 ][ 567 ][ 890 ]

Поскольку квантификатор + является жадным, то выражение (\d)+ совпадает со всеми тройками цифр. Обозначим совпадение круглыми скобками:

1. 1, ( [ 234 ][ 567 ][ 890 ] )

Вот это первое максимально длинное совпадение и отделяется запятой. Но ведь есть и другие совпадения, менее длинные, которые тоже нужно отделить запятыми:

2. 1 [ 234 ] , ( [ 567 ][ 890 ] ) 3. 1 [ 234 ][ 567 ] , ( [ 890 ] )

Для этого надо заставить регулярное выражение не останавливаться после нахождения первого совпадения и найти два оставшихся возможных совпадения.

Чтобы регулярное выражение искало все возможные совпадения, нужно к оператору s/// добавить модификатор g.

$ perl -e '$a=1234567890; $a=

Ага, теперь результатом выполнения будет 1,234,567,890.

Это - правильный результат. Победа? Увы, нет.

Проведем контрольную проверку с числом 123456789:

$ perl -e '$a=123456789; $a=

Результатом выполнения будет ,123,456,789.

Как видите, впереди числа появилась лишняя запятая. Откуда она взялась? Она взялась из-за еще одного возможного совпадения, которого не было в числе 1234567890, но которое есть в числе 123456789:

Тут все число состоит из троек цифр. Поэтому регулярное выражение дает совпадение со всем числом целиком.

Формально все правильно, но здравый смысл подсказывает, что слева от числа ставить запятую бессмысленно. А почему, собственно? Очевидно, потому, что запятыми мы разделяем цифры, а слева от этой лишней запятой ни одной цифры нет.

Значит, в регулярное выражение нужно добавить условие, требующее ставить запятые только в тех местах, слева от которых цифра есть.

Высказывание "место, слева от которого есть цифра" состоит из следующих частей:

1. "место, слева от которого" 2. "есть цифра"

"место, слева от которого" - это, очевидно, заглядывание назад. На языке регулярных выражений заглядывание назад записывается так:

Соединим эти две части:

Теперь присоединим получившееся к ранее составленному (и запомненному) регулярному выражению. Присоединяем слева, ведь наличие цифры мы проверяем именно слева:

$ perl -e '$a=123456789; $a=

Отлично, получился результат 123,456,789.

Все, окончательное решение найдено. Регулярное выражение для разделения числа на разряды выглядит так: