J'ai un fichier avec la structure suivante:

SE|text|Baz
SE|entity|Bla
SE|relation|Bla
SE|relation|Foo

SE|text|Bla
SE|entity|Foo

SE|text|Zoo
SE|relation|Bla
SE|relation|Baz

Les enregistrements (c'est-à-dire les blocs) sont séparés par une ligne vide. Chaque ligne d'un bloc commence par une balise SE. La balise text apparaît toujours dans la première ligne de chaque bloc.

Je me demande comment extraire correctement uniquement les blocs avec une balise relation, qui n'est pas nécessairement présente dans chaque bloc. Ma tentative est collée ci-dessous:

from itertools import groupby
with open('test.txt') as f:
    for nonempty, group in groupby(f, bool):
        if nonempty:
            process_block() ## ?

La sortie souhaitée est un vidage json:

{
    "result": [
        {
            "text": "Baz", 
            "relation": ["Bla","Foo"]
        },
        {
            "text": "Zoo", 
            "relation": ["Bla","Baz"]
        }

    ]
}
2
Andrej 13 sept. 2020 à 13:41

2 réponses

Vous ne pouvez pas stocker la même clé deux fois dans un dictionnaire comme mentionné dans les commentaires. Vous pouvez lire votre fichier, divisé en '\n\n' en blocs, diviser les blocs en lignes en '\n', diviser les lignes en données en '|'.

Vous pouvez ensuite le mettre dans une structure de données appropriée et l'analyser en une chaîne à l'aide du module json:

Créer un fichier de données:

with open("f.txt","w")as f:
    f.write('''SE|text|Baz
SE|entity|Bla
SE|relation|Bla
SE|relation|Foo

SE|text|Bla
SE|entity|Foo

SE|text|Zoo
SE|relation|Bla
SE|relation|Baz''')

Lisez les données et traitez-les:

with open("f.txt") as f:
    all_text = f.read()
    as_blocks = all_text.split("\n\n")
    # skip SE when splitting and filter only with |relation|
    with_relation = [[k.split("|")[1:]
                      for k in b.split("\n")]
                     for b in as_blocks if "|relation|" in b]

    print(with_relation)

Créez une structure de données appropriée en regroupant plusieurs clés identiques dans une liste:

result = []
for inner in with_relation:
    result.append({})
    for k,v in inner:
        # add as simple key
        if k not in result[-1]:
            result[-1][k] = v

        # got key 2nd time, read it as list
        elif k in result[-1] and not isinstance(result[-1][k], list):
            result[-1][k] = [result[-1][k], v]

        # got it a 3rd+ time, add to list
        else:
            result[-1][k].append(v)

print(result)

Créez json à partir de la structure de données:

import json

print( json.dumps({"result":result}, indent=4))

Production:

# with_relation
[[['text', 'Baz'], ['entity', 'Bla'], ['relation', 'Bla'], ['relation', 'Foo']], 
 [['text', 'Zoo'], ['relation', 'Bla'], ['relation', 'Baz']]]

# result
[{'text': 'Baz', 'entity': 'Bla', 'relation': ['Bla', 'Foo']}, 
 {'text': 'Zoo', 'relation': ['Bla', 'Baz']}]

# json string
{
    "result": [
        {
            "text": "Baz",
            "entity": "Bla",
            "relation": [
                "Bla",
                "Foo"
            ]
        },
        {
            "text": "Zoo",
            "relation": [
                "Bla",
                "Baz"
            ]
        }
    ]
}
2
Patrick Artner 13 sept. 2020 à 11:21

À mon avis, c'est un très bon cas pour un petit analyseur.
Cette solution utilise un PEG analyseur appelé parcimonieux mais vous pouvez utiliser totalement un autre:

from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor
import json

data = """
SE|text|Baz
SE|entity|Bla
SE|relation|Bla
SE|relation|Foo

SE|text|Bla
SE|entity|Foo

SE|text|Zoo
SE|relation|Bla
SE|relation|Baz
"""


class TagVisitor(NodeVisitor):
    grammar = Grammar(r"""
        content = (ws / block)+

        block   = line+
        line    = ~".+" nl?
        nl      = ~"[\n\r]"
        ws      = ~"\s+"
    """)

    def generic_visit(self, node, visited_children):
        return visited_children or node

    def visit_content(self, node, visited_children):
        filtered = [child[0] for child in visited_children if isinstance(child[0], dict)]
        return {"result": filtered}

    def visit_block(self, node, visited_children):
        text, relations = None, []
        for child in visited_children:
            if child[1] == "text" and not text:
                text = child[2].strip()
            elif child[1] == "relation":
                relations.append(child[2])

        if relations:
            return {"text": text, "relation": relations}

    def visit_line(self, node, visited_children):
        tag1, tag2, text = node.text.split("|")
        return tag1, tag2, text.strip()


tv = TagVisitor()
result = tv.parse(data)

print(json.dumps(result))

Cela donne

{"result": 
    [{"text": "Baz", "relation": ["Bla", "Foo"]}, 
     {"text": "Zoo", "relation": ["Bla", "Baz"]}]
}

L'idée est de formuler une grammaire, d'en construire une arborescence syntaxique abstraite et de renvoyer le contenu du bloc dans un format de données approprié.

1
Jan 13 sept. 2020 à 12:58