Zwischensummen bilden aus Liste

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.
Antworten
mali321
User
Beiträge: 4
Registriert: Samstag 7. Mai 2022, 11:04

Ich habe eine Tabelle und möchte eine Zwischensumme bilden. Dabei soll sich der Code an der ersten Spalte richten. Solange die erste Spalte einen Wert x hat, soll er aus der Spalte y die Werte aufaddieren. Wenn x1 zu x2 wird soll er eine neue Summe bilden. Anschliessend soll er die Summen mit Summe für x printen.

Ich kann zwar die Summe mit while bilden aber nur bis x1. Danach geht es nicht mehr weiter.

Code: Alles auswählen

import csv

with open(r"C:\Users\nobody\Desktop\x.csv") as einzulesende_datei:
    datei=csv.reader(einzulesende_datei, delimiter=";")
    liste=list(datei)
    liste.pop(0)## um Header zu entfernen
    zähler=0
    ergebnis=[]
    vorgänger=None
    while zähler<len(liste):
        zähler+=1
        aktueller=liste[zähler-1][0]
        if aktueller==vorgänger:
            continue
        if zähler>len(liste):
            break
        zahl=liste[zähler-1][18].replace(".","")
        zahl_bereinigt_1=zahl.replace(",",".")
        zahl_bereinigt_2=zahl_bereinigt_1.replace("-","")
        zahl_liste=float(zahl_bereinigt_2)
        ergebnis.append(zahl_liste)
        vorgänger=aktueller
        print("Ergebnis für",liste[zähler-1][0],sum(ergebnis)/12)
Muss da vielleicht eine Rekursion rein?
mali321
User
Beiträge: 4
Registriert: Samstag 7. Mai 2022, 11:04

Vielleicht wird es so verständlicher:

Artikel nr. Menge
3 20
3 40
3 60
6 11
8 31
8 22
8 10

gewünschtes Ergebnis:

