Проверьте, находится ли позиция в строке в паре определенных символов - PullRequest
0 голосов
/ 03 октября 2018

В 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 Ответ

0 голосов
/ 03 октября 2018

Вам нужно будет проанализировать строку до запрошенной позиции и, если она находится в допустимой паре разделителей среды 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, чтобы обнаружить \) ближе безПроблема сошника.

...