Letzter Index in einer Schleife (out of range)

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.
Nachbar
User
Beiträge: 24
Registriert: Sonntag 10. Juli 2016, 08:12

Hi @ll,

ich habe einen Trick anwenden müssen, um beim Schleifendurchlauf nicht "out of range" zu gelangen. Allerdings glaube ich, dass dieser "Trick"
a) schlechter Programmierstil und
b) gar nicht nötig ist

Ich stehe aber auf dem Schlauch und komme auf nichts anderes. Der "Trick" ist in Zeile 7, wo ich einfach ein weiteres Zeichen an die String-Variable hänge, um einen Index mehr zu haben. Dies ist der Code (wandelt römische in arabische Zahlen um):

Code: Alles auswählen

zahlen = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
zahlzeichen = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
roemisch = ""
i = 0

zahl = raw_input("Zahl: ")
zahl = zahl + "A"
zahl = zahl.upper()

while i < len(zahl) - 1:
    if zahl[i] + zahl[i+1] in zahlzeichen:
        zahlzeichen_index = zahlzeichen.index(zahl[i] + zahl[i+1])
        roemisch = roemisch + zahlen[zahlzeichen_index]
        i = i + 1
    elif zahl[i] in zahlzeichen:
        zahlzeichen_index = zahlzeichen.index(zahl[i])
        roemisch = roemisch + zahlen[zahlzeichen_index]
    i = i + 1
Habt ihr eine Idee?
BlackJack

@Nachbar: Du kannst vorher entweder prüfen ob ``i + 1`` zu gross wäre, oder die Ausnahme behandeln, oder per slicing syntax auf die beiden Zeichen zugreifen, was nicht zu einer Ausnahme, aber potentiell zu einer Zeichenkette führen kann die nur ein Zeichen lang ist. Was aber nichts ausmachen sollte.

`roemisch` ist der falsche Name für die Variable und das dürfte so nicht funktionieren weil Du sie mit einer leeren Zeichenkette initialisierst und dann aber versuchst da Zahlen drauf zu addieren. Das führt zu einer Ausnahme.

Die anderen Namen sind auch nicht wirklich gut gewählt. Und die Werte in den beiden parallelen Listen sollten in einer Datenstruktur stecken: einem Wörterbuch. Statt immer linear nach dem Index in der einen Liste zu suchen um damit dann auf den zugehörigen Wert in der anderen Liste zuzugreifen.
Nachbar
User
Beiträge: 24
Registriert: Sonntag 10. Juli 2016, 08:12

Erstmal danke und vor allem sorry, ich habe ausversehen den Code aus einem Testskript und nicht aus dem fertigen kopiert (daher die "roemisch" Variable). Ist spät und heiß :oops: .
Das mit dem Prüfen ob "i + 1" zu groß wäre hatte ich versucht aber irgendwie nicht hinbekommen. Deinen Vorschlag mit dem Slicing werde ich morgen mal ausprobieren.
Ich hatte auch schon versucht, das Ganze mit einem Dictionary umzusetzen aber auch das ist mir nicht gelungen. Bei der Technik macht mir als Anfänger zum Einen der Zugriff auf einzelne Einträge und gerade auch das Nichtlineare zu schaffen. Was wären denn bessere Variablennamen?
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Lass einfach in Zeile 10 das ``- 1`` weg. Dann musst du auch vorher kein Pseudo-Element dazu nehmen.
BlackJack

@snafu: Das dürfte das Problem eher nicht lösen sondern wenn dann ”verschlimmern”. Das Problem ist ja das in der Schleife nicht nur auf das i-te Element zugegriffen wird, sondern auch auf das i+1-te Element.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Dann könnte man z.B. den `IndexError` am Ende abfangen. Da Exceptions bekanntlich nur dann teuer sind, wenn sie tatsächlich geworfen werden, dürfte das noch die performanteste Lösung sein.
Nachbar
User
Beiträge: 24
Registriert: Sonntag 10. Juli 2016, 08:12

Ich habe nun den Vorschlag mit dem Slicing umgesetzt:

Code: Alles auswählen

