Mehrere Bedinungen in if-Verzweigung

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
PyFlo
User
Beiträge: 9
Registriert: Montag 21. Februar 2022, 14:11

Hallo zusammen,
ich habe eine grundlegende Frage zu if-Verzweigungen mit mehreren Bedingungen.
Und zwar möchte ich im folgenden Beispiel überprüfen, ob mehrere Zahlen (x, y und z) in einer bestimmten Range liegen.
Wenn ich den Code wie folgend schreibe, wird aber nur z in der Range überprüft.

Code: Alles auswählen

x, y, z = 25, 9, 19

if x and y and z in range(0,20):
    print("True")
else:
    print("False")
Muss ich in diesem Beispiel dann jede Variable auf die Range anwenden, also:

Code: Alles auswählen

if x in range(0,20) and y in range(0,20) and z in range(0,20):
oder gibt es hierfür eine "bessere" Lösung.

Vielen Dank im Voraus
PyFlo
User
Beiträge: 9
Registriert: Montag 21. Februar 2022, 14:11

Alternativ könnte man die Zahlen ja auch in eine Liste packen und diese über eine for-Schleife durchlaufen, also:

Code: Alles auswählen

x, y, z = 25, 9, 19
numbers = [x, y, z]
for number in numbers:
	if number in range(0, 20):
		print("True")
	else:
		print("False")
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Beim Beispiel `x and y and z in range(0,20)` wird zuerst `x` geprüft, ob das "wahr" ist, also hier != 0. Wenn das der Fall ist (bei and kann man schon beim ersten False abbrechen) wird geprüft ob y wahr ist und falls ja, wird geprüft ob z eine ganze Zahl zwischen 0 und 20 ist.

Für Dein konkretes Problem gibt es viele mögliche Lösungen. Falls x, y und z immer Ganzahlen sind, würde ich das vorschlagen:

Code: Alles auswählen

if 0 <= min(x, y, z) and max(x, y, z) < 20:
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

Oder auch mit `all`:

Code: Alles auswählen

if all(number in range(20) for number in numbers):
    print("all in range")
PyFlo
User
Beiträge: 9
Registriert: Montag 21. Februar 2022, 14:11

@Sirius3, @narpfel: Vielen Dank für eure Lösungsvorschläge!
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Noch besser, weil O(n) statt O(n²):

Code: Alles auswählen

set(numbers).issubset(range(20))
Ungetestet.

Siehe auch hier: https://docs.python.org/3/library/stdty ... t.issubset
In specifications, Murphy's Law supersedes Ohm's.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

@pillmuncher: `x in range(N)` ist O(1), damit ist mein und Sirius3s Lösungsvorschlag jeweils O(K) (wenn K die Länge von `numbers` ist), deiner ist allerdings O(K + N).
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@narpfel: Das ist AFAIK nicht garantiert. Ich kann mich da dunkel an eine Diskussion erinnern über eine Python-Implementierung die beim `range()`-Ergebnis keine eigene `__contains__()`-Implementierung hat(te). Ich würde das mit ``0 <= value < 20`` testen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vielleicht noch zur Erklärung: Das range() erstellt ja seit Python 3.x keine Liste mehr, sondern ein spezielles range-Objekt. Dieses kennt sein Minimum und Maximum. Für Ganzzahlen (PyLong) ist der Code so optimiert, dass ein ``x in range(N)`` einem ``min(range(N)) <= x <= max(range(N))`` entspricht. Ein Set würde dieses Verhalten quasi kaputt machen. Die beiden performanteren Vorschläge übertragen den "Min-Max-Trick" nochmals auf die zu testenden Zahlen. Der Overhead für das Set entfällt dabei komplett. Daher kommt die entsprechend bessere Laufzeit zustande. An sich ist halt die Frage, warum man dafür überhaupt ein range-Objekt benutzt, wo man doch einfach direkt einen Vergleich mit dem Minimum und Maximum machen könnte...

Auszug aus rangeobject.c:

Code: Alles auswählen

