Auswahl von Listenwerten nach Priorität

mit matplotlib, NumPy, pandas, SciPy, SymPy und weiteren mathematischen Programmbibliotheken.
Antworten
Alex_Ing
User
Beiträge: 4
Registriert: Dienstag 19. Juli 2022, 09:19

Hallo zusammen,

ich starre jetzt schon sehr lange den Bildschirm an auf der Suche nach einer Lösung für mein Problem, auch langes Googlen und nach Tutorials suchen hat mir noch nicht das Richtige geliefert. :roll:

Ich habe eine Liste von Zeitpunkten und eine Liste in gleicher Länge mit Werten von 0 bis 17, die stellvertretend für die Priorirät dieser Zeitpunkte stehen. ( 0 = höchste Priorität, 17 = am besten gar nicht verwenden)
Es geht um Börsenstrompreise und wann ein Fahrzeug laden sollte und wann nicht.
Es wird dann eine Liste für jedes Auto erstellt, die "Ladestunden" heißt mit 0 und 1 für jeden Zeitpunkt. Das ganze findet in einer Klasse statt, deshalb überall "self".

Hier mein bisheriger Code-Ausschnitt dazu:

Code: Alles auswählen

def wähleLadestunden(self):
        """Diese Funktion weist jedem Auto die Stunden zu, zu denen es laden
        kann entsprechend der Priorisierung der günstigsten Stunden. Wenn
        genügend Stunden zum Aufladen der verbrauchten Energie gefunden wurde,
        wird keine weitere Ladestunde hinzugefügt. Es soll die Stunde mit dem
        niedrigsten Wert aus der Liste Prio gewählt werden. Dieser darf bei der
        Auswahl der nächsten Stunde nicht nochmal wählbar sein.
        """
        self.Ladestunden = []   # Liste, ob ein Fz zu dieser Zeit lädt
        for p in Prio:
            if p < 10:
                if self.Ladedauer > 0:
                    self.Ladestunden.append(1)
                    self.Ladedauer -= 25
                else:
                    self.Ladestunden.append(0)
            else:
                self.Ladestunden.append(0)
        return self.Ladestunden
        
Soweit funktioniert das auch. Nur möchte ich anstelle der Bedingung p < 10 gerne, dass immer der Zeitpunkt mit dem niedrigsten Prio-Wert ausgesucht wird. Also erst alle Zeitpunkte mit dem Wert 0, dann alle mit dem Wert 1 usw... Immer, wenn ein Zeitpunkt ausgewählt wurde, kann er natürlich nicht nochmal wählbar sein.
Wenn ein Zeitpunkt ausgewählt wurde, wird der Zeitraum entsprechend von der benötigten Ladedauer des Fahrzeugs abgezogen, deshalb -25.

Das sind alle relevanten Hintergründe. Vielleicht hat jemand eine Idee oder einen Link zu einem ähnlichen Problem.

Danke im Voraus! :)
__deets__
User
Beiträge: 14533
Registriert: Mittwoch 14. Oktober 2015, 14:29

So wie du das Problem beschreibst, ist das doch trivial durch eine Sortierung nach Prioritaet loesbar. Unter Verwendung einer geeigneteren Datenstruktur:

Code: Alles auswählen

hours_needed = 8
charging_opportunities = [
  # timepoint, priority. Achtung: das sind pseudo-Werte, muss man natuerlich eigentlich datetime fuer nehmen.
  (17:00, 10),
  (18:00, 9),
  (19:00, 17),
   ... ]

charging_points = sorted(changing_opportunities, key=lambda entry: entry[1])[:hours_needed]
Also einfach eine aufsteigend sortierte Liste aller Zeitpunkte nach Prioritaet, und dann eben so viele Stunden davon genommen, wie man braucht.
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Alex_Ing: Noch was zum `self`: Das die Methode `self.Ladestunden` sowohl als Attribut setzt als auch als Rückgabewert hat ist komisch. Ich vermute mal das Attribut sollte nicht sein.

`Prio` dagegen kommt einfach magisch aus dem Nichts: Alles was Funktionen und Methoden ausser Konstanten benötigen sollte als Argument(e) übergeben werden.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Alex_Ing
User
Beiträge: 4
Registriert: Dienstag 19. Juli 2022, 09:19

Ich danke euch beiden für die schnellen Antworten! Ich hätte am Anfang direkt schreiben sollen, dass ich erst seit ein paar Wochen dabei bin. Ich hab jetzt doch unerwarteterweise selbst einen Weg gefunden.

@__deets__: Bei dir sieht es natürlich schicker aus, ich tue mich damit aber noch schwer. Ich brauche die Liste später nochmal und das Format müsste ich dann komplett ändern. Habe ich zwar ausprobiert, habe dann aber wie gesagt eine andere Lösung gefunden.

Code: Alles auswählen

    def wähleLadestunden(self):
        self.Ladestunden = []   # Liste, ob ein Fz zu dieser Zeit lädt
        for t in T:
            self.Ladestunden.append(0)

        p = 0   	# erster Wert für p als Zähler
        while p < 17:
            if self.Ladedauer > 0:
                for t in range(len(T)):
                    if Prio[t] == p:
                        self.Ladestunden[t] = 1
                        self.Ladedauer -= 25
    # mehrere Elemente t mit passendem p, deshalb kann zu viel geladen werden
            p += 1
        return self.Ladestunden

@__blackjack__: Da hast du recht, meine Bezeichnungen sind noch nicht ganz sauber. Das möchte ich auch alles nochmal überarbeiten, falls ich noch Zeit habe. Prio und T werden außerhalb der Klasse festgelegt, den Code habe ich hier im Forum nicht hinzugefügt. Auf Ladestunden beziehe ich mich wieder in einer anderen Klasse und Funktion, deshalb hielt ich das 'self.' für notwendig.