zahlen = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
zahlzeichen = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", \
"IV", "I"]
arabisch = 0
i = 0

zahl = raw_input("Zahl ")
#zahl = zahl + "A"
zahl = zahl.upper()

while i < len(zahl) - 2:
    if zahl[i] + zahl[i+1] in zahlzeichen:
        zahlzeichen_index = zahlzeichen.index(zahl[i] + zahl[i+1])
        arabisch = arabisch + zahlen[zahlzeichen_index]
        i = i + 1
    elif zahl[i] in zahlzeichen:
        zahlzeichen_index = zahlzeichen.index(zahl[i])
        arabisch = arabisch + zahlen[zahlzeichen_index]
    i = i + 1

# die letzten beiden Zeichen abgreifen
x = zahl[len(zahl)-2]
y = zahl[len(zahl)-1]

if x+y in zahlzeichen:
    zahlzeichen_index = zahlzeichen.index(x+y)
    arabisch = arabisch + zahlen[zahlzeichen_index]
elif x in zahlzeichen:
        zahlzeichen_index = zahlzeichen.index(x)
        arabisch = arabisch + zahlen[zahlzeichen_index]
        zahlzeichen_index = zahlzeichen.index(y)
        arabisch = arabisch + zahlen[zahlzeichen_index]

print arabisch
Ich finde es ist eine Menge mehr Code entstanden und frage mich, ob meine ursprüngliche Lösung mit dem "Trick" nicht doch als gleichwertig anzusehen ist? Oder habe ich diese Lösung jetzt ungeschickt umgesetzt? Bzgl. des Abfangens des Index Errors stehe ich nach wie vor auf dem Schlauch, weil ich nicht weiß was in den 'except'-Teil kommen muss, bzw. wie ich trotzdem alle benötigten Zeichen von "zahl" abgreifen kann.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Code: Alles auswählen

ZAHLZEICHEN = {
    'M': 1000, 'CM': 900, 'D': 500, 'CD': 400, 'C': 100, 'XC': 90,
    'L': 50, 'XL': 40, 'X': 10, 'IX': 9, 'V': 5, 'IV': 4, 'I': 1
}

def gib_zahl(zahlzeichen):
    zahl = 0
    for i, zeichen in enumerate(zahlzeichen):
        try:
            wert = ZAHLZEICHEN.get(zeichen + zahlzeichen[i + 1])
        except IndexError:
            # Letztes Zeichen erreicht
            wert = None
        if wert is None:
            wert = ZAHLZEICHEN.get(zeichen)
        if wert is None:
            meldung = 'Unbekanntes Zahlzeichen an Position {}: {!r}'
            raise ValueError(meldung.format(i + 1, zeichen))
        zahl += wert
    return zahl
Hinweise zum Code:
Dass die Datenstruktur besser in einem Dictionary abgebildet werden kann, ist wohl offensichtlich. Dann ist auch die `get()`-Methode nutzbar: Sie holt entweder den passenden Wert zu einem angegebenen Schlüssel oder liefert `None` als Standardwert, wenn kein passender Wert gefunden wurde (d.h. Schlüssel ist nicht vorhanden). Den "Trick" im `except`-Block kannst du dir wahrscheinlich selbst erklären. Und wenn gar kein Wert gefunden wird, dann wirft der Code eine Ausnahme. Die geschweiften Klammern bei `meldung` stehen in Zusammenhang mit dem dort genutzten "String Formatting". Bei Fragen dazu, bitte vorher nach dem Stichwort googlen...
Nachbar
User
Beiträge: 24
Registriert: Sonntag 10. Juli 2016, 08:12