print("Summe Artikelnr. {0} ist{1}".format(<artikelnr.>,<summe der mengen>)

mit einer einfachen for-Schleife komme ich nicht weiter, weil die ja durch alle Artikel geht. Sie muss aber abbrechen, sobald sich die artikel-nr. ändert.

Dann muss eine neue Summe gebildet werden....

Ich habe es mit collection.defaultdict probiert.

Ich komme nicht darauf....

Code: Alles auswählen

import csv
from collections import defaultdict

with open(r"C:\Users\nobody\Desktop\x.csv") as einzulesende_datei:
    datei=csv.reader(einzulesende_datei, delimiter=";")
    liste=list(datei)
    liste.pop(0)
    neue_liste_artikel=[]
    neue_liste_mengen=[]
    for element in liste:
            neue_liste_artikel.append(element[0])
            neue_liste_mengen.append(element[len(element)-1])
            neue_liste_mapping=zip(neue_liste_artikel,neue_liste_mengen)
    d=defaultdict(list)
    for k,v in neue_liste_mapping:
        d[k].append(v)
    for d[k] in d:
        print(sum(float(element.replace(",",".")) for element in d.values()))
Wo ich Probleme habe ist, dass er eine neue Runde starten soll, sobald er auf einen anderen Artikel stosst....
Benutzeravatar
sparrow
User
Beiträge: 4186
Registriert: Freitag 17. April 2009, 10:28

Das klingt wie eine perfekte Aufgabe für pandas.
Das Einarbeiten in das Modul wird dir bei solchen Aufgaben sehr helfen.
Benutzeravatar
__blackjack__
User
Beiträge: 13071
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mali321: `defaultdict` kannst Du verwenden wenn die Reihenfolge der Ergebnisse nicht zwingend erhalten bleiben muss. Allerdings macht die letzte ``for``-Schleife keinen Sinn. Was denkst Du denn was ``for d[k] in d:`` bedeutet? Und *in* der Schleife ist `d.values()` sicher nicht das was Du denkst was es ist, denn die Elemente haben keine `replace()`-Methode.

Es kann auch sein, dass es Dir einfacher fallen würde wenn die Namen nicht fast durchgehend schlecht gewählt sind. Nichtssagende Namen wie `liste` oder gar einbuchstabige Namen helfen nicht beim Verständnis was für Werte das sind und welche Bedeutung die im Programm haben. Auch sollten Grunddatentypen nicht Bestandteil von Namen sein. Bei `neue_liste_mapping` ist `liste` auch falsch, denn `zip()` liefert als Ergebnis keine Liste. Auch der Präfix `neue_` bei den Namen macht nicht wirklich sinn, denn irgendwie ist ja jedes Objekt das man erstellt Neu, so ein Namenszusatz macht also nur Sinn wenn man auch eine ”alte” Liste mit gleichartigen Werten hat gegen die man die neue Liste abgrenzen möchte.

Aber mal von oben angefangen: Beim öffnen der CSV-Datei fehlt die Angabe der verwendeten Kodierung und das `newline`-Argument wie in der Dokumentation vom `csv`-Modul gefordert. Das kann so funktionieren, muss es aber nicht.

`datei` ist ein schlechter Name für ein CSV-Reader-Objekt weil das keine Datei ist, und dementsprechend auch nicht die Methoden bietet, die man von einem Dateiobjekt erwartet. Wenn man das beispielsweise `rows` nennt, kann man `einzulesende_datei` dafür einfach nur `datei` nennen. In den zwei direkt aufeinander folgenden Zeilen wo dieser Name verwendet wird, sieht man ja sehr leicht, dass die Datei zum lesen geöffnet wurde, auch ohne dass das im Namen steht.

Alle Datensätze erst in eine Liste einzuslesen um dann die Kopfzeile mit ``pop(0)`` zu entfernen ist ineffizient, sowohl was den Speicherverbrauch angeht, als auch von der Laufzeit. Man würde hier einfach die Kopfzeile direkt aus dem Reader-Objekt mit `next()` überspringen.

Es macht keinen Sinn `neue_liste_mapping` in jedem Schleifendurchlauf erneut zu erstellen und am Ende nur den letzten zugewiesenen Wert zu verwenden. Was auch zusätzlich noch die Gefahr birgt, dass bei leerer Eingabedatei dieser Name gänzlich undefiniert bleibt und der Code danach unweigerlich in eine Ausnahme läuft.

Aber es macht eigentlich auch gar keinen Sinn erst die Artikelnummern in einer und die Mengen in eine zweite Liste zu stecken, nur um die dann gleich danach mit `zip()` zusammenzuführen. Man könnte da gleich eine Liste mit Paaren erstellen.

Und auch die kann man sich sparen wenn die dann nur wieder in einer weiteren Schleife verwendet wird um die ganzen Werte in ein `defaultdict` zu stecken.

Und auch das `defaultdict` das Artikelnummer auf Mengen abbildet ist zu umständlich wenn im nächsten Schritt dann die Mengen pro Artikelnummer aufsummiert werden. Das aufsummieren hätte man ja auch *geich* machen können, statt da erst Listen pro Artikelnummer aufzubauen.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import csv
from collections import defaultdict


def main():
    with open(
        R"C:\Users\nobody\Desktop\x.csv", encoding="ascii", newline=""
    ) as datei:
        rows = csv.reader(datei, delimiter=";")
        next(rows)  # Skip header.

        article_number_to_amount = defaultdict(float)
        for article_number, *_, amount_text in rows:
            article_number_to_amount[article_number] += float(
                amount_text.replace(",", ".")
            )

    for article_number, amount in article_number_to_amount.items():
        print(f"{article_number}: {amount:.3f}")


if __name__ == "__main__":
    main()
Wobei das nicht die Speichereffizienteste Lösung ist erst ein Wörterbuch aufzubauen und es dann auszugeben, denn eigentlich braucht man ja nur die Summe für die aktuell bearbeitete Artikelnummer und wenn man die fertig berechnet und ausgegeben hat, kann man sowohl Artikelnummer als auch Summe wieder ”vergessen”. Das wäre dann eine Lösung mit `itertools.groupby()`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
mali321
User
Beiträge: 4
Registriert: Samstag 7. Mai 2022, 11:04

wow.
danke.

ich werde es ausprobieren....

was macht eigentlich "*_", ist das nur zum packen von etwas, also 0 bis unendlich viele werte in _?

sehr nice. eigentlich war es einfach und ich habe mir so den kopf zerbrochen. wollte es mit while lösen rekursion oder itertools.count(), umd die schleife bei einem bestimmten count neu zu starten etc.... :lol:
Benutzeravatar
__blackjack__
User
Beiträge: 13071
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mali321: Sternchen auf der linken Seite bei Zuweisungen vor Namen wird in PEP 3132 – Extended Iterable Unpacking erklärt. Also unendlich viele Werte geht nicht, da wird das Programm irgendwann mit einem `MemoryError` aussteigen wenn der Speicher voll ist. 😉

So sähe eine Lösung aus die nicht erst die Ergebnisse im Speicher aggregiert, sondern jedes Zwischenergebnis sofort ausgibt (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import csv
from itertools import groupby
from operator import itemgetter


def main():
    with open(
        R"C:\Users\nobody\Desktop\x.csv", encoding="ascii", newline=""
    ) as datei:
        rows = csv.reader(datei, delimiter=";")
        next(rows)  # Skip header.

        for article_number, group in groupby(rows, itemgetter(0)):
            amount = sum(float(row[-1].replace(",", ".")) for row in group)
            print(f"{article_number}: {amount:.3f}")


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten