Teilstring am Anfang des Textes suchen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

in einer großen Textdatei mit TAB getrennten Spalten, möchte ich bei bestimmten Datensätze, die Textspalte am Anfang mit einem String zu vergleichen. Dazu gehören alle Datensätze, bei der der Herstellername 'LIEFERANT' lautet.

Beispiel:
  • 028 028934 PAPER MATE KUGELSCHREIBER S0190393 SW PAPER MATE KUGELSCHREIBER S0190393 SW LIEFERANT S0190393 STÜCK 1 2 1,39 1,29 08008285096109 12 040 510 E LAGER 2012.04.05
Die Textspalte wäre hier:
  • PAPER MATE KUGELSCHREIBER S0190393 SW
Nun habe ich auch eine große Vergleichdatei, die aus 2 Spalten besteht. Die erste Spalte, beinhaltet den Basisnamen des Herstellers, der auch so mit der gleichen Schreibweise in meinen Daten verwendet wird. Die zweite Spalte, enthält verschiedene Schreibweisen und Produktnamen, die zum gleichen Hersteller zu zuordnen sind.

Beispiel:
  • PAPERMATE PAPEMATE
    PAPERMATE PAPEMATEPAPEMATE
    PAPERMATE PAPERMATE
    PAPERMATE PAPER MATE
    PAPERMATE PAPER:MATE
    PAPERMATE PAPER*MATE
    PAPERMATE SHARPIE
Ich habe eine Möglichkeit gefunden, dies umzusetzen. Leider ist dies so wohl nicht die optimalste Lösung, da meine Textdatei ca. 85000 Datensätze und die Vergleichsdatei ca. 900 Datensätze hat und ich hier mit zwei Schleifen untereinander arbeiten muß und daher auch entsprechend Zeit benötigt.

Hier poste ich mal mein Konstrukt:

Code: Alles auswählen

herstellervergleich = {}
with codecs.open(liste_hersteller_leerzeichen_path, "r") as infile:
    reader = csv.reader(infile, delimiter="\t", quotechar="^")
    for item in reader:
        herstellervergleich[item[1]] = item[0]


with codecs.open(base_daten_path, "r") as infile:
    reader = csv.reader(infile, delimiter="\t", quotechar="^")

    datenvergleich = {}
    for item in reader:
        if item[4] == 'LIEFERANT':
            for i in herstellerleer:
                a = re.match('{} '.format(i), item[2])
                if a != None:
                    b = herstellervergleich.get(i)
                    if b != '':
                        datenvergleich[item[0], item[1]] = b
                        break


with codecs.open(base_daten_path, "r") as infile:
    reader = csv.reader(infile, delimiter="\t", quotechar="^")

    datenneu = []
    for item in reader:
        zeile = (item[0], item[1])
        if datenvergleich.get(zeile):
            b = datenvergleich.get(zeile)
            zeile = (item[0], item[1], item[2], item[3], b, item[5], item[6], item[7], item[8], item[9], item[10], item[11], item[12], item[13], item[14], item[15], item[16], item[17], item[18], item[19], item[20], item[21], item[22], item[23], item[24], item[25], item[26], item[27], item[28], item[29], item[30], item[31])
            datenneu.append(zeile)
        else:
            zeile = (item[0], item[1], item[2], item[3], item[4], item[5], item[6], item[7], item[8], item[9], item[10], item[11], item[12], item[13], item[14], item[15], item[16], item[17], item[18], item[19], item[20], item[21], item[22], item[23], item[24], item[25], item[26], item[27], item[28], item[29], item[30], item[31])
            datenneu.append(zeile)

daten = sorted(set(datenneu))

write_csv(base_daten_path, daten)
Habt Ihr mir dazu vielleicht Vorschläge?

Grüße Nobuddy
Zuletzt geändert von Nobuddy am Freitag 6. April 2012, 18:27, insgesamt 2-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also zuerst einmal setze Deinen Python-Quell-Code doch in Zukunft in die Python-Code-Tags: [ code=python ] (ohne die Leerzeichen).

Ich kapiere folgendes nicht:

Code: Alles auswählen

PAPER MATE KUGELSCHREIBER S0190393 SW
Das hier ist *eine* Spalte? Für mich sieht das wie fünf Spalten aus... zumindest erscheinen diese Daten nicht gerade atomar.

Desweiteren kapiere ich die Vergleichsoperation nicht ganz:

Code: Alles auswählen

PAPERMATE PAPER MATE
Dies wäre ja das Tupel, bei dem der zweite Teil mit dem Anfang Deiner obigen Spalte übereinstimmt. Aber wozu dient der erste Wert? Soll der an Stelle der bisherigen Bezeichnung ersetzt werden?

Wenn ja, dann würde ich das ganze doch umdrehen und ein Dictionary so aufbauen, dass der Schlüssel eine der möglichen Varianten umfasst und als Wert die homogene Bezeichnung genommen wird. Damit würde die Lookup-Operation in O(1) ablaufen und Du hättest als Komplexität lediglich O(n), wobei n eben den 5000 Datensätzen entspräche.

Du musst hier ggf. noch mal für Aufklärung sorgen ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Du könntest auch einen Päfixbaum verwenden. Als ersten Gedanken hatte ich aber auch Hyperions Idee, da sich dies schnell implementieren und testen lässt. Wenn das insgesamt immer noch zu langsam ist, dann kann man sich weitere Gedanken machen.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Vielleicht noch das ein oder andere Wort zum Code:

- Das hier

Code: Alles auswählen

zeile = (item[0], item[1], item[2], item[3], item[4], item[5], item[6], item[7], item[8], item[9], item[10], item[11], item[12], item[13], item[14], item[15], item[16], item[17], item[18], item[19], item[20], item[21], item[22], item[23], item[24], item[25], item[26], item[27], item[28], item[29], item[30], item[31])
kann man doch kürzer so schreiben:

Code: Alles auswählen

zeile = tuple(item)
- Und im `if`-Zweig ginge es so kürzer (und lesbarer):

Code: Alles auswählen

zeile = tuple(item[0:4] + [b] + item[5:])
- Wobei ich mich grundsätzlich frage, wozu das eigentlich? Du willst doch eigentlich nur in einem Spezialfall *ein* einzelnes Element eines Datentupels ändern. Wandle doch einfach `item` in eine Liste (iirc. liefert `csv.reader` eine Immutual Sequence?) um und setze dann das Objekt an Index vier einfach neu. Den `else`-Zweig bräuchtest Du dann gar nicht mehr...

- Den RegExp braucht man auf den ersten Blick auch nicht; ein `"".startswith()` sollte doch reichen, oder übersehe ich das etwas?

Generell sehe ich nach wie vor bei Dir kein "Ziel". Du scheinst immer noch einen Haufen Konvertierungs- bzw. Konsolidierungsscripte zu schreiben, ohne eine Zieldatenstruktur zu haben... behältst Du da noch den Überblick, welches Script Du in welcher Reihenfolge auf welcher eingehenden oder bestehenden Datendatei laufen lassen musst? ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hyperion hat geschrieben:Also zuerst einmal setze Deinen Python-Quell-Code doch in Zukunft in die Python-Code-Tags: [ code=python ] (ohne die Leerzeichen).
Sorry, habe da das falsche Button erwischt, habe es nachträglich geändert.
Hyperion hat geschrieben:Ich kapiere folgendes nicht:

Code: Alles auswählen

PAPER MATE KUGELSCHREIBER S0190393 SW
Das hier ist *eine* Spalte? Für mich sieht das wie fünf Spalten aus... zumindest erscheinen diese Daten nicht gerade atomar.
In meiner Textdatei, sind die Spalten durch TAB getrennt. Innerhalb der Spalten gibt es nur Leerzeichen.
Hyperion hat geschrieben:Desweiteren kapiere ich die Vergleichsoperation nicht ganz:

Code: Alles auswählen

PAPERMATE PAPER MATE
Dies wäre ja das Tupel, bei dem der zweite Teil mit dem Anfang Deiner obigen Spalte übereinstimmt. Aber wozu dient der erste Wert? Soll der an Stelle der bisherigen Bezeichnung ersetzt werden?
Der erste Teil, hat bei der Spalte Hersteller Gültigkeit und wird auch da so übernommen. Der zweite Teil, zeigt Variationen des Namens auf, die z.B. in der Benennungsspalte auftauchen können.
Hyperion hat geschrieben:Wenn ja, dann würde ich das ganze doch umdrehen und ein Dictionary so aufbauen, dass der Schlüssel eine der möglichen Varianten umfasst und als Wert die homogene Bezeichnung genommen wird. Damit würde die Lookup-Operation in O(1) ablaufen und Du hättest als Komplexität lediglich O(n), wobei n eben den 5000 Datensätzen entspräche.
Entschuldige, aber verstehe jetzt nicht ganz, wie Du dies meinst?
Beim Beispiel 'PAPER MATE', ist dies ja ein Teilstring, der wenn am Anfang der Benennungsspalte steht.
Mir ist da jetzt nicht ganz klar, wie ich hier direkt ein Dictionary anwenden kann.
Vielleicht kannst Du mir das kurz aufzeigen?
Hyperion hat geschrieben:Vielleicht noch das ein oder andere Wort zum Code:

- Das hier

Code: Alles auswählen

zeile = (item[0], item[1], item[2], item[3], item[4], item[5], item[6], item[7], item[8], item[9], item[10], item[11], item[12], item[13], item[14], item[15], item[16], item[17], item[18], item[19], item[20], item[21], item[22], item[23], item[24], item[25], item[26], item[27], item[28], item[29], item[30], item[31])
kann man doch kürzer so schreiben:

Code: Alles auswählen

zeile = tuple(item)
- Und im `if`-Zweig ginge es so kürzer (und lesbarer):

Code: Alles auswählen

zeile = tuple(item[0:4] + [b] + item[5:])
Danke für den Tip!
Hyperion hat geschrieben:- Wobei ich mich grundsätzlich frage, wozu das eigentlich? Du willst doch eigentlich nur in einem Spezialfall *ein* einzelnes Element eines Datentupels ändern. Wandle doch einfach `item` in eine Liste (iirc. liefert `csv.reader` eine Immutual Sequence?) um und setze dann das Objekt an Index vier einfach neu. Den `else`-Zweig bräuchtest Du dann gar nicht mehr...
iirc sagt mir im Moment nichts, werde mich mal da durch die Lektüre lesen.
Hyperion hat geschrieben:- Den RegExp braucht man auf den ersten Blick auch nicht; ein `"".startswith()` sollte doch reichen, oder übersehe ich das etwas?
Evtl. ein kurzes Beispiel?

Hyperion hat geschrieben:Generell sehe ich nach wie vor bei Dir kein "Ziel". Du scheinst immer noch einen Haufen Konvertierungs- bzw. Konsolidierungsscripte zu schreiben, ohne eine Zieldatenstruktur zu haben... behältst Du da noch den Überblick, welches Script Du in welcher Reihenfolge auf welcher eingehenden oder bestehenden Datendatei laufen lassen musst? ;-)
Manches sieht für Außenstehende bestimmt chaotisch aus, hat aber seinen tieferen Sinn.
Um im Chaos den Überblick zu behalten, dokumentiere ich das Ganze auch.
Zuletzt geändert von Nobuddy am Freitag 6. April 2012, 18:27, insgesamt 1-mal geändert.
webspider
User
Beiträge: 485
Registriert: Sonntag 19. Juni 2011, 13:41