Okay also da sind einige Sachen drin die ich noch nicht kenne ("enumerate" oder ".get" oder "raise" z. B.). Meine "Bordmittel" reichen dann wohl für so eine Lösung nicht aus. Danke jedenfalls, werde mir mal die mir unbekannten Elemente in Ruhe zu Gemüte führen.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Eine andere Möglichkeit wäre noch die Nutzung von Iteratoren, ggf in Verbindung mit `collections.deque()`. Das sieht dann super-nerdig aus, weil man ohne den direkten Einsatz des Zählers `i` bzw `i + 1` auskommen kann, aber im Endeffekt ist mir keine kürzere Lösung mit dem Ansatz gelungen. Es wird dann eher komlexer vom Code her und es beeinflusst die Laufzeit negativ. Und sooo kryptisch finde ich den klassischen Ansatz mit Zählervariablen hier sowieso nicht.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nachbar hat geschrieben:Okay also da sind einige Sachen drin die ich noch nicht kenne ("enumerate" oder ".get" oder "raise" z. B.). Meine "Bordmittel" reichen dann wohl für so eine Lösung nicht aus. Danke jedenfalls, werde mir mal die mir unbekannten Elemente in Ruhe zu Gemüte führen.
Mach das. Aber hier die vorab in Kurzform:

`enumerate(elemente)` liefert ein Tupel in Form von `(aktueller Zählerstand, aktuelles Element)`. Das ist also eine Kombination aus Hochzählen und dem Iterieren über die Elemente.

`.get()` hatte ich unter dem Code als Erklärung schon dazu editiert.

Mit `raise` sorgt man dafür, dass eine eigene Exception geworfen wird. Es muss also nicht immer fremder Code sein, sondern dein eigener Code kann ja auch in Situationen kommen, wo er von sich aus einen Fehler mitteilen möchte.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nachbar hat geschrieben:

Code: Alles auswählen

# die letzten beiden Zeichen abgreifen
x = zahl[len(zahl)-2]
y = zahl[len(zahl)-1]
Das geht in Python kürzer:

Code: Alles auswählen

x = zahl[-2]
y = zahl[-1]
# oder:
x, y = zahl[-2:]
Wie du dir wahrscheinlich denken kannst, zählt er mit "-" von rechts nach links. Und in der anderen Variante kommt zusätzlich Slicing zum Einsatz. In diesem Fall: "Gib alles vom vorletzten Element bis zum Ende zurück".
Nachbar
User
Beiträge: 24
Registriert: Sonntag 10. Juli 2016, 08:12

Ja stimmt:

Code: Alles auswählen

x = zahl[-2]
y = zahl[-1]
hätte ich wissen müssen :evil: . Komme leider immer wieder durcheinander...
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Noch eine Anmerkung zu meinem Code-Vorschlag:
Anstelle von ``enumerate(zahlzeichen)`` kann man auch ``enumerate(zahlzeichen, 1)`` schreiben. Dann fängt er mit dem Zählen bei 1 an anstatt bei 0. Das ist sinnvoll, da ich ohnehin mit ``i + 1`` arbeite. Damit spart man eine zusätzliche Berechnung ein.
BlackJack

@snafu: Dein Code funktioniert nicht korrekt:

Code: Alles auswählen

In [2]: gib_zahl('CD')
Out[2]: 900
Das 'D' wird einmal als Teil von 'CD' (400) und dann nochmal selbst (500) berücksichtigt.

Mein Versuch:

Code: Alles auswählen

ROMAN_TO_VALUE = {
    'M': 1000, 'CM': 900, 'D': 500, 'CD': 400, 'C': 100, 'XC': 90,
    'L': 50, 'XL': 40, 'X': 10, 'IX': 9, 'V': 5, 'IV': 4, 'I': 1
}


def roman_to_int(roman):
    result = i = 0
    while i < len(roman):
        digits = roman[i:i + 2]
        value = ROMAN_TO_VALUE.get(digits)
        if value is None:
            value = ROMAN_TO_VALUE[digits[0]]
        else:
            i += 1
        result += value
        i += 1
    return result
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier mal basierend auf `collections.deque()`:

Code: Alles auswählen

from collections import deque

ROMAN_TO_INT = {
    'M': 1000, 'CM': 900, 'D': 500, 'CD': 400, 'C': 100, 'XC': 90,
    'L': 50, 'XL': 40, 'X': 10, 'IX': 9, 'V': 5, 'IV': 4, 'I': 1
}

