Seite 1 von 1

Textdatei einlesen und in dict ablegen

Verfasst: Dienstag 29. Mai 2012, 08:00
von tvtue
Hallo zusammen,

was wäre der eleganteste Weg eine Textdatei in ein dict einzulesen, wobei der Inhalt der Textdatei einem bestimmten Muster. Die Textdatei ist die Ausgabe von linklint bzw ein File der Ausgabe von linklint und sieht so aus:

Code: Alles auswählen

/path/to/bla/bla1
     used in 3 files:
     /path/to/other/file1
     /path/to/other/file2
     /path/to/other/file3

/path/to/bla/bla2
     used in 2 files:
     /path/to/other/file4
     /path/to/other/file5
Eine for Schleife über alle Zeilen mit einer regex die auf die entsprechenden Zeilenanfänge matched habe ich schon implementiert. Das scheint auch zu funktionieren, ich frage mich nur, ob es nicht einen eleganteren Weg gibt.

Gruß
tvtue

Re: Textdatei einlesen und in dict ablegen

Verfasst: Dienstag 29. Mai 2012, 09:00
von sma
Es wäre hilfreich, wenn du noch erwähnen würdest, was in dem dict die Schlüssel und was die Werte sein sollen.

Unter der Annahme, dass du zu jedem Schlüssel eine Liste von Werten ablegen willst, ist http://docs.python.org/library/collecti ... efaultdict eine praktische Variante von dict, wo man sagen definieren kann, dass jeder neue Eintrag automatisch einen Standardwert hat, also zum Beispiel eine leere Liste. Möglicherweise brauchst du das aber nicht, denn wenn die eingerückten Pfade die Werte sein sollen, kannst du diese ja immer selbst zu Listen zusammenfassen.

Vielleicht so:

Code: Alles auswählen

import re

links, key, paths = {}, None, None

for line in lines:
	if re.match(r"^\s*$"):
		continue
	if re.match(r"^\S"):
		if key:
			links[key] = paths
		key = line
		paths = []
		continue
	if re.match(r"^\s+used in.*:$"):
		continue
	paths.append(line)

if key:
	links[key] = paths
Stefan

Re: Textdatei einlesen und in dict ablegen

Verfasst: Dienstag 29. Mai 2012, 09:01
von BlackJack
@tvtue: Was testet denn der reguläre Ausdruck was man nicht auch ohne ihn testen könnte? Ich sehe drei Zeilentypen: Leerzeilen, Zeilen die mit „whitespace” anfangen, und den Rest. Das kann man auch ohne `re` auseinander halten.

Ich würde mit Generatoren und Iteratoren arbeiten. Im ersten Schritt die Leerzeilen ausfiltern. Dann in einem Generator die Abschnitte markieren und mit `itertools.groupby()` die gruppieren. Und die kann man dann verarbeiten.

Re: Textdatei einlesen und in dict ablegen

Verfasst: Dienstag 29. Mai 2012, 11:35
von tvtue
Hey, danke für eure schnellen Antworten!

In der Tat hätte ich noch mehr Infos dazu geben können, sorry!
Trotzdem habt ihr es kapiert. :)
Ja, die Zeile ohne Whitespace, die mit /path anfängt, soll der Key sein.
Und ja, die Zeilen, die nach der "used in" Zeile kommen, sollen als Array bei dem Key im dict stehen.

Mein Code dafür lautet so:

Code: Alles auswählen

#!/usr/bin/env python
# *-* coding: UTF-8 *-*
"""
~/tmp/linklint/linkdoc/errorAX_tvtue.txt

format:

/wiki/official/advancedtopics-adaptative#id2656141
    used in 1 file:
    /wiki/official/part-advancedtopics

/wiki/official/advancedtopics-adaptative#id2656160
    used in 1 file:
    /wiki/official/part-advancedtopics

- im dict soll der link der key sein und die vorkommnisse ein array
- andersrum wäre es ja besser, dann bräuchte ich jede url nur einmal zu editieren
"""

import os, sys, re

textfile = os.path.expanduser('~/tmp/linklint/linkdoc/errorAX_tvtue.txt')

file = open(textfile,'r')

dict = {}

for line in file:
   if re.match('^\/', line):
      key = line.rstrip()
   elif re.match('^    used in', line):
      pass
   elif re.match('^    /wiki/', line):
      val = line.strip()
      if dict.has_key(key):
         dict[key].append(val)
      else:
         dict[key] = [ val ]

# kontrollausgabe zu testzwecken
for key,val in dict.iteritems():
   print key, " -> ", val
   print
Wie gesagt, ich vielleicht gibts was eleganteres als diese Lösung. Wobei Deine @sma Regex gefallen mir besser.

In meinem Kommentar im Code hab ich noch angedeutet, daß ich das ganze noch andersrum besser finde. Der Sinn des ganzen ist es später ein Wiki automatisch zu editieren. Daher wäre es besser als Keys die Vorkommnisse (also die Zeilen nach "used in") als Key zu verwenden und als Value den ersten Pfad ohne die Leerzeichen. Dazu hab ich mir aber noch kein Gedanken gemacht. Als Anfänger wollte erst mal überhaupt einen Weg finden. :) Entweder muß ich dann komplett anders einlesen oder das dict umsortieren bzw die Werte anderes in zweites dict reinschreiben.

@BlackJack: Generatoren und Iteratoren sind mir nicht bekannt. Da muß ich mich erst mal ein wenig einlesen.

Re: Textdatei einlesen und in dict ablegen

Verfasst: Dienstag 29. Mai 2012, 15:41
von Hyperion
So, ich habe das mal in Richtung von dem, was BlackJack angeregt hatte, versucht umzusetzen: Link

Ich vermute das geht alles noch kompakter :-)

Re: Textdatei einlesen und in dict ablegen

Verfasst: Dienstag 29. Mai 2012, 19:30
von BlackJack
@tvtue: Du magst reguläre Ausdrücke wohl. ;-) Ein '^' ist bei `match()` nicht nötig, denn das ist implizit. Und alle drei Fälle wären auch ohne `re` gegangen, mit der `startswith()`-Methode auf Zeichenketten.

`dict.has_key()` ist „deprecated”, dafür benutzt man den ``in``-Operator. Mit einem `collections.defaultdict()` könnte man sich das ``if``/``else`` an der Stelle komplett sparen.

Mein Ansatz hätte so ausgesehen:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from itertools import groupby, imap
from operator import itemgetter
from pprint import pprint


LINES = '''\
/path/to/bla/bla1
     used in 3 files:
     /path/to/other/file1
     /path/to/other/file2
     /path/to/other/file3
    
/path/to/bla/bla2
     used in 2 files:
     /path/to/other/file4
     /path/to/other/file5
'''.splitlines(True)


def iter_groups(lines):
    def stamp():
        i = 0
        for line in lines:
            if not line[0].isspace():
                i += 1
            yield (i, line)
    
    for i, group in groupby(stamp(), itemgetter(0)):
        result = imap(itemgetter(1), group)
        if i == 0:
            raise ValueError(
                'junk before first group detected:\n' + '\n'.join(result)
            )
        yield result


def _extract_file_count(line):
    try:
        used, in_, i, files = line.split()
        if (used, in_, files) != ('used', 'in', 'files:'):
            raise ValueError
        return int(i)
    except ValueError:
        raise ValueError(
            'expected "used in ... files:" line, got %r instead' % line
        )


def main():
    lines = (line.rstrip() for line in LINES if line.strip())
    
    result = dict()
    for line_group in iter_groups(lines):
        key = next(line_group)
        value_count = _extract_file_count(next(line_group))
        values = [s.strip() for s in line_group]
        if value_count != len(values):
            raise ValueError(
                'expected %d filenames for %r, got %d instead' % (
                    value_count, key, len(values)
                )
            )
        result[key] = values
    
    pprint(result)


if __name__ == '__main__':
    main()

Re: Textdatei einlesen und in dict ablegen

Verfasst: Dienstag 29. Mai 2012, 21:00
von tvtue
Wow, cool!

Danke euch allen, das muß ich erst mal schlucken und verdauen. :)

@BlackJack: ja, ich bin Regex Fan :) , aber ich lasse mich auch gerne auf Neues ein.
Gut zu wissen, daß mit der has_key() Funktion.

So, nun muß ich noch ein bisschen weiter verdauen ;)

Danke !

Re: Textdatei einlesen und in dict ablegen

Verfasst: Dienstag 29. Mai 2012, 21:07
von Hyperion
@BlackJack: Den Trick mit dem Index-Counter zum Markieren der Gruppen muss ich mir dringend merken :-)

Re: Textdatei einlesen und in dict ablegen

Verfasst: Mittwoch 30. Mai 2012, 21:01
von tvtue
@BlackJack bin immer noch am verdauen ;)

Es hapert ja schon an der ersten Zeile in main(). Würdest Du die bitte mal näher erläutern?

Code: Alles auswählen

lines = (line.rstrip() for line in LINES if line.strip())
Mit den runden Klammern ganz außen wird das Generator Objekt erzeugt. Das in den Klammern würde ich eher für Perl halten. *duck* Von dort kenne ich sowas wie print $var if $bedingung; Aber um sowas handelt es sich wohl nicht, oder? Der for Schleife fehlt ein Doppelpunkt. Wieso darf man den weglassen und zwischen dem rstrip() und dem for sucht mein Hirn irgendwie einen Trenner, Semikolon oder sowas. ;) In welcher Reihenfolge werden die drei Ausdrücke innerhalb der Klammern denn nun ausgeführt? Lauter Fragezeichen.

Re: Textdatei einlesen und in dict ablegen

Verfasst: Mittwoch 30. Mai 2012, 21:09
von /me
tvtue hat geschrieben:

Code: Alles auswählen

lines = (line.rstrip() for line in LINES if line.strip())
In welcher Reihenfolge werden die drei Ausdrücke innerhalb der Klammern denn nun ausgeführt? Lauter Fragezeichen.
Man kann es eigentlich direkt lesen:
Liefere den Wert line.rstrip() für jedes Element line aus dem Iterable LINES wenn die Zeile überhaupt Inhalt hat.

Um das "überhaupt Inhalt" zu erläutern: line.strip() liefert einen leeren String wenn nur Whitespaces in line enthalten sind.

Schau dir einfach mal List Comprehensions im Tutorial an. Im Prinzip ist die Syntax die gleiche wie bei einem Generator.

Re: Textdatei einlesen und in dict ablegen

Verfasst: Donnerstag 31. Mai 2012, 06:51
von sma
tvtue hat geschrieben:

Code: Alles auswählen

lines = (line.rstrip() for line in LINES if line.strip())
Das ist eine Kurzform für

Code: Alles auswählen

def einzeiler():
    for line in LINES:
        if line.strip():
            yield line.rstrip()
lines = einzeiler()
Stünden da eckige statt runder Klammern, wäre es vielleicht einfacher zu verstehen:

Code: Alles auswählen

lines = []
for line in LINES:
    if line.strip():
        lines.append(line.rstrip())
Stefan