Проверьте, находится ли позиция в строке в паре определенных символов
В Python, что было бы наиболее эффективным способом выяснить, если позиция в строке находится в паре определенных последовательностей символов?
0--------------16-------------------37---------48--------57
| | | | |
cost=r"a) This costs \$1 but price goes as $x^2$ for \(x\) item(s)."
В строке cost
Я хочу выяснить, если определенная позиция заключена в пару $
или в пределах \(
а также \)
,
Для строки cost
функция is_maths(cost,x)
вернется True
за x
в [37,38,39,48]
и оценить False
для всего остального.
Мотивация состоит в том, чтобы выяснить действительные позиции по латексной математике, также приветствуются любые альтернативные эффективные способы использования Python.
1 ответ
Вам нужно будет проанализировать строку до запрошенной позиции и, если она находится в допустимой паре разделителей среды LaTeX, до закрывающего разделителя, чтобы иметь возможность ответить с True
или же False
, Это потому, что вы должны обработать каждый соответствующий метасимвол (обратную косую черту, доллары и скобки), чтобы определить их влияние.
Я поняла что латекс $...$
а также \(...\)
Разделители среды не могут быть вложенными, поэтому вам не нужно беспокоиться о вложенных выражениях здесь; вам нужно только найти ближайший полный $...$
или же \(...\)
пара.
Вы не можете просто соответствовать буквальному $
или же \(
или же \)
символы, однако, потому что каждому из них может предшествовать произвольное число \
обратные косые. Вместо этого токенизируйте входную строку по обратным слешам, долларам или скобкам, итерируйте по токенам, чтобы отследить, что было найдено в последний раз, чтобы определить их эффект (экранирование следующего символа, а также открывающая и закрывающая математические среды).
Вам не нужно продолжать синтаксический анализ, если вы находитесь за запрошенной позицией и вне раздела математической среды; у вас уже есть ответ, и вы можете вернуться False
рано.
Вот моя реализация такого парсера:
import re
_maths_pairs = {
# keys are opening characters, values matching closing characters
# each is a tuple of char (string), escaped (boolean)
('$', False): ('$', False),
('(', True): (')', True),
}
_tokens = re.compile(r'[\\$()]')
def _tokenize(s):
"""Generator that produces token, pos, prev_pos tuples for s
* token is a single character: a backslash, dollar or parethesis
* pos is the index into s for that token
* prev_pos is te position of the preceding token, or -1 if there
was no preceding token
"""
prev_pos = -1
for match in _tokens.finditer(s):
token, pos = match[0], match.start()
yield token, pos, prev_pos
prev_pos = pos
def is_maths(s, pos):
"""Determines if pos in s is within a LaTeX maths environment"""
expected_closer = None # (char, escaped) if within $...$ or \(...\)
opener_pos = None # position of last opener character
escaped = False # True if the most recent token was an escaping backslash
for token, token_pos, prev_pos in _tokenize(s):
if expected_closer is None and token_pos > pos:
# we are past the desired position, it'll never be within a
# maths environment.
return False
# if there was more text between the current token and the last
# backslash, then that backslash applied to something else.
if escaped and token_pos > prev_pos + 1:
escaped = False
if token == '\\':
# toggle the escaped flag; doubled escapes negate
escaped = not escaped
elif (token, escaped) == expected_closer:
if opener_pos < pos < token_pos:
# position is after the opener, before the closer
# so within a maths environment.
return True
expected_closer = None
elif expected_closer is None and (token, escaped) in _maths_pairs:
expected_closer = _maths_pairs[(token, escaped)]
opener_pos = token_pos
prev_pos = token_pos
return False
Демо-версия:
>>> cost = r'a) This costs \$1 but price goes as $x^2$ for \(x\) item(s).'
>>> is_maths(cost, 0) # should be False
False
>>> is_maths(cost, 16) # should be False, preceding $ is escaped
False
>>> is_maths(cost, 37) # should be True, within $...$
True
>>> is_maths(cost, 48) # should be True, within \(...\)
True
>>> is_maths(cost, 57) # should be False, within unescaped (...)
False
и дополнительные тесты, чтобы показать, что экранирование обрабатывается правильно:
>>> is_maths(r'Doubled escapes negate: \\$x^2$', 27) # should be true
True
>>> is_maths(r'Doubled escapes negate: \\(x\\)', 27) # no longer escaped, so false
False
Моя реализация старательно игнорирует искаженные проблемы LaTeX; неэкранированный $
символы внутри \(...\)
или сбежал \(
а также \)
символы внутри $...$
игнорируются, как и дальше \(
открывашки внутри \(...\)
последовательности или \)
доводчики без соответствия \(
открывалка предшествующая. Это гарантирует, что функция продолжает работать, даже если задан ввод, который сам LaTeX не будет отображать. Парсер может быть изменен для выдачи исключения или возврата False
в этих случаях, однако. В этом случае вам нужно добавить глобальный набор, созданный из _math_pairs.keys() | _math_pairs.values()
и проверить (char, escaped)
против этого набора, когда expected_closer is not None and (token, escaped) != expected_closer
false (обнаружение разделителей вложенной среды) и проверка на char == ')' and escaped and expected_closer is None
обнаружить \)
ближе без проблемы сошника.