2013-02-10 1 views
2

Я использую модуль re в Python (3) и хочу заменить (re.sub (regex , заменить строку)) строку в следующем форматеРегулярное выражение, которое принимает <...> как один элемент в «foo bar <hello world> и т. Д.» (Цель: Простая музыка/синтаксический анализ lilypond)

"foo <bar e word> f ga <foo b>" 

к

"#foo <bar e word> #f #ga <foo b>" 

или даже

"#foo #<bar e word> #f #ga #<foo b>" 

Но я не могу изолировать отдельные слова от границ слов в пределах конструкции < ...>.

Справка будет приятной!

PS 1

Вся история музыкальной один: У меня есть строки в формате LilyPond (или лучше, подмножество очень простой формат ядра, просто отмечает и длительности) и хотите конвертировать их к парам python int (продолжительность), списку (строк основного тона). Производительность не важна, поэтому я могу конвертировать их туда и обратно, перебирать списки python, разделять строки и присоединяться к ним снова и т. Д. Но для вышеупомянутой проблемы я не нашел ответа.

Источник Строка

"c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1" 

должно привести к

[ 
(4, ["c'"]), 
(8, ["d"]), 
(16, ["e'", "g'"]), 
(4, ["fis'"]), 
(0, ["a,,"]), 
(0, ["g", "b'"]), 
(1, ["c''"]), 
] 

основной формат строки + Номер так: e4 bes16

  • Элемент списка
  • строка может состоять из нескольких, по крайней мере одного, [a-zA-Z] символов
  • за строкой следует ноль или более цифр: e bes g4 c16
  • строка за ней следует ноль или более 'или (не комбинируется): e' bes, f '' '2 g ,, 4
  • строка может быть заменена на список строк, список лимитеры <>: 4 число приходит позади>, нет места позволили

PS 2

Цель состоит не в создании парсера Lilypond. Это действительно только для очень коротких фрагментов без дополнительной функциональности, без расширений для вставки заметок. Если это не сработает, я бы пошел на другой формат (упрощенный), такой как ABC. Так что все, что связано с Lilypond («Запустите его через lilypond, пусть он выдаст музыкальные данные в Scheme, проанализируйте это»), или его инструментальная цепочка, безусловно, НЕ является ответом на этот вопрос. Пакет даже не установлен.

ответ

1

Ваш первый вопрос можно ответить так:

>>> import re 
>>> t = "foo <bar e word> f ga <foo b>" 
>>> t2 = re.sub(r"(^|\s+)(?![^<>]*?>)", " #", t).lstrip() 
>>> t2 
'#foo #<bar e word> #f #ga #<foo b>' 

lstrip() Я добавил, чтобы удалить единое пространство, которое происходит до результата этого шаблона.Если вы хотите перейти к первому варианту, вы можете просто заменить #< на <.

Ваш второй вопрос может быть решен следующим образом, хотя вам может потребоваться подумать о , в списке, например ['g,', "b'"]. Должна быть запятая из вашей струны или нет? Там может быть более быстрый способ. Следующее - просто доказательство концепции. Понятие окончательного элемента может быть заменено списком, хотя это будет довольно сложно.

>>> s = "c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1" 
>>> q2 = re.compile(r"(?:<)\s*[^>]*\s*(?:>)\d*|(?<!<)[^\d\s<>]+\d+|(?<!<)[^\d\s<>]+") 
>>> s2 = q2.findall(s) 
>>> s3 = [re.sub(r"\s*[><]\s*", '', x) for x in s2] 
>>> s4 = [y.split() if ' ' in y else y for y in s3] 
>>> s4 
["c'4", 'd8', ["e'", "g'16"], "fis'4", 'a,,', ['g,', "b'"], "c''1"] 
>>> q3 = re.compile(r"([^\d]+)(\d*)") 
>>> s = [] 
>>> for item in s4: 
    if type(item) == list: 
      lis = [] 
      for elem in item: 
        lis.append(q3.search(elem).group(1)) 
        if q3.search(elem).group(2) != '': 
          num = q3.search(elem).group(2) 
      if q3.search(elem).group(2) != '': 
        s.append((num, lis)) 
      else: 
        s.append((0, lis)) 
    else: 
      if q3.search(item).group(2) != '': 
        s.append((q3.search(item).group(2), [q3.search(item).group(1)])) 
      else: 
        s.append((0, [q3.search(item).group(1)])) 


>>> s 
[('4', ["c'"]), ('8', ['d']), ('16', ["e'", "g'"]), ('4', ["fis'"]), (0, ['a,,']), (0, ['g,', "b'"]), ('1', ["c''"])] 
+0

Я обновил этот ответ с быстрым и грязным решением первого постскриптума. –

+0

принято как ответ, потому что использование только стандартных библиотек python – nilsge

2

Я знаю, что вы не ищете общий синтаксический анализ, но pyparsing делает этот процесс очень прост. Ваш формат казался очень похожим на chemical formula parser, который я написал как один из самых ранних примеров пирафинга.

Вот ваша проблема реализуется с использованием Pyparsing:

from pyparsing import (Suppress,Word,alphas,nums,Combine,Optional,Regex,Group, 
         OneOrMore) 

""" 
List item 
-the string can consist of multiple, at least one, [a-zA-Z] chars 
-the string is followed by zero or more digits: e bes g4 c16 
-the string is followed by zero or more ' or , (not combined): 
    e' bes, f'''2 g,,4 
-the string can be substituted by a list of strings, list limiters are <>; 
    the number comes behind the >, no space allowed 
""" 

LT,GT = map(Suppress,"<>") 

integer = Word(nums).setParseAction(lambda t:int(t[0])) 

note = Combine(Word(alphas) + Optional(Word(',') | Word("'"))) 
# or equivalent using Regex class 
# note = Regex(r"[a-zA-Z]+('+|,+)?") 

# define the list format of one or more notes within '<>'s 
note_list = Group(LT + OneOrMore(note) + GT) 

# each item is a note_list or a note, optionally followed by an integer; if 
# no integer is given, default to 0 
item = (note_list | Group(note)) + Optional(integer, default=0) 

# reformat the parsed data as a (number, note_or_note_list) tuple 
item.setParseAction(lambda t: (t[1],t[0].asList())) 

source = "c'4 d8 < e' g' >16 fis'4 a,, <g, b'> c''1" 
print OneOrMore(item).parseString(source) 

С этим выходом:

[(4, ["c'"]), (8, ['d']), (16, ["e'", "g'"]), (4, ["fis'"]), (0, ['a,,']), 
(0, ['g,', "b'"]), (1, ["c''"])] 
+1

это очень хорошо и чисто. Тем не менее, Pyparsing добавит дополнительную зависимость. Для действительно небольшой вспомогательной функции фрагмента это слишком много. – nilsge