У меня есть файл, заполненный культурами, и каждая культура имеет уникальный набор фамилий для людей каждой культуры. У меня проблема в том, что существует множество файлов, каждый из которых имеет сотни, если не тысячи имен, поэтому вместо того, чтобы собирать все эти файлы вручную, я хотел бы в некотором смысле автоматизировать эту задачу, используя python и regex.
Вот пример содержимого файла:
###Myrman###
360 = { # DUPLICATE §§§§§§
name = "of Myr"
culture = myrman
}
300507 = {
name = "of Myr"
culture = myrman
}
300525 = {
name = "Trellos"
culture = myrman
}
300534 = {
name = "Uteuran"
culture = myrman
}
##Lysene##
1386 = {
name = "Ormollen"
culture = lysene
coat_of_arms = {
template = 0
layer = {
texture = 14
texture_internal = 9
emblem = 0
color = 0
color = 0
color = 0
}
}
}
300505 = {
name = "of Lys"
culture = lysene
}
300523 = {
name = "Lohar"
culture = lysene
}
300532 = {
name = "Assadyrn"
culture = lysene
}
Итак, как вы можете видеть, здесь есть два типа культур, каждая из которых имеет разные фамилии для людей соответствующих культур. Я хочу взять все эти разные имена и отсортировать их по разным группам, которые также разделены запятыми и кавычками. Вот пример того, что я хочу сделать:
Myrman: ["of Myr", "of Myr", "Trellos", "Uteuran"]
Lysene: ["Ormollen", "of Lys", "Lohar", "Assadyrn"]
Как мне это сделать с python и его библиотекой регулярных выражений?
Какое расширение у файла?
Привет, это обычный текстовый файл. Это файлы из мода для игры "Crusader Kings 2", но эти файлы структурированы так же, как и обычные файлы игры.
О, проблема с парсером! Давайте воспользуемся генератором парсеров lark, чтобы разобраться в этом.
Во-первых, давайте создадим синтаксис для нашего файла — он собран на основе примера парсера JSON:
import lark
parser = lark.Lark(r"""
start: (term)*
term: key " = " value "\n"
key: CNAME | SIGNED_NUMBER
value: CNAME | SIGNED_NUMBER | ESCAPED_STRING | map
map: "{" (term)* "}"
%import common.CNAME
%import common.ESCAPED_STRING
%import common.SIGNED_NUMBER
%import common.WS
%ignore WS
%ignore /#.*/
""")
Довольно просто; файл представляет собой список term
s, которые являются ключами-значениями, где ключ может быть именем или числом, а значение может быть именем, числом, строкой или картой, которая представляет собой список, заключенный в фигурные скобки. условия.
Затем давайте напишем преобразователь для преобразования дерева синтаксического анализа Lark в словарь:
class TreeTransformer(lark.Transformer):
def start(self, items):
return dict(items)
def term(self, items):
return (items[0], items[1])
def CNAME(self, item):
return item.value
def SIGNED_NUMBER(self, item):
return int(item.value)
def ESCAPED_STRING(self, item):
return item.value[1:-1]
def map(self, items):
return dict(items)
def key(self, item):
return item[0]
def value(self, item):
return item[0]
Возможно, можно было бы быть более кратким, но это работает. Давайте запустим его против данных:
from pathlib import Path
from pprint import pprint
data = Path("./so75472097-data.txt").read_text()
tree = parser.parse(data)
res = TreeTransformer().transform(tree)
pprint(res)
Выход
{360: {'culture': 'myrman', 'name': 'of Myr'},
1386: {'coat_of_arms': {'layer': {'color': 0,
'emblem': 0,
'texture': 14,
'texture_internal': 9},
'template': 0},
'culture': 'lysene',
'name': 'Ormollen'},
300505: {'culture': 'lysene', 'name': 'of Lys'},
300507: {'culture': 'myrman', 'name': 'of Myr'},
300523: {'culture': 'lysene', 'name': 'Lohar'},
300525: {'culture': 'myrman', 'name': 'Trellos'},
300532: {'culture': 'lysene', 'name': 'Assadyrn'},
300534: {'culture': 'myrman', 'name': 'Uteuran'}}
-- выглядит многообещающе!
Тогда это просто вопрос обхода dict:
from collections import defaultdict
names_by_culture = defaultdict(list)
for info in res.values():
names_by_culture[info["culture"]].append(info["name"])
pprint(dict(names_by_culture))
... и вуаля!
{'lysene': ['Ormollen', 'of Lys', 'Lohar', 'Assadyrn'],
'myrman': ['of Myr', 'of Myr', 'Trellos', 'Uteuran']}
Теперь все, что вам нужно сделать, это обернуть этого плохого мальчика в функцию и вызвать ее для всех ваших файлов.
(EDIT, теперь, когда я прочитал последний комментарий и знаю, что гуглить: вы могли бы просто использовать библиотеку ClauseWizard вместо того, чтобы писать синтаксический анализатор самостоятельно, но это было веселее!)
Как обсуждалось в комментариях, грамматика и преобразователь также подходят для «практически всего» для значений без кавычек:
parser = lark.Lark(r"""
start: (term)*
term: key " = " value "\n"
key: KEYNAME | SIGNED_NUMBER
value: VALUENAME | SIGNED_NUMBER | ESCAPED_STRING | map
map: "{" (term)* "}"
VALUENAME: /[a-zA-Z][^\s=]*/
KEYNAME: /[a-zA-Z][-a-zA-Z0-9_]*/
%import common.ESCAPED_STRING
%import common.SIGNED_NUMBER
%import common.WS
%ignore WS
%ignore /#.*/
""")
from operator import itemgetter, attrgetter
class TreeTransformer(lark.Transformer):
start = dict
map = dict
key = itemgetter(0)
value = itemgetter(0)
VALUENAME = attrgetter("value")
KEYNAME = attrgetter("value")
term = tuple
def SIGNED_NUMBER(self, item):
return int(item.value)
def ESCAPED_STRING(self, item):
return item.value[1:-1]
Эй, во-первых, я хочу сказать спасибо за ваше очень подробное объяснение и помощь, я очень благодарен! В любом случае, я проверил ваш код, и он работает, но у меня есть только один вопрос: как я могу позволить этому синтаксическому анализатору анализировать имена со специальными символами, такими как: Crom-Ya или K'Mool? Эти имена есть в файле и я получил ошибку при его запуске, после их удаления все заработало, но как мне разобрать имена с такими спецсимволами?
Кроме того, я также проверю те синтаксические анализаторы, которые вы предложили, но я согласен, что писать свой собственный код гораздо веселее, ха-ха
Если они не заключены в кавычки, вам нужно настроить это, чтобы использовать для них что-то другое, кроме CNAME - может быть, просто определить тип терминала NAME: /\w+/
?
@Keenonthedaywalk Я редактировал что-то, что может иметь дело, например. culture = ly'sene
:)
Эй, мужик, спасибо за всю помощь и усилия с этим постом, я очень ценю это!
Поскольку файл хорошо структурирован, просто используйте регулярное выражение с соответствующим запросом и соответствующим образом обработайте выводимые кортежи.
result = re.findall('name[ ]*=[ ]*"([A-z ]+)"\n[ ]+culture[ ]*=[ ]*([A-z]+)', a)
names_by_culture = {}
for i in result:
name = i[0]
culture = i[1]
try:
names_by_culture[culture].append(name)
except:
names_by_culture[culture] = []
names_by_culture[culture].append(name)
print(names_by_culture)
Выход:
{'myrman': ['of Myr', 'of Myr', 'Trellos', 'Uteuran'],
'lysene': ['Ormollen', 'of Lys', 'Lohar', 'Assadyrn']}
Привет, я отметил сообщение пользователя AKX выше как решенное, но это также очень полезно. Я очень ценю помощь!
Где ты взял этот файл? Очевидно, что это структурированный формат, поэтому было бы проще найти для него синтаксический анализатор, чем пытаться собрать что-то вместе с регулярными выражениями.