"iirc" ist die Abkürzung für "If I remember/recall correctly". Dokumentation sehe ich auch keine. Und unterschiedlichen whitespace (soll heißen: alles was über Leerzeichen und Absätze hinausgeht) als Trenner bei komplexeren Daten zu nutzen ist suboptimal, insbesondere wenn man mal schnell drüberlesen will. Variationen könnte man zum Beispiel schön in Klammern packen.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

hat jetzt eine Weile für meine Rückmeldung gedauert, aber dies hat sich gelohnt.
Besonderen Dank an Hyperion, dessen letzten Post ich nach und nach durchgearbeitet habe, auch wenn nicht alles nachvollziehen konnte.

Mein Konstrukt sieht jetzt so aus:

Code: Alles auswählen

# Herstellerliste
# Key, Value
herstellervergleich = {}
with codecs.open(liste_hersteller_leerzeichen_path, "r") as infile:
    reader = csv.reader(infile, delimiter="\t", quotechar="^")
    for item in reader:
        herstellervergleich[item[1]] = item[0]

# Verwende aus Hauptarbeitsliste zum Vergleich:
# Lieferant und Artikelnummer
artikelvergleich = {}
with codecs.open(quellliste_path, "r") as infile:
    reader = csv.reader(infile, delimiter="\t", quotechar="^")
    for item in reader:
        zeile = (item[0], item[1])
        artikelvergleich[item[0], item[1]] = zeile

# Basisliste für aktuelle Lieferantendaten
with codecs.open(base_daten_path, "r") as infile:
    reader = csv.reader(infile, delimiter="\t", quotechar="^")

    # Überprüfe neue Datensätze
    # Lieferant, Artikelnummer, Benennung und Hersteller
    artikelneu = []
    for item in reader:
        zeile = (item[0], item[1])
        if not artikelvergleich.get(zeile):
            zeile = (item[0], item[1], item[2], item[4])
            artikelneu.append(zeile)

   # Überprüfe, wenn Hersteller ist 'LIEFERANT'
   # in Benennung nach Herstellernamen
    datenvergleich = {}
    for item in artikelneu:
        if item[3] == 'LIEFERANT':
            for key, value in herstellervergleich.iteritems():
                if item[2].startswith('{} '.format(key)) == True:
                    datenvergleich[item[0], item[1]] = value
                    break


with codecs.open(base_daten_path, "r") as infile:
    reader = csv.reader(infile, delimiter="\t", quotechar="^")

    wert = 0
    datenneu = []
    for item in reader:
        zeile = (item[0], item[1])
        if datenvergleich.get(zeile):
            b = datenvergleich.get(zeile)
            zeile = tuple(item[0:4] + [b] + item[5:])
            datenneu.append(zeile)
            wert = wert + 1
        else:
            zeile = tuple(item)
            datenneu.append(zeile)
Ich habe auch einen Geschwindigkeitsvergleich zwischen 'match' und 'startswith' gemacht und war sehr über den großen Unterschied überrascht.
Bei 1495 neuen Datensätzen ('LIEFERANT') und 885 Herstellervergleichsdatensätze, habe ich mit 'match' ca. 140 Sekunden benötigt, während mit 'startswith' das Ganze nur 1,94 Sekunden benötigte!

Sollte nach etwas zu optimieren sein, freue ich mich gern auf Euren Input!

Grüße Nobuddy
BlackJack

@Nobuddy: `codecs.open()` kann ich nicht so ganz nachvollziehen. Wenn das so funktioniert, dann enthalten die Daten nichts ausserhalb von ASCII. Dann sehe ich hier aber auch keinen Grund sie zu dekodieren.

Die Zeile mit dem `csv.reader()`-Aufruf wiederholt sich öfter, da könnte man mit `functools.partial()` eine eigene Funktion für erstellen, damit man die gleichbleibenden Argumente nur einmal im Quelltext schreiben muss.

Mit Generatorausdrücken könnte man einige der Schleifen kompakter schreiben.

`artikelvergleich` benutzt die falsche Datenstruktur. Wenn Schlüssel und Wert eines Wörterbuchs jeweils immer gleich sind, dann will man eigentlich eine Menge, also den Datentyp `set` verwenden.

Vergleiche von Ergebnissen von Operationen die selbst einen Wahrheitswert liefern mit literalen Wahrheitswerten sind überflüssig. Man muss beim Ergebnis von `startswith()` nicht noch einmal explizit überprüfen ob das ``True`` war. Denn wenn es das war, dann ergibt der Vergleich ja auch wieder nur ``True`` als Ergebnis.

Bei `herstellervergleich` nutzt Du nirgends, dass das ein Wörterbuch ist. Also könnte man das im Grunde auch in einer Liste speichern. Entweder sofort oder über den Umweg eines Wörterbuchst wenn die Schlüssel in der Eingabedatei mehrfach vorkommen könnten. Dann merkt man später aber auch dass die generischen Namen `key` und `value` nicht mehr wirklich stimmen. Statt dem `item` und der Indexerei wären Namen für die einzelnen Elemente auch schöner, weil verständlicher.

Beim Befüllen/Erstellen von `datenvergleich` kann es übrigens passieren, dass ``item[3] == 'LIEFERANT'`` wahr ist, aber nichts in `herstellervergleich` gefunden wird. Das würde dann einfach ignoriert. Ist das gewollt?

Der letzte Schleifenkörper ist nicht optimal aufgebaut. Operationen sollte man wenn möglich nicht wiederholen wenn man weiss, dass das gleiche Ergebnis heraus kommt. *Ein* ``datenvergleich.get()`` sollte genügen. Gemeinsamkeiten in beiden Zweigen in einem ``if``/``else``-Konstrukt sollte man dort nach Möglichkeit heraus ziehen. In beiden Fällen wird etwas an `datenneu` angehängt.