def get_int(romans):
    result = 0
    buf = deque(maxlen=2)
    for char in romans:
        buf.append(char)
        if len(buf) == 2:
            value = ROMAN_TO_INT.get(buf[0] + buf[1])
            if value is None:
                value = ROMAN_TO_INT[buf.popleft()]
            else:
                buf.clear()
            result += value
    if buf:
        # One item remaining in `buf`
        result += ROMAN_TO_INT[buf.pop()]
    return result
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Mit re:

Code: Alles auswählen

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

import re

scan = re.Scanner([
    ('CM', 900),
    ('CD', 400),
    ('XC', 90),
    ('XL', 40),
    ('IX', 9),
    ('IV', 4),
    ('M', 1000),
    ('D', 500),
    ('C', 100),
    ('L', 50),
    ('X', 10),
    ('V', 5),
    ('I', 1),
]).scan

def main():
    values, invalid = scan(input('Please enter a roman number: '))
    if invalid:
        print('invalid input:', invalid)
    else:
        print('The corresponding decimal number is', sum(values))

if __name__ == '__main__':
    main()
Zum Beispiel:[codebox=bash file=Unbenannt.bsh]Please enter a roman number: MMXVI
The corresponding decimal number is 2016[/code]
In specifications, Murphy's Law supersedes Ohm's.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

die bisherigen Lösungen prüfen alle nicht die korrekte Konstruktion der Zahlen

Code: Alles auswählen

ROMAN_DIGITS = [
    ('M', 1000, 0),
    ('CM', 900, 3),
    ('D', 500, 1),
    ('CD', 400, 1),
    ('C', 100, 0),
    ('XC', 90, 3),
    ('L', 50, 1),
    ('XL', 40, 1),
    ('X', 10, 0),
    ('IX', 9, 3),
    ('V', 5, 1),
    ('IV', 4, 1),
    ('I', 1, 0),
]

def convert_roman(roman):
    result = 0
    skip = 0
    for digit, value, newskip in ROMAN_DIGITS:
        if skip > 0:
            skip -= 1
        else:
            while roman.startswith(digit):
                result += value
                roman = roman[len(digit):]
                if newskip:
                    skip = newskip
                    break
    if roman:
        raise ValueError("invalid roman number")
    return result
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Dann so: :mrgreen:

Code: Alles auswählen

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

import re

scan = re.Scanner([
    ('M+', lambda s, t: len(t) * 1000),
    ('CM(?!M|C|D)', 900),
    ('D(?!M|CM|CD|D)', 500),
    ('CD(?!M|C|D)', 400),
    ('C{1,3}(?!M|C|D)', lambda s, t: len(t) * 100),
    ('XC(?!M|C|D|X|L)', 90),
    ('L(?!M|C|D|XC|XL|L)', 50),
    ('XL(?!M|C|D|X|L)', 40),
    ('X{1,3}(?!M|C|D|X|L)', lambda s, t: len(t) * 10),
    ('IX(?!M|C|D|X|L|V|I)', 9),
    ('V(?!M|C|D|X|L|IX|IV|V)', 5),
    ('IV(?!M|C|D|X|L|V|I)', 4),
    ('I{1,3}(?!M|C|D|X|L|V|I)', lambda s, t: len(t)),
]).scan

def main():
    values, invalid = scan(input('Please enter a roman number: '))
    if invalid:
        print('invalid input:', invalid)
    else:
        print('The corresponding decimal number is', sum(values))

if __name__ == '__main__':
    main()
Beispiele:[codebox=bash file=Unbenannt.bsh]$ python3 roman_numerals.py
Please enter a roman number: MMXVI
The corresponding decimal number is 2016
$ python3 roman_numerals.py
Please enter a roman number: MMXVIIII
invalid input: IIII
$ python3 roman_numerals.py
Please enter a roman number: CMCMCM
invalid input: CMCMCM[/code]
In specifications, Murphy's Law supersedes Ohm's.
Nachbar
User
Beiträge: 24
Registriert: Sonntag 10. Juli 2016, 08:12

Mir ist in BlackJacks Lösung diese Zeile aufgefallen:

Code: Alles auswählen

result = i = 0
Ist das gleichbedeutend mit

Code: Alles auswählen

result = 0
i = 0
? Falls ja, ist das eine übliche Form der Variableninitialisierung in Python?
Antworten