Скажи у меня есть строка как

'1 - hello.mp3'
'22 - hellox.mp3'
'223 - hellox.mp3'
'hellox.mp3'

Я надеюсь, что выход будет

'001 - hello.mp3'
'022 - hellox.mp3'
'223 - hellox.mp3'
'hellox.mp3'

То есть, если начало - число, добавляя 0, чтобы сделать его тремя цифрами.

Есть ли способ достичь с помощью регулярных выражений в Python?

-2
william007 23 Июн 2019 в 13:57

3 ответа

Лучший ответ

Да, регулярные выражения могут сделать это. Используйте re.sub() с функцией обратного вызова:

import re

def pad_number(match):
    number = int(match.group(1))
    return format(number, "03d")

fixed_text = re.sub(r"^(\d+)", pad_number, text)

Шаблон, который я использовал, ^(\d+) соответствует 1 или более цифрам (\d является цифрой, + будет соответствовать по крайней мере один раз, но будет охватывать все последующие цифры), но только в начале строка (^ здесь является якорем начала текста).

Затем для каждого сопоставленного шаблона вызывается функция pad_number(), и строка, которую возвращает эта функция, используется для замены сопоставленного шаблона. Поскольку в шаблоне используется группа захвата (все, что между ( и ) является такой группой), функция может получить доступ к сопоставленным цифрам, вызвав match.group(1).

Функция превращает цифры в целое число, а затем использует функцию format() чтобы превратить это целое число обратно в текст, но на этот раз в 0-значное число шириной 3 символа; это то, что 03 инструкция форматирования говорит format() произвести.

Обратите внимание, что шаблон может соответствовать большему количеству цифр, но ограничение их не имеет большого смысла, если нет строгого верхнего числа, которое вы хотите ограничить (в этот момент вам также необходимо добавить ограничение на следующий символ не является цифрой). Инструкция format(number, "03d") создает число шириной не менее 3 цифры, но может обрабатывать более длинные значения.

Демо-версия :

>>> import re
>>> samples = [
...     '1 - hello.mp3',
...     '22 - hellox.mp3',
...     '223 - hellox.mp3',
...     'hellox.mp3',
... ]
>>> def pad_number(match):
...     number = int(match.group(1))
...     return format(number, "03d")
...
>>> for sample in samples:
...     result = re.sub(r"^(\d+)", pad_number, sample)
...     print(f"{sample!r:20} -> {result!r:20}")
...
'1 - hello.mp3'      -> '001 - hello.mp3'
'22 - hellox.mp3'    -> '022 - hellox.mp3'
'223 - hellox.mp3'   -> '223 - hellox.mp3'
'hellox.mp3'         -> 'hellox.mp3'

Опять же, примите во внимание, что этот метод не содержит особых строк с 4 или более цифрами в начале; вы просто получаете более длинную последовательность цифр:

>>> re.sub(r"^(\d+)", pad_number, "4281 - 4 digits")
'4281 - 4 digits'
>>> re.sub(r"^(\d+)", pad_number, "428117 - 6 digits")
'428117 - 6 digits'

Это произойдет, даже если мы ограничим шаблон \d только тремя цифрами (например, \d{1,3}).

Если вы хотите настроить ширину отступа, вы можете поместить все во вложенную функцию и использовать форматирование строки. На самом деле вам не нужно

import re

def pad_leading_number(text, width):
    def pad_number(match):
        number = int(match.group(1))
        return format(number, f"0{width}d")

    return re.sub(fr"^(\d+)", pad_number, text)

Демо-версия :

>>> pad_leading_number("22 - hellox.mp3", 3)
'022 - hellox.mp3'
>>> pad_leading_number("22 - hellox.mp3", 7)
'0000022 - hellox.mp3'
2
Martijn Pieters 23 Июн 2019 в 11:42

Вы специально запросили регулярное выражение - (см. решение Martijn) - но также можете сделать это с помощью манипуляции со строками:

data = [ '1 - hello.mp3', '22 - hellox.mp3', '223 - hellox.mp3', 'hellox.mp3' ]


def prefixZeroIfStartWithNumber(t):
    """Splits t at first space, if convertable to in, prefixes with three zeros
    and returns it together with remainder of t. Else returns t."""
    spl = t.split(" ",1)
    try:
        k = int(spl[0])
        return f"{k:03} {spl[1]}" # format with zeros, add remainder
    except (ValueError, IndexError): 
        return t

for text in data:
    print(prefixZeroIfStartWithNumber(text))

Выход:

001 - hello.mp3
022 - hellox.mp3
223 - hellox.mp3
hellox.mp3

Альтернативный способ - предположительно, будет быстрее:

def prefixZeroIfStartWithNumber(t):
    number, _, remainder = t.partition(" ")
    try:
        k = int(number)
        return f"{k:03} {remainder}" # format with zeros, add remainder
    except (ValueError, IndexError): 
        return t 

Использование str.partition

0
Patrick Artner 23 Июн 2019 в 13:20

@martijn Pieters дал вам правильный ответ , но вы также можете сделать то же самое проще, запустив это регулярное выражение дважды:

regex = r"^(?=\d\d? )"
replacedby = "0"


^ asserts position at start of a line
Positive Lookahead (?=\d\d? )
\d matches a digit (equal to [0-9])
\d? matches a digit (equal to [0-9]) between zero and one times
Matches SPACE

I use the positive lookahead to match the lines starting with ## - but not including the ## - in the match, then I replace the start of the line with 0

Здесь я покажу вам то же самое в javascript для простоты:

const regex = /^(?=\d\d? )/gm;
const str = `
1 - hello.mp3
22 - hellox.mp3
223 - hellox.mp3
hellox.mp3`;

const subst = `0`;

const result = str.replace(regex, subst).replace(regex, subst);

console.log('Substitution result: ', result);

Посмотрите пример онлайн по этой ссылке и экспортируйте его на python или другие языки:

https://regex101.com/r/jVsY5v/1/

0
YOGO 23 Июн 2019 в 16:32