Es wird dort auch mehrfach der Name `zeile` an Werte mit völlig unterschiedlicher Bedeutung gebunden. Das ist verwirrend.

Wenn man sich anschaut was mit `item`, `b` und `zeile` in den beiden Zweigen passiert, ist das auch alles viel zu umständlich. Wenn man das vereinfacht, kann man sich den ``else``-Zweig sogar komplett sparen.

Ich wäre dann bei (ungetestet):

Code: Alles auswählen

import csv
from functools import partial

my_reader = partial(csv.reader, delimiter='\t', quotechar='^')


def main():
    liste_hersteller_leerzeichen_path = '...'
    quellliste_path = '...'
    base_daten_path = '...'
    # 
    # Herstellerliste:
    # Key, Value.
    # 
    with open(liste_hersteller_leerzeichen_path, 'r') as infile:
        herstellervergleich = [(r[1], r[0]) for r in my_reader(infile)]
    # 
    # Verwende aus Hauptarbeitsliste zum Vergleich:
    # Lieferant und Artikelnummer.
    # 
    with open(quellliste_path, 'r') as infile:
        artikelvergleich = set(tuple(r[:2]) for r in my_reader(infile))
    # 
    # Basisliste für aktuelle Lieferantendaten.
    # 
    with open(base_daten_path, 'r') as infile:
        # 
        # Überprüfe neue Datensätze:
        # Lieferant, Artikelnummer, Benennung und Hersteller.
        # 
        artikelneu = (
            r[:5]
            for r in my_reader(infile)
            if tuple(r[:2]) not in artikelvergleich
        )
        # 
        # Überprüfe, wenn Hersteller ist 'LIEFERANT'
        # in Benennung nach Herstellernamen.
        # 
        datenvergleich = dict()
        for lieferant, artikelnummer, benennung, hersteller in artikelneu:
            if hersteller == 'LIEFERANT':
                for key, value in herstellervergleich:
                    if benennung.startswith(key + ' '):
                        datenvergleich[lieferant, artikelnummer] = value
                        break

    with open(base_daten_path, 'r') as infile:
        wert = 0
        datenneu = list()
        for row in my_reader(infile):
            value = datenvergleich.get(tuple(row[:2]))
            if value is not None:
                row[4] = value
                wert += 1
            datenneu.append(tuple(row))

    print wert, datenneu


if __name__ == '__main__':
    main()
Wobei das für meinen Geschmack zu komplex und umfangreich für eine einzelne Funktion ist.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

BlackJack hat geschrieben: Wobei das für meinen Geschmack zu komplex und umfangreich für eine einzelne Funktion ist.
Um das Erlernen von Funktionen (Teilprobleme sinnvoll zerlegen, eigene Funktionen zu konzipieren, usw) drückt sich Nobuddy seit ich ihn "kenne" herum ;-) Ich habe ihm schon bei uu.de zig Mal Code gezeigt, der einzelne Aufgaben in kleine Häppchen verpackt erledigt. Zugegeben ist das vermutlich eine der schwersten Sachen für einen Anfänger, aber wenn man es nie versucht, wird man darin auch nicht besser :-)

@Nobuddy: Vielleicht wäre es eine gute Idee, wenn Du basierend auf dem Code von BlackJack genau das Problem mal angehen würdest?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
Hyperion, daß ich mich um Funktionen herumdrücke, stimmt nicht ganz.
Da mir das momentan noch nicht ganz so von der Hand läuft, baue ich meine Konstrukte so wie ich es momentan noch am besten kann. Wenn dies dann funktioniert, versuche ich das dann in eine Funktion zu packen.

Kleines Beispiel:

Code: Alles auswählen

#!/usr/bin/python3
# -*- coding: utf-8

from __modul_base__ import *
from __modul_write__ import *


### Funktionen

########################################################################

## Zähler erstellen
# Dazu wird get_num und get_ergebnis benötigt
#

class groupindex:
    '''groupindex erstellt einen numerischen,
    fortlaufenden Index, der jeweiligen Gruppierung.
    filename = Datei für Indexerstellung, mit den untenstehenden Daten
    base1 = Hauptbezugspalte für Hauptgruppe
    base2 = Untergruppenspalte zu Hauptbezugspalte
    index = Indexspalte (leer))
    output1 = item[base1]
    output2 = item[base2]'''
    def __init__(self, filename, base1, base2, index, output1, output2):
        self.filename = filename
        self.base1 = base1
        self.base2 = base2
        self.index = index
        self.output1 = output1
        self.output2 = output2

    def get_num(self):
        with codecs.open(self.filename, 'r') as infile:
            reader = csv.reader(infile, delimiter="\t", quotechar="^")
            table = []
            for item in reader:
                if item[self.base1] != '' and item[self.index] != '':
                    if len(item[self.index]) == 1:
                        w = '000{}'.format(item[self.index])
                        zeile = (item[self.base1], w)
                        table.append(zeile)
                    if len(item[self.index]) == 2:
                        w = '00{}'.format(item[self.index])
                        zeile = (item[self.base1], w)
                        table.append(zeile)
                    if len(item[self.index]) == 3:
                        w = '0{}'.format(item[self.index])
                        zeile = (item[self.base1], w)
                        table.append(zeile)
                    if len(item[self.index]) == 4:
                        w = '{}'.format(item[self.index])
                        zeile = (item[self.base1], w)
                        table.append(zeile)
                if item[self.base1] != '' and item[self.index] == '':
                    if len(item[self.index]) == 0:
                        w = '0000{}'.format(item[self.index])
                        zeile = (item[self.base1], w)
                        table.append(zeile)

        table = sorted(set(table))

        y = 1
        vergleich = {}
        while True:
            tablewert = tuple(table)
            try:
                biggest = max(filter(lambda x: int(x[self.base1]) == y, tablewert))
                vergleich[biggest[0]] = int(biggest[1])
                if y < 20:
                    y = y + 1
                else:
                    y = y + 10
            except ValueError:
                break

        daten = []
        basisdaten = []
        with codecs.open(self.filename, 'r') as infile:
            reader = csv.reader(infile, delimiter="\t", quotechar="^")
            for item in reader:
                if item[self.base1] == self.output1 and item[self.base2] == self.output2:
                    zeile = (item[self.base1], item[self.base2], item[self.index])
                    basisdaten.append(zeile)
                else:
                    zeile = (item[self.base1], item[self.base2], item[self.index])
                    daten.append(zeile)

        basisdaten = sorted(set(basisdaten))

        for item in basisdaten:
            if item[self.base1] != '' and item[self.index] == '':
                if not vergleich.get(item[self.base1]):
                    wertneu = (item[self.base1], item[self.base2], 1)
                    daten.append(wertneu)
                if vergleich.get(item[self.base1]):
                    num = int(vergleich.get(item[self.base1])) + 1
                    wertneu = (item[self.base1], item[self.base2], num)
                    daten.append(wertneu)

        if daten != []:
            write_csv(self.filename, daten)


