Pyparsing - где порядок токенов в непредсказуемом - PullRequest
7 голосов
/ 25 января 2010

Я хочу быть в состоянии извлечь тип и количество букв из фрагмента текста, где буквы могут быть в любом порядке. У меня есть какой-то другой разбор, над которым я работаю, но этот бит меня озадачил!

input -> result
"abc" -> [['a',1], ['b',1],['c',1]]
"bbbc" -> [['b',3],['c',1]]
"cccaa" -> [['a',2],['c',3]]

Я мог бы использовать поиск или сканирование и повторять для каждой возможной буквы, но есть ли чистый способ сделать это?

Насколько я понял:

from pyparsing import *


def handleStuff(string, location, tokens):

        return [tokens[0][0], len(tokens[0])]


stype = Word("abc").setParseAction(handleStuff)
section =  ZeroOrMore(stype("stype"))


print section.parseString("abc").dump()
print section.parseString("aabcc").dump()
print section.parseString("bbaaa").dump()

Ответы [ 5 ]

6 голосов
/ 26 января 2010

Из вашего описания не было ясно, можно ли смешивать входные символы, например "ababc", поскольку во всех ваших тестовых примерах буквы всегда группировались вместе. Если буквы всегда сгруппированы, вы можете использовать следующий код:

def makeExpr(ch):
    expr = Word(ch).setParseAction(lambda tokens: [ch,len(tokens[0])])
    return expr

expr = Each([Optional(makeExpr(ch)) for ch in "abc"])

for t in tests:
    print t,expr.parseString(t).asList()

Каждая конструкция заботится о соответствии не по порядку, а Word (ch) обрабатывает повторение 1-к-n. Действие parse обеспечивает преобразование проанализированных токенов в кортежи (символ, число).

6 голосов
/ 25 января 2010

Одно решение:

text = 'sufja srfjhvlasfjkhv lasjfvhslfjkv hlskjfvh slfkjvhslk'
print([(x,text.count(x)) for x in set(text)])

Никаких попыток разбора, но это похоже на излишество.

3 голосов
/ 25 января 2010

Мне нравится Однолинейное решение Леннарта .

Алекс упоминает еще один отличный вариант , если вы используете 3.1

Еще один вариант - collection.defaultdict :

>>> from collections import defaultdict
>>> mydict = defaultdict(int)
>>> for c in 'bbbc':
...   mydict[c] += 1
...
>>> mydict
defaultdict(<type 'int'>, {'c': 1, 'b': 3})
2 голосов
/ 26 января 2010

Если вам нужен подход с чисто пирипарингом, это выглядит правильно:

from pyparsing import *

# lambda to define expressions
def makeExpr(ch):
    expr = Literal(ch).setResultsName(ch, listAllMatches=True)
    return expr

expr = OneOrMore(MatchFirst(makeExpr(c) for c in "abc"))
expr.setParseAction(lambda tokens: [[a,len(b)] for a,b in tokens.items()])


tests = """\
abc
bbbc
cccaa
""".splitlines()

for t in tests:
    print t,expr.parseString(t).asList()

Печать:

abc [['a', 1], ['c', 1], ['b', 1]]
bbbc [['c', 1], ['b', 3]]
cccaa [['a', 2], ['c', 3]]

Но это начинает попадать в непонятную область кода, поскольку она опирается на некоторые из более загадочных функций pyparsing. В общем, мне нравятся частотомеры, которые используют defaultdict (еще не пробовал Counter), так как достаточно ясно, что вы делаете.

1 голос
/ 25 января 2010

разделение на части - в Python 3.1 collection.Counter делает такие задачи подсчета действительно легкими. Хорошую версию Counter для Python 2 можно найти здесь .

...