Ich bin mit meiner Lösung nah dran an dem, was ich will. Sicher gibt es programmiertechnisch schönere Lösungen, aber wie gesagt, bin ich noch nicht lange dabei. Ich bin schon froh, wenn ich die Logik aus meinem Kopf in den Code bekomme.

Vielen Dank euch beiden trotzdem!
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Alex_Ing: Weder `Prio` noch `T` sollten irgendwie magisch in der Umgebung existieren. Ausserhalb der Klasse muss hier ja bedeuten global im Modul, dort gehören aber überhaupt keine Variablen hin.

Wenn `Ladestunden` später noch gebraucht wird, dann darf halt der Rückgabewert nicht sein. Der Aufrufer hat ja auch ohne den Zugriff wenn das ein Attribut ist. Der Grund warum ich aber eher vermute, dass das kein Attribut sein sollte ist das es ja offenbar nicht in der `__init__()` definiert wird. Attribute sollten aber nach dem Ablauf der `__init__()` alle existieren, vorzugsweise mit einem sinnvollen Wert, denn das ist ja die Aufgabe der `__init__()`, das Objekt in einen benutzbaren Zustand zu versetzen.

In `ladestunden` werden die Werte 0 und 1 gespeichert — dem Kommentar nach sollten das Wahrheitswerte sein und nicht die Zahlen 0 und 1. Python hat da extra einen Datentyp für.

In einer Schleife 0en an eine Liste anhängen wenn man genau weiss wie viele das am Ende werden sollen ist ineffizient.

Die ``while``-Schleife ist eigentlich eine ``for``-Schleife, weil man vorher genau weiss wie oft die durchlaufen wird und wie sich `p` dabei entwickelt.

Statt in der Schleife nur etwas zu machen wenn die Ladedauer noch nicht 0 ist, also gegebenenfalls unnötige Schleifendurchläufe zu machen, würde man da eher ein Schleifenabbruchkriterium formulieren.

``for i in range(len(sequence)):`` nur um dann `i` als Index zu verwenden ist in Python ein „anti-pattern“ weil man direkt über die Elemente von Sequenzwerten iterieren kann. Sollte man *zusätzlich* eine laufende Zahl benötigen gibt es die `enumerate()`-Funktion.

Ungetestet und die beiden magischen Zahlen 17 und 25 sollten Konstanten oder Argumente sein:

Code: Alles auswählen

    def waehle_ladestunden(self, timestamps, priorities):
        ladestunden = [False] * len(timestamps)
        for current_priority in range(17):
            if self.ladedauer <= 0:
                break
            
            for timestamp_index, priority in enumerate(priorities):
                if priority == current_priority:
                    ladestunden[timestamp_index] = True
                    self.ladedauer -= 25
        
        return ladestunden
Und die Ladedauer kann hier deutlich unter 0 fallen — vielleicht sollte man den Test ob noch weiter geladen werden muss an eine andere Stelle verschieben.

Und ein sortieren statt alle Prioritäten nacheinander abzuprüfen hatte __deets___ ja bereits vorgeschlagen.
Zuletzt geändert von __blackjack__ am Dienstag 19. Juli 2022, 14:53, insgesamt 1-mal geändert.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alex_Ing: Variablenamen und Methoden schreibt man komplett klein. Wenn eine Funktion einen Rückgabewert hat und gleichzeitig das Ergebnis in einem Attribut speichert, dann verwirrt das den Leser, weil er sich fragt, was ist die relevante Information.
Globale Variablen darf man nicht verwenden, `Prio` und `T` müssen also entweder in der Klasse definiert sein, oder als Argumente übergeben werden. `T` ist wirklich eine schlechter Variablenname, weil er nichts aussagt. Den Inhalt von T benutzt Du gar nicht.
Deine while-Schleife ist eigentlich eine for-Schleife und sollte deshalb auch so geschrieben werden..

Code: Alles auswählen

    def berechne_ladestunden(self, prios):
        self.ladestunden = [0] * len(prios)

        for p in range(17):
            if self.ladedauer > 0:
                for t in range(len(prios)):
                    if prios[t] == p:
                        self.ladestunden[t] = 1
                        self.ladedauer -= 25
Über einen Index iteriert man nicht, statt dessen kann man hier `enumerate` benutzen. Statt 0 und 1 benutzt man True und False.

Code: Alles auswählen

    def berechne_ladestunden(self, prios):
        self.ladestunden = [False] * len(prios)
        for p in range(17):
            if self.ladedauer > 0:
                for t, prio in enumerate(prios):
                    if prio == p:
                        self.ladestunden[t] = True
                        self.ladedauer -= 25
Die if-Abfrage zur ladedauer ist im Moment am falschen Ort.
Es ist komisch, dass die Ladedauer ein Attribut ist, und während der Berechnung verändert wird.

Viel logischer ist es, zuerst die Zeiten nach Prio zu sortieren und dann die Anzahl der benötigten Zeitabschnitte zu ermitteln:

Code: Alles auswählen

    def berechne_ladestunden(self, times, prios):
        time_slot_prios = sorted(zip(prios, times), reverse=True)
        needed_time_slots = self.ladedauer // 25
        time_slots = set(t for t,p in time_slot_prios[:needed_time_slots])
        self.ladestunden = [t in time_slots[:needed_time_slots] for t in times]
Hier stört noch die magische 25. Woher kommen die?
Antworten