'''filename = Datei für Indexerstellung, mit den untenstehenden Daten
base1 = Hauptbezugspalte für Gruppierung
base2 = Bezeichnungsspalte zu Hauptbezugspalte für Gruppierung
index = Indexspalte (leer)'''
def get_indexwork(filename, base1, base2, index):
    lines = []
    null =[]
    with codecs.open(filename, 'r') as infile:
        reader = csv.reader(infile, delimiter="\t", quotechar="^")
        for item in reader:
            lines.append(item)

    end = len(lines)
    if end == 0:
        end = 1

    while True:
        with codecs.open(filename, 'rU') as infile:
            reader = csv.reader(infile, delimiter="\t", quotechar="^")
            for item in reader:
                try:
                    if item[base1] != '' and item[index] == '':
                        ouput1 = item[base1]
                        ouput2 = item[base2]
                        p = groupindex(filename, base1, base2, index, ouput1, ouput2)
                        p.get_num()
                        end = end - 1
                    else:
                        pass
                except IndexError:
                    pass
        if end <= -2:
            break
        else:
            end = end - 1


    daten = []
    with codecs.open(filename, 'r') as infile:
        reader = csv.reader(infile, delimiter="\t", quotechar="^")
        for item in reader:
            zeile = (item[base1], item[base2], item[index])
            daten.append(zeile)

    daten = sorted(set(daten))
    write_csv(filename, daten)
Bestimmt nicht unbedingt das gelbe vom Ei ... aber funktioniert.

Hyperion, Du hast aber mit dem was Du schreibst, schon recht ... ist nicht so einfach wie es aussieht.
Aber, ich werde mich da durchbeißen, mache halt kleinere Schritte. :wink:

BlackJack, werde Deinen Post durcharbeiten!
Wahrscheinlich wird da die eine oder andere Frage von mir noch auftauchen ... aber zuerst einmal ran an die Arbeit. :wink:

Grüße Nobuddy
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Nobuddy hat geschrieben: Da mir das momentan noch nicht ganz so von der Hand läuft, baue ich meine Konstrukte so wie ich es momentan noch am besten kann. Wenn dies dann funktioniert, versuche ich das dann in eine Funktion zu packen.
Imho ist das genau der falsche Weg! Du solltest eher den umgekehrten weg gehen und versuchen, das Gesamtproblem in kleine Teilprobleme aufzuteilen, die Du dann in einer Funktion umsetzt. Diese kannst Du erst einmal separat testen und langsam zu einem ganzen zusammenfügen.

Erst alles in Spaghettimanier aufzuschreiben und anschließend zu trennen ist eher suboptimal. Zum einen verliert sich schnell der Überblick und man implementiert vieles einfach doppelt, zum anderen ist es später immer schwerer, in dem Wust den Überblick so zu behalten, dass man tatsächlich noch generalisieren kann.

Natürlich kommt es in der Praxis vor, dass man Code schreibt, von dem man anschließend erkennt, dass man ihn lieber in zwei oder mehrere Funktionen trennen sollte. Aber grundsätzlich solltest Du den umgekehrten Weg beschreiten :-)

Zu Deinem Code äußere ich mich erst einmal nicht... das sieht doch viel zu komplex und konfus aus :-D (Zumal, wenn man die Datendateien und deren Beschaffenheit nicht kennt). Allerdings kann ich auf den ersten Blick wieder viel Copy&Paste-Code erkennen... wenn man zig Mal das annähernd gleiche if-Konstrukt untereinander schreibt, dann kann man immer etwas optimieren ;-)

Das hier...

Code: Alles auswählen

if len(item[self.index]) == 1:
    w = '000{}'.format(item[self.index])
    zeile = (item[self.base1], w)
    table.append(zeile)
if len(item[self.index]) == 2:
    w = '00{}'.format(item[self.index])
    zeile = (item[self.base1], w)
    table.append(zeile)
if len(item[self.index]) == 3:
    w = '0{}'.format(item[self.index])
    zeile = (item[self.base1], w)
    table.append(zeile)
if len(item[self.index]) == 4:
    w = '{}'.format(item[self.index])
    zeile = (item[self.base1], w)
    table.append(zeile)
kann man doch auf dieses reduzieren...

Code: Alles auswählen

table.append(
        (
            item[self.base1], 
            "{}{}".format("0" * (4-len(item[self.index])), item[self.index])
        )
    )
... oder nicht?