/* Assumes (PyLong_CheckExact(ob) || PyBool_Check(ob)) */
static int
range_contains_long(rangeobject *r, PyObject *ob)
{
    PyObject *zero = _PyLong_GetZero();  // borrowed reference
    int cmp1, cmp2, cmp3;
    PyObject *tmp1 = NULL;
    PyObject *tmp2 = NULL;
    int result = -1;

    /* Check if the value can possibly be in the range. */

    cmp1 = PyObject_RichCompareBool(r->step, zero, Py_GT);
    if (cmp1 == -1)
        goto end;
    if (cmp1 == 1) { /* positive steps: start <= ob < stop */
        cmp2 = PyObject_RichCompareBool(r->start, ob, Py_LE);
        cmp3 = PyObject_RichCompareBool(ob, r->stop, Py_LT);
    }
    else { /* negative steps: stop < ob <= start */
        cmp2 = PyObject_RichCompareBool(ob, r->start, Py_LE);
        cmp3 = PyObject_RichCompareBool(r->stop, ob, Py_LT);
    }
   /* ... */
}
Genau genommen ist dem range-Objekt nicht das MInimum und Maximum bekannt, sondern lediglich die Werte für start und stop. Durch die Verzweigung auf Grundlage des step-Arguments läuft es aber darauf hinaus.
Zuletzt geändert von snafu am Donnerstag 22. September 2022, 18:45, insgesamt 2-mal geändert.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

@__blackjack__: Die Dokumentation ist da relativ klar:
https://docs.python.org/3/library/stdtypes.html#range hat geschrieben: Changed in version 3.2: [...] Test int objects for membership in constant time instead of iterating through all items.
Das liest sich für mich wie eine Garantie.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@narpfel: Jo, aber erst sein Python 3.2. Die Diskussion an die ich mich erinnerte war zeitlich deutlich davor. Ich glaube das war so zu Python 2.5 Zeiten. 😎
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich würde range() ja nur zum Iterieren benutzen. Bezüglich __contains__() erschließt sich mir der Vorteil da nicht wirklich.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

Ich finde `42 in range(100)` ist lesbarer als `0 <= 42 < 100`. So cool chained comparison operators auch sind, `42 in range(100)` ist für mich einfach näher an dem, was ich eigentlich ausdrücken möchte: „Ist 42 in diesem Intervall enthalten?“, nicht „Ist 0 kleinergleich als 42 und 42 kleiner als 100?“

Aber vielleicht bin auch einfach komisch. 🤷
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@snafu: mit len, Index-Zugriff, `in` etc. verhält sich das `range`-Objekt in vielen Fällen wie eine Liste und kann so in Funktionen benutzt werden, die eigentlich eine Liste erwarten.

@narpfel: was erwartest Du bei `13.5 in range(100)`?
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Aha. Hab ich wieder was gelernt. Danke schön.

Tatsächlich finde ich

Code: Alles auswählen

if all(number for number in numbers if 0 <= number < 20)
am klarsten. Es wird genau ausgedrückt, was man haben möchte.
In specifications, Murphy's Law supersedes Ohm's.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

@pillmuncher: Mit `numbers = [0]` funktioniert das nicht?

@Sirius3: `False`, weil `in range(N)` ein „ist eine der ersten N natürlichen Zahlen“ ist und `13.5` keine natürliche Zahl ist.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@pillermulcher: der Ausdruck ist fehlerhaft, denn wenn eine Zahl 0 ist, funktioniert das nicht.
Noch klarer und zusätzlich richtig ist es, wenn die Bedingung vorne steht:

Code: Alles auswählen

if all(0 <= number < 20 for number in numbers):
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich finde, dass gerade der verkettete Vergleich das mögliche Vorhandensein von einer Zahl in einem Intervall sehr gut ausdrückt. Andererseits heißt Range halt Bereich und ist somit etwas "sprechender". Das ist wohl eine Geschmacksfrage, was man da lieber verwendet.

Dass sich ein range-Objekt im Sinne des Duck Typings möglichst wie eine Liste verhält, leuchtet mir nun ein.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

narpfel hat geschrieben: Donnerstag 22. September 2022, 19:33 @Sirius3: `False`, weil `in range(N)` ein „ist eine der ersten N natürlichen Zahlen“ ist und `13.5` keine natürliche Zahl ist.
Und auch einfach, weil sich Sequenzen in Python so verhalten, dass kein TypeError geworfen wird (falls das der Gedanke dahinter war). Mit range() kriegt man quasi ein imitiertes Tupel (da unveränderbar), das auf Ganzzahlen beschränkt ist. Und die geben False für jedes Objekt (egal welchen Typs) zurück, wenn es nicht enthalten ist.
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Ja, mein Code war fehlerhaft. Ich sollte nicht neben dem Fernsehen programmieren. Der Code von Sirius3 ist der richtige:

Code: Alles auswählen

if all(0 <= number < 20 for number in numbers):
In specifications, Murphy's Law supersedes Ohm's.
Antworten