Seite 1 von 2

Letzter Index in einer Schleife (out of range)

Verfasst: Samstag 27. August 2016, 21:21
von Nachbar
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?

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Samstag 27. August 2016, 21:30
von 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.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Samstag 27. August 2016, 22:01
von Nachbar
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?

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 00:10
von snafu
Lass einfach in Zeile 10 das ``- 1`` weg. Dann musst du auch vorher kein Pseudo-Element dazu nehmen.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 01:19
von 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.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 03:36
von snafu
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.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 09:38
von Nachbar
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.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 10:49
von snafu

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...

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 11:09
von Nachbar
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.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 11:09
von snafu
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.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 11:15
von snafu
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.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 11:28
von snafu
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".

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 12:08
von Nachbar
Ja stimmt:

Code: Alles auswählen

x = zahl[-2]
y = zahl[-1]
hätte ich wissen müssen :evil: . Komme leider immer wieder durcheinander...

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 12:44
von snafu
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.

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 14:20
von 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

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Sonntag 28. August 2016, 22:34
von snafu
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

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Montag 29. August 2016, 00:50
von pillmuncher
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]

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Montag 29. August 2016, 08:06
von Sirius3
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

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Montag 29. August 2016, 18:52
von pillmuncher
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]

Re: Letzter Index in einer Schleife (out of range)

Verfasst: Dienstag 30. August 2016, 21:19
von Nachbar
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?