So, das reicht dann aber erst einmal :-D
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Nobuddy: Okay, Anmerkungen zum Quelltext: Namen mit zwei führenden und folgenden Unterstrichen sind konventionell für ”magische” Objekte reserviert, die das Verhalten von Objekten im Zusammenhang mit der Sprache beeinflussen. Bei Modulnamen wäre das zum Beispiel das `__builtin__`-Modul, das die Besonderheit aufweist, dass die Namen darin überall zur Verfügung stehen, auch ohne dass man es explizit importieren muss.

Sternchenimporte sollte man vermeiden, weil man dann schnell die Übersicht verliert was woher kommt, und die Gefahr von Namenskollisionen wächst.

Klassennamen werden üblicherweise in MixedCase benannt. Der `groupindex` müsste also `GroupIndex` heissen. Allerdings sehe ich da nicht warum das überhaupt eine Klasse sein muss. Eine Funktion hätte es hier auch getan. Klassen die nur aus einer `__init__()` und einer einzigen weiteren Methode bestehen, sind oft ein Hinweis darauf, dass es sich gar nicht wirklich um eine Klasse handelt, sondern nur um eine Funktion, die sich als Klasse verkleidet hat. ;-)

Zusätzlich zu dem was Hyperion über die ``if``\s gesagt hat ist der zweite ``if``-Block eine Ebene höher sogar noch eine Spur unsinniger:

Code: Alles auswählen

                if item[self.base1] != '' and item[self.index] == '':
                    if len(item[self.index]) == 0:
                        w = '0000{}'.format(item[self.index])
                        zeile = (item[self.base1], w)
                        table.append(zeile)
Im zweiten Teilausdruck wird geprüft ob ``item[self.index]`` eine leere Zeichenkette ist. Dann wird im Erfolgsfall geprüft ob die Länge der leeren Zeichenkette auch ganz wirklich und wahrhaftig 0 ist. Um dann die leere Zeichenkette von der wir jetzt auch ganz sicher sein können, dass sie die Länge 0 hat, in eine andere Zeichenkette hinein zu formatieren. Argh!

Letztendlich kann man diese ganzen Zweige mit der `zfill()`-Methode auf Zeichenketten ersetzen.

Im nächsten Teilabschnitt in der Funktion bei der ``while True``-Schleife fällt auf das in jedem Schleifendurchlauf der Wert von `table` in ein Tupel umkopiert wird. `table` wird aber innerhalb der Schleife gar nicht verändert, damit ist das völlig sinnfrei. Auch ist der Name `tablewert` irreführend, da Wert eher nach einem Skalarwert, denn nach einer Liste klingt.

Wenn man diesen überflüssigen Namen entfernt, steht der komplette Körper der ``while``-Schleife in einem ``try``/``catch``-Block der die Schleife beendet. Womit man den auch *um* die Schleife legen könnte.

Die Berechnung von `y` kann man mit `chain()` und `count()` aus dem `itertools`-Modul kompakter und mit einer ``for``-Schleife schreiben.

Letztendlich ist die Frage ob `table` hier die passenste Datenstruktur ist, und ob man nicht gleich mehr an der Eingabe hätte filtern können und eine Struktur hätte aufbauen können bei der man nicht mehrfach mit der `max()`-Funktion alle Elemente durchgehen muss. Der `sorted()`-Aufruf ist auch überflüssig.

Mir fällt auch gerade auf, dass die Werte, die Du als Zeichenkette mit 0en aufgefüllt hast im weiteren Verlauf immer als Zahlen behandelt/umgewandelt werden. Womit dieses Auffüllen komplett unsinnig wird. An *der* Stelle sollte man die in Zahlen umwandeln, statt das später immer wieder zu tun.

Danach wird die Eingabedatei erneut gelesen. Hätte man das nicht schon in der ersten Schleife erledigen können!?

Die letzte ``for``-Schleife kann man auch vereinfachen. Anstelle der zwei ``if``-Abfragen mit genau entgegengesetzten Bedingungen, aber fast identischen Körpern kann man hier das optionale Argument von `dict.get()` verwenden um ohne ``if`` auszukommen.

Zu guter Letzt ist der Vergleich auf die leere Liste am Ende unnötig, weil die leere Liste in diesem Kontext schon als „Falsch” angesehen wird und jede Liste die nicht leer ist als „Wahr”.

Damit wäre ich bei folgendem anstelle der Klasse (wieder ungetestet):

Code: Alles auswählen

def get_num(filename, base1, base2, index, output1, output2):
    table = set()
    daten = list()
    basisdaten = set()
    with open(filename, 'r') as infile:
        for row in my_reader(infile):
            if row[base1]:
                table.add((row[base1], int(row[index])))
    
            (
                basisdaten.add if row[base1] == output1
                    and row[base2] == output2 else daten.append
            )((row[base1], row[base2], row[index]))
    
    vergleich = dict()
    try:
        for i in chain(range(1, 21), (i * 10 for i in count(3))):
            key, value = max(x for x in table if int(x[base1]) == i)
            vergleich[key] = value
    except ValueError:
        pass        # Ignored intentionally.

    for row in sorted(basisdaten):
        if row[base1] != '' and row[index] == '':
            daten.append(
                (
                    row[base1],
                    row[base2],
                    vergleich.get(row[base1], 0) + 1
                )
            )

    if daten:
        write_csv(filename, daten)
Für die zweite Funktion ist es mir jetzt ein wenig zu spät geworden. Ausserdem erschreckt es mich ein wenig, dass Du dort die gleiche Eingabedatei schon wieder mehrfach liest und in einer der Schleifen anscheinend sogar in die Datei die Du dort in einer Schleife mehrfach verarbeitest, während der Verarbeitung schreibst. Das sieht alles sehr gruselig, unübersichtlich, und wahrscheinlich auch ineffizient aus.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
Danke für Euren Input und eure Geduld, ohne dies würde ich auf der Stelle treten.
Anhand der letzten Posts von Hyperion und BlackJack, ist mir vieles bewußt geworden und das ich auch gerne annehme und umsetzen möchte.

