Заменить в строке на основе функции

Итак, для ввода:

accessibility,random good bye 

Я хочу вывод:

 a11y,r4m g2d bye 

Поэтому, в основном, я должен сокращать все слова длины, большие или равные 4, в следующем формате: first_letter + length_of_all_letters_in_between + last_letter

Я пытаюсь сделать это:

 re.sub(r"([A-Za-z])([A-Za-z]{2,})([A-Za-z])", r"\1" + str(len(r"\2")) + r"\3", s) 

Но это не работает. В JS я бы легко сделал:

 str.replace(/([A-Za-z])([A-Za-z]{2,})([A-Za-z])/g, function(m, $1, $2, $3){ return $1 + $2.length + $3; }); 

Как мне сделать то же самое в Python?

EDIT: Я не могу позволить себе потерять какие-либо знаки препинания в оригинальной строке.

Проблема, с которой вы сталкиваетесь, заключается в том, что len(r'\2') всегда 2 , а не длина второй группы захвата в вашем регулярном выражении. Вы можете использовать выражение lambda для создания функции, которая работает так же, как код, который вы будете использовать в JavaScript:

 re.sub(r"([A-Za-z])([A-Za-z]{2,})([A-Za-z])", lambda m: m.group(1) + str(len(m.group(2)) + m.group(3), s) 

Аргумент m для лямбда является объектом соответствия, а вызовы его group методу эквивалентны обратным ссылкам, которые вы использовали ранее.

Может быть проще просто использовать простой шаблон совпадения слов без захвата групп ( group() все равно можно вызвать без аргумента, чтобы получить весь согласованный текст):

 re.sub(r'\w{4,}', lambda m: m.group()[0] + str(len(m.group())-2) + m.group()[-1], s) 

То, что вы делаете в JavaScript, конечно, правильно, вы передаете анонимную функцию. То, что вы делаете в Python, – это передать постоянное выражение («\ 12 \ 3», так как len(r"\2") оценивается перед вызовом функции), это не функция, которую можно оценить для каждого соответствия!

Хотя анонимные функции в Python не так полезны, как в JS, они выполняют эту работу здесь:

 >>> import re >>> re.sub(r"([A-Za-z])([A-Za-z]{2,})([A-Za-z])", lambda m: "{}{}{}".format(m.group(1), len(m.group(2)), m.group(3)), "accessability, random good bye") 'a11y, r4m g2d bye' 

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

 tmp, out = "","" for ch in s: if ch.isspace() or ch in {",", "."}: out += "{}{}{}{}".format(tmp[0], len(tmp) - 2, tmp[-1], ch) if len(tmp) > 3 else tmp + ch tmp = "" else: tmp += ch out += "{}{}{}".format(tmp[0], len(tmp) - 2, tmp[-1]) if len(tmp) > 3 else tmp print(out) a11y,r4m g2d bye 

Если вам нужны только альфа-символы, используйте str.isalpha:

 tmp, out = "", "" for ch in s: if not ch.isalpha(): out += "{}{}{}{}".format(tmp[0], len(tmp) - 2, tmp[-1], ch) if len(tmp) > 3 else tmp + ch tmp = "" else: tmp += ch out += "{}{}{}".format(tmp[0], len(tmp) - 2, tmp[-1]) if len(tmp) > 3 else tmp print(out) a11y,r4m g2d bye 

Логика одинакова для обоих, это то, что мы проверяем на то, что отличается, если not ch.isalpha() False, мы обнаружили not ch.isalpha() символ, поэтому нам нужно обработать строку tmp и добавить ее к выходной строке. if len(tmp) не больше 3 согласно требованию, мы просто добавляем строку tmp плюс текущий символ в нашу строку.

Нам нужен финальный out += "{}{}{} вне цикла, чтобы поймать, когда строка не заканчивается запятой, пробелом и т. Д. Если строка закончилась не-альфой, мы добавили бы пустую строку так что это не имело бы никакого значения для выхода.

Он сохранит знаки препинания и пробелы:

  s = "accessibility,random good bye !! foobar?" def func(s): tmp, out = "", "" for ch in s: if not ch.isalpha(): out += "{}{}{}{}".format(tmp[0], len(tmp) - 2, tmp[-1], ch) if len(tmp) > 3 else tmp + ch tmp = "" else: tmp += ch return "{}{}{}".format(tmp[0], len(tmp) - 2, tmp[-1]) if len(tmp) > 3 else tmp print(func(s,3)) a11y,r4m g2d bye !! f4r? 

Будь проще…

 >>> s = "accessibility,random good bye" >>> re.sub(r'\B[A-Za-z]{2,}\B', lambda x: str(len(x.group())), s) 'a11y,r4m g2d bye' 

\B который соответствует двум символам слов или двум символам, отличным от слов, помогает совместить все символы, кроме первого и последнего.

В качестве альтернативного точного способа вы можете использовать отдельную функцию для re.sub и использовать простое регулярное выражение r"(\b[a-zA-Z]+\b)" .

 >>> def replacer(x): ... g=x.group(0) ... if len(g)>3: ... return '{}{}{}'.format(g[0],len(g)-2,g[-1]) ... else : ... return g ... >>> re.sub(r"(\b[a-zA-Z]+\b)", replacer, s) 'a11y,r4m g2d bye' 

Также как пифонический и общий способ, чтобы получить замененные слова в списке, вы можете использовать понимание списка с помощью re.finditer :

 >>> from operator import sub >>> rep=['{}{}{}'.format(i.group(0)[0],abs(sub(*i.span()))-2,i.group(0)[-1]) if len(i.group(0))>3 else i.group(0) for i in re.finditer(r'(\w+)',s)] >>> rep ['a11y', 'r4m', 'g2d', 'bye'] 

re.finditer вернет генератор, содержащий все matchobjects после чего вы можете перебрать его и получить начало и конец matchobject s с помощью метода span() .

Использование регулярного выражения и понимания:

 import re s = "accessibility,random good bye" print "".join(w[0]+str(len(w)-2)+w[-1] if len(w) > 3 else w for w in re.split("(\W)", s)) 

дает:

 a11y,r4m g2d bye 

Посмотрите следующий код

 sentence = "accessibility,random good bye" sentence = sentence.replace(',', " ") sentence_list = sentence.split(" ") for item in sentence_list: if len(item) >= 4: print item[0]+str(len(item[1:len(item)-1]))+item[len(item)-1] 

Единственное, что вам нужно позаботиться о запятой и других символах пунктуации.