Zuvor noch eine kurze Rückmeldung für BlackJack.
BlackJack hat geschrieben:@Nobuddy: `codecs.open()` kann ich nicht so ganz nachvollziehen. Wenn das so funktioniert, dann enthalten die Daten nichts ausserhalb von ASCII. Dann sehe ich hier aber auch keinen Grund sie zu dekodieren.
Das habe ich irgendwo aufgeschnappt.
Wäre dies so richtig?

Code: Alles auswählen

with codecs.open(liste_hersteller_leerzeichen_path, "r", "utf8") as infile:
    reader = csv.reader(infile, delimiter="\t", quotechar="^")
Oder liege ich da noch immer falsch?
BlackJack hat geschrieben:Die Zeile mit dem `csv.reader()`-Aufruf wiederholt sich öfter, da könnte man mit `functools.partial()` eine eigene Funktion für erstellen, damit man die gleichbleibenden Argumente nur einmal im Quelltext schreiben muss.

Mit Generatorausdrücken könnte man einige der Schleifen kompakter schreiben.
Das kenne ich noch nicht, werde es mir anhand von Deinem Beispielcode anschauen.
BlackJack hat geschrieben:`artikelvergleich` benutzt die falsche Datenstruktur. Wenn Schlüssel und Wert eines Wörterbuchs jeweils immer gleich sind, dann will man eigentlich eine Menge, also den Datentyp `set` verwenden.
Ich vergleiche hier den Schlüsselwert und wenn dieser nicht vorhanden ist, packe ich die Werte aus 'zeile' in eine Liste zur Weiterverarbeitung. Deinen Vorschlag werde ich versuchen umzusetzen.
BlackJack hat geschrieben:Vergleiche von Ergebnissen von Operationen die selbst einen Wahrheitswert liefern mit literalen Wahrheitswerten sind überflüssig. Man muss beim Ergebnis von `startswith()` nicht noch einmal explizit überprüfen ob das ``True`` war. Denn wenn es das war, dann ergibt der Vergleich ja auch wieder nur ``True`` als Ergebnis.
Bin mir jetzt da nicht sicher, aber ich möchte ja das was wahr ist in ein Dictionary packen.
BlackJack hat geschrieben:Bei `herstellervergleich` nutzt Du nirgends, dass das ein Wörterbuch ist. Also könnte man das im Grunde auch in einer Liste speichern. Entweder sofort oder über den Umweg eines Wörterbuchst wenn die Schlüssel in der Eingabedatei mehrfach vorkommen könnten. Dann merkt man später aber auch dass die generischen Namen `key` und `value` nicht mehr wirklich stimmen. Statt dem `item` und der Indexerei wären Namen für die einzelnen Elemente auch schöner, weil verständlicher.
`herstellervergleich` verwende ich in der for-Schleife. Eine Liste könnte man hier auch verwenden, für mich war es aber mit dem Dictionary einfacher.
BlackJack hat geschrieben:Beim Befüllen/Erstellen von `datenvergleich` kann es übrigens passieren, dass ``item[3] == 'LIEFERANT'`` wahr ist, aber nichts in `herstellervergleich` gefunden wird. Das würde dann einfach ignoriert. Ist das gewollt?
Deswegen habe ich das Konstrukt

Code: Alles auswählen

datenvergleich = {}
    for item in artikelneu:
        if item[3] == 'LIEFERANT':
            for key, value in herstellervergleich.iteritems():
                if item[2].startswith('{} '.format(key)) == True:
                    datenvergleich[item[0], item[1]] = value
                    break
so gewählt. Alles was wahr ist kommt in das Dictonary, welches dann zur Weiterverarbeitung genuzt wird.
BlackJack hat geschrieben:Der letzte Schleifenkörper ist nicht optimal aufgebaut. Operationen sollte man wenn möglich nicht wiederholen wenn man weiss, dass das gleiche Ergebnis heraus kommt. *Ein* ``datenvergleich.get()`` sollte genügen. Gemeinsamkeiten in beiden Zweigen in einem ``if``/``else``-Konstrukt sollte man dort nach Möglichkeit heraus ziehen. In beiden Fällen wird etwas an `datenneu` angehängt.
Das werde ich mal testen.
BlackJack hat geschrieben:Es wird dort auch mehrfach der Name `zeile` an Werte mit völlig unterschiedlicher Bedeutung gebunden. Das ist verwirrend.
Dies hat ja aber nur im jeweiligen Bereich Gültigkeit, daher habe ich mir keine Mühe gemacht, andere Namen dafür zu verwenden.
BlackJack hat geschrieben:Wenn man sich anschaut was mit `item`, `b` und `zeile` in den beiden Zweigen passiert, ist das auch alles viel zu umständlich. Wenn man das vereinfacht, kann man sich den ``else``-Zweig sogar komplett sparen.
Ist bestimmt so, aber ich muß ja auch verstehen, was ich mache und wenn mein Wissen da noch sehr lückenhaft ist, kann ich nur das umsetzen was ich auch kann.

Dein Konstrukt werde ich mir jetzt genauer anschauen.

Grüße Nobuddy
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

BlackJack, Dein Konstrukt funktioniert prima. :wink:

Mußte nur eine kleine Korrektur machen, was Du anhand meiner Daten nicht wissen konntest.

Code: Alles auswählen

#!/usr/bin/python3
# -*- coding: utf-8 -*-

### Import Module
from __modul_base__ import *
from __modul_write__ import *
from __modul_files__ import *
from functools import partial

my_reader = partial(csv.reader, delimiter='\t', quotechar='^')


def main():
    print('')
    print('>>> Überprüfe Benennungsspalte nach Hersteller, bei Herstellernamen \'LIEFERANT\'.')
    #
    # Herstellerliste:
    # Key, Value.
    #
    with open(liste_hersteller_leerzeichen_path, 'r') as infile:
        herstellervergleich = [(r[1], r[0]) for r in my_reader(infile)]
    #
    # Verwende aus Hauptarbeitsliste zum Vergleich:
    # Lieferant und Artikelnummer.
    #
    with open(quellliste_path, 'r') as infile:
        artikelvergleich = set(tuple(r[:2]) for r in my_reader(infile))
    #
    # Basisliste für aktuelle Lieferantendaten.
    #
    with open(base_daten_path, 'r') as infile:
        #
        # Überprüfe neue Datensätze:
        # Lieferant, Artikelnummer, Benennung und Hersteller.
        #
        artikelneu = (
            r[:5]
            for r in my_reader(infile)
            if tuple(r[:2]) not in artikelvergleich
        )
        #
        # Überprüfe, wenn Hersteller ist 'LIEFERANT'
        # in Benennung nach Herstellernamen.
        #
        datenvergleich = dict()
        for lieferant, artikelnummer, benennung, beschreibung, hersteller in artikelneu:
            if hersteller == 'LIEFERANT':
                for key, value in herstellervergleich:
                    if benennung.startswith(key + ' '):
                        datenvergleich[lieferant, artikelnummer] = value
                        break

    with open(base_daten_path, 'r') as infile:
        wert = 0
        datenneu = list()
        for row in my_reader(infile):
            value = datenvergleich.get(tuple(row[:2]))
            if value is not None:
                row[4] = value
                wert += 1
            datenneu.append(tuple(row))

    if wert > 0:
        daten = sorted(set(datenneu))
        write_csv(base_daten_path, daten)
        if wert == 1:
            print('>>>>>> Bei ' + str(wert) + ' Datensatz wurde der Herstellername aktualisiert.')
            print('')
        else:
            print('>>>>>> Bei ' + str(wert) + ' Datensätze wurde der Herstellernamen aktualisiert.')
            print('')
    else:
        print('>>>>>> Überprüfung agbeschlossen.')


if __name__ == '__main__':
    main()
Das Konstrukt, werde ich noch einige male durchgehen müssen, damit ich alles verstehe und nachvollziehen kann.

Danke und Grüße
Nobuddy
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
durch Eure Hilfe, komme ich mehr in die Materie hinein, verstehe Eure kleine Schubser in die richtige Richtung und versuche dies auch umzusetzen. Bei manchem hapert es noch ein wenig, bin aber auch da zuversichtlich. :wink:

Ich habe zu diesem Teilkonstrukt von BlackJack, eine Frage:

Code: Alles auswählen

    with open(base_daten_path, 'r') as infile:
        #
        # Überprüfe neue Datensätze:
        # Lieferant, Artikelnummer, Benennung und Hersteller.
        #
        artikelneu = (
            r[:5]
            for r in my_reader(infile)
            if tuple(r[:2]) not in artikelvergleich
        )
        #
        # Überprüfe, wenn Hersteller ist 'LIEFERANT'
        # in Benennung nach Herstellernamen.
        #
        datenvergleich = dict()
        for lieferant, artikelnummer, benennung, beschreibung, hersteller in artikelneu:
            if hersteller == 'LIEFERANT':
                for key, value in herstellervergleich:
                    if benennung.startswith(key + ' '):
                        datenvergleich[lieferant, artikelnummer] = value
                        break
und zwar bei

Code: Alles auswählen

artikelneu = (
    r[:5]
Wie ist das, wenn ich jetzt nur r0, r1, r4, r25 für artikelneu brauche?

Habe da noch keine Lösung gefunden.

Grüße Nobuddy
BlackJack

@Nobuddy: Dann kann man das nicht mehr ganz so kompakt schreiben. Es würde sich eine „list comprehension” (LC) anbieten, oder man erstellt sich eine Funktion mit `operator.itemgetter()` um die Elemente von `r` abzufragen.

Code: Alles auswählen

artikelneu = (
    [r[i] for i in [0, 1, 4, 25]]
    # ...
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Manchmal ist der einfachste Weg auch der kürzeste:

Code: Alles auswählen

[r[i] for i in [0, 1, 4, 25]]
vs.
[r[0], r[1], r[4], r[25]]
Stefan
BlackJack

@sma: Wobei man nicht immer den kürzesten wählen sollte. Ist hier Geschmackssache, aber ich finde den Namen `r` nicht wiederholen zu müssen und die Indizes als einen Teilausdruck einfach herausziehen zu können schöner. Zumal das mit dem Kürzer schnell kippen kann wenn man einen längeren Bezeichner verwendet und/oder die Liste der Indizes länger wird.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Es ist aber für einen Anfänger viel leichter zu verstehen, selbst wenn der Ausdruck durch einen längeren (besseren) Variablennamen doch länger wird.

Stefan
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,
das von sma sieht auf den ersten Blick einfacher aus, aber das von BlackJack ist eigentlich auch nicht schwer zu verstehen.

Hatte zwar zuerst das Problem, wie der Eintrag in der zweiten for-Schleife auszusehen hat.
Habe es zuerst mit 'for r in my_reader(infile)' dann mit 'for i in my_reader(infile)' versucht, was ja eigentlich Quatsch ist.
Bis ich dann da drauf kam.

Code: Alles auswählen

        artikelneu = (
            [r[i] for i in [0, 1, 4, 5, 25]]
            for r in my_reader(infile)
            if tuple(r[:2]) not in artikelvergleich
        )
Zum Verständnis!
Ist meine Annahme richtig, daß die Spaltendefinition von '[r for i in [0, 1, 4, 5, 25]]' in 'my_reader(infile)' übernommen wird?

Grüße Nobuddy
Antworten