AUFGABE: Jeden tag eine Änderung des Bankcodes...

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.
Benutzeravatar
BlackJack
Moderator
Beiträge: 33025
Registriert: Dienstag 25. Januar 2005, 23:29
Wohnort: Berlin
Kontaktdaten:

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon BlackJack » Dienstag 7. Februar 2017, 14:42

@PythonRookie9000: Eine weitere Idee aufgrund dieser Aufgabe wäre es mal zu schauen wie sich das bei den anderen 9999 Möglichkeiten neben 1986 verhält. Wie lange brauchen andere Kombinationen und wie viele Kombinationen gibt es, die nie wieder zum Ausgangscode zurückkehren?
“Programs must be written for people to read, and only incidentally for machines to execute.” — Abelson & Sussman, SICP (preface to the first edition)
sebastian0202
User
Beiträge: 138
Registriert: Montag 9. Mai 2016, 09:14
Wohnort: Berlin

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon sebastian0202 » Mittwoch 8. Februar 2017, 11:38

Dann will ich mal wieder 8)
Hoffe die Namen sind jetzt eindeutiger..

  1. #!/usr/bin/python3
  2. # -*- coding: utf-8 -*-
  3.  
  4.  
  5.  
  6. class Mathe_Functions(object):
  7.     def __init__(self):
  8.         super(Mathe_Functions, self).__init__()
  9.         # Zahl hat mindestens eine Stelle
  10.         self.stellen = 1
  11.  
  12.     def anzahl_ziffern(self, zahl):
  13.         # die Anzahl der Stellen von der Zahl ermitteln
  14.         anzahl = 1
  15.         while zahl >= 10:
  16.             zahl /= 10
  17.             anzahl += 1
  18.         return anzahl
  19.      
  20.     def quersumme(self, zahl):
  21.         # Quersumme der Zahl bilden
  22.         stellen = self.anzahl_ziffern(zahl)
  23.         summe = 0
  24.         while stellen > 0:
  25.             summe += zahl % 10
  26.             zahl = (zahl - zahl % 10) / 10
  27.             stellen -= 1
  28.         return int(summe)
  29.  
  30.     def stellen_kuerzen(self, zahl, kuerzen = 1):
  31.         # Zahl um x Stellen kuerzen (standard eine Stelle)
  32.         # dabei muss die Zahl mindestens x Stellen lang 'self.stellen' sein
  33.         stellen = self.anzahl_ziffern(zahl)
  34.         if stellen - self.stellen >= kuerzen:
  35.             zahl = zahl % 10**(stellen-kuerzen)
  36.         return zahl
  37.  
  38.     def zahl_links_shiften(self, zahl, einer):
  39.         # Zahl um eine Stelle nach Links verschieben
  40.         zahl *= 10
  41.         zahl += einer
  42.         zahl = self.stellen_kuerzen(zahl, 1)
  43.         return zahl
  44.  
  45.     def letzte_zahl(self, zahl):
  46.         # die letzte Ziffer der Zahl ermitteln
  47.         if zahl >= 10:
  48.             return zahl % 10
  49.         return zahl
  50.  
  51.  
  52. class Code_X_Stellig(Mathe_Functions):
  53.     def __init__(self, stellen):
  54.         super(Code_X_Stellig).__init__()
  55.         self.stellen = stellen
  56.         self.codes = {}
  57.         self.groups = []
  58.    
  59.     def __str__(self):
  60.         liste = [int(zahl) for zahl in self.codes]
  61.         liste.sort()
  62.         for code in liste:
  63.             tage = str(self.codes[str(code)]['tage']).ljust(4)
  64.             code_string = ('000'+str(code))[-4:]
  65.             print("Code: %s Tage: %s Gruppe: %i" % (code_string, tage, self.codes[str(code)]['gruppe']))
  66.  
  67.         return ''
  68.  
  69.     def auswertung(self):
  70.         gruppen_anzahl = len(self.groups)
  71.         kombinationen = set()
  72.         ohne_wiederkehr = []
  73.         for code in self.codes:
  74.             kombinationen.add(self.codes[code]['tage'])
  75.             if self.codes[code]['tage'] == -1:
  76.                 ohne_wiederkehr.append(code)
  77.         print("Es existieren %i unterschiedliche Gruppierungen" % gruppen_anzahl)
  78.         print("Folgende Komibnationen traten dabei auf: %s" % kombinationen)
  79.         print("Folgende Zahlen kehren nie wieder zurück: %s" % ohne_wiederkehr)
  80.  
  81.     def recheck_groups(self):
  82.         liste = [int(zahl) for zahl in self.codes]
  83.         liste.sort()
  84.         for zahl in liste:
  85.             for nr, group in enumerate(self.groups):
  86.                 if zahl in group:
  87.                     self.codes[str(zahl)]['gruppe'] = nr
  88.                     break
  89.        
  90.     def add_group(self, zahl, liste):
  91.         gefunden = False
  92.         for group in self.groups:
  93.             if zahl in group:
  94.                 gefunden = True
  95.                 break
  96.  
  97.         if not gefunden:
  98.             self.groups.append(liste)
  99.    
  100.     def mathe(self, ur_code):
  101.         # nach wievielen durchlaeufen kommt die zahl wieder vor
  102.         # bedingung:  1. links-shiften um eine Stelle
  103.         #             2. quersumme (einer-stelle) anhaengen
  104.         code = ur_code
  105.         tage = 0
  106.         zahlen_liste = []
  107.         while tage < 10000:
  108.             summe = self.quersumme(code)
  109.             zahl = self.letzte_zahl(summe)
  110.             code = self.zahl_links_shiften(code, zahl)
  111.             zahlen_liste.append(code)
  112.             tage += 1
  113.             if code == ur_code:
  114.                 break
  115.         if tage == 10000: tage = -1
  116.         return tage, zahlen_liste
  117.    
  118.     def experimental(self, zahlen_liste, tage):
  119.         # da der vorige Code nach X Durchlaeufen wiederkehrt,
  120.         # kehren auch alle Zahlen in der zahlen_liste nach diesen X Durchlaeufen wieder
  121.         for zahl in zahlen_liste:
  122.             sammeln.codes[str(zahl)] = {'tage' : tage, 'gruppe': -1}
  123.  
  124. if __name__ == '__main__':
  125.  
  126.     stellen = 4
  127.     sammeln = Code_X_Stellig(stellen)
  128.     # tage, zahlen_liste = sammeln.mathe(1986)
  129.     # print("Es vergehen %i Tage, bis sich der Code %i wiederholt!" % (tage, 1986))
  130.  
  131.     for code in range(1,10000):
  132.         if not str(code) in sammeln.codes:
  133.             tage, zahlen_liste = sammeln.mathe(code)
  134.             sammeln.codes[str(code)] = {'tage' : tage, 'gruppe': -1}
  135.             sammeln.add_group(code, zahlen_liste)
  136.             sammeln.experimental(zahlen_liste, tage)
  137.    
  138.     sammeln.recheck_groups()
  139.     # print(sammeln)
  140.     sammeln.auswertung()
  141.  
Benutzeravatar
BlackJack
Moderator
Beiträge: 33025
Registriert: Dienstag 25. Januar 2005, 23:29
Wohnort: Berlin
Kontaktdaten:

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon BlackJack » Mittwoch 8. Februar 2017, 13:14

@sebastian0202: Das sind keine Klassen die Du da geschrieben hast. Das ist so gar keine objektorientierte Programmierung. Bei `Mathe_Functions` sagt es ja schon der Name das es keine Klasse mit Methoden ist, sondern einfach nur Funktionen die ohne Grund in eine Klasse gesteckt wurden.

`Code_X_Stellig` ist auch ein komischer Name. Da würde ich erwarten das *ein* Exemplar dieser Klasse für *einen* x-stelligen Code steht. Unterstriche haben in Klassennamen übrigens nichts zu suchen.

Vererbung ist eine „ist-ein(e)“-Beziehung. Was `Code_X_Stellig` ist ein(e) `Mathe_Functions` überhaupt bedeuten soll ist mir an sich schon schleierhaft. Aber es macht ja sowieso schon keinen Sinn Funktionen in eine Klasse zu stecken.

Die `__str__()`-Methode ist dazu da eine Zeichenkettendarstellung des Objekts zu erzeugen und zurück zu geben. *Nicht* um irgendwelche Ausgaben oder andere Effekte zu haben. Du wirst ja sogar gezwungen eine Zeichenkette zurück zu geben, wie man an dem hier sinnlosen ``return ''`` sehen kann.

Die Ausrichtungen der Tage und des Codes (inklusive führender Nullen) hätte man beim Platzhalter in der Zeichenkette wo die Werte hinein formatiert werden, erledigen können.

`sorted()` hätte eine bzw. sogar zwei Zeilen sparen können.

Das ganze ständige hin und her wandeln zwischen `int()` und `str()` bei den Codes ist verwirrend und hätte man ziemlich sicher einfach weglassen können in dem man nur Zahlen nimmt und die einzig für die Ausgabe mit 0en aufgefüllt formatiert.

Wenn das `codes`-Attribut auf `collections.namedtuple` abbilden würde, dann hätte man im Code eine Dokumentation wie die Struktur aussieht, also welche Attribute/Schlüssel ein Wert in diesem Wörterbuch hat.

Programmlogik und Benutzerinteraktion sind vermischt.

Den Namen `recheck_groups()` verstehe ich nicht im Zusammenhang mit dem was die Methode tut. Bei `mathe()` das gleiche.

Für `add_group()` könntest Du Dir mal `any()` und `all()` anschauen und wie man eine der beiden Funktionen verwenden kann um da einen Ein- oder Zweizeiler draus zu machen.

Zur ``while``-Schleife in `mathe()` könntest Du Dir mal ``else``-Anschauen, dann muss man den Test mit der 10000 nach der Schleife nicht machen.

Einerseits scheint `stellen` etwas variables zu sein, andererseits funktioniert es nicht etwas anderes als 4 dafür zu verwenden, weil die 10000 als magische Zahl im Quelltext steht und nicht zu anderen Werten als 4 passt.

Ist das `ur` bei `ur_code` die deutsche Vorsilbe „Ur“ wie in „Ursprung“ oder Slangabkürzung für das englische `your`? Im ersten Fall: Keine natürlichen Sprachen mischen, schon gar nicht in *einem* Bezeichner (betrifft auch `Mathe_Functions`). Im zweiten Fall: Keine kryptischen Abkürzungen verwenden.

`experimental()` ist eindeutig keine Methode, wie so einiges andere auch auf `Code_X_Stellig` wahrscheinlich als Funktion geschrieben werden könnte, und die ”Methode” greift auf `sammeln` zu was nicht möglich sein dürfte, denn das Hauptprogramm sollte unter anderem genau aus diesem Grund in einer Funktion stehen.

`sammeln` ist auch wieder ein Name den ich nicht verstehe. Das wäre ein Name für eine Funktion oder eine Methode weil es eine Tätigkeit beschreibt. Aber `Code_X_Stellig` ist ja schon ein unverständlicher Name. Wenn Dir für die beiden nichts besseres, passendes einfällt, dann liegt das daran das die Klasse IMHO keinen Sinn macht. Das was da an Daten und Funktionen zusammengefasst wurde sieht völlig willkürlich aus. Also im Grunde alles was das Programm so können muss abzüglich einiger Hilfsfunktionen die sinnloser Weise in einer ererbten Klasse stehen. Irgendwie habe ich den Eindruck das Ziel war es keine Funktionen zu schreiben und alles *irgendwie* in Klassen zu stopfen. Das ist falsch! Funktionen sind nichts böses, und Klassen sollte man verwenden um *sinnvoll* Daten und Funktionen zu einem Objekt zusammen zu fassen.

Im Hauptprogramm wird dann umgekehrt in einer Art und Weise auf `codes` zugegriffen die man so auch nicht unbedingt erwarten würde.

Eine `Code`-Klasse die *tatsächlich* *einen* x-stelligen Code als Werttyp modelliert, könnte beispielsweise Sinn machen. Die könnte dann zum Beispiel eine Methode haben, welche den nächsten Code liefert. Und Methoden um die Quersumme abzufragen und einen verschobenen Code zu liefern.

Mal am Beispiel der CoffeeScript-Lösung objektorientiert, dort allerdings nicht mit einer zusätzlichen Klasse, sondern einfach die vorhandene `Number`-Klasse um entsprechende Methoden erweitert:
  1. #!/usr/bin/env coffee
  2. 'use strict'
  3.  
  4. Number::lastDigit = -> this % 10
  5.  
  6. Number::crossSum = ->
  7.   sum = 0
  8.   n = this
  9.   while n > 0
  10.     digit = n.lastDigit()
  11.     sum += digit
  12.     n = (n - digit) / 10
  13.   sum
  14.  
  15. Number::shiftCode = (digit) -> this * 10 % 10000 + digit
  16.  
  17. Number::nextCode = -> this.shiftCode(this.crossSum().lastDigit())
  18.  
  19. main = ->
  20.   startCode = 1986
  21.   day = 0
  22.   code = startCode
  23.   loop
  24.     code = code.nextCode()
  25.     day++
  26.     break if code is startCode
  27.   console.log(day, code)
  28.  
  29. main() if require.main == module
“Programs must be written for people to read, and only incidentally for machines to execute.” — Abelson & Sussman, SICP (preface to the first edition)
Sirius3
User
Beiträge: 6118
Registriert: Sonntag 21. Oktober 2012, 17:20

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon Sirius3 » Mittwoch 8. Februar 2017, 13:18

@sebastian0202: auf die Schnelle habe ich nichts gefunden, was das Verwenden von Klassen rechtfertigen würde. Mathe_Functions ist nur eine Sammlung von einzelnen Funktionen, die entweder zu kompliziert gedacht, oder gleich ganz überflüssig sind. Man will eben nicht eine 5 stellige Zahl um eine Ziffer kürzen, sondern nur dafür sorgen, dass man immer eine 4 stellige Zahl hat. Bei Code_X_Stellig.__str__: es gibt Formatangaben um Zahlen 4 stellig (mit und ohne führende Nullen) auszugeben (wobei Du da die 4 fest codiert hast, statt die Stellenanzahl zu benutzen). __str__ sollte auch einen String zurückgeben, und nicht etwas per print ausgeben! Die ständige Umwandlung von Zahl zu String und zurück ist unnötig kompliziert. Mit Sets könnte man sich wohl 99% der Rechenzeit sparen. Es ist sinnvoller, eine Listen mit Gruppen anzulegen, da dort die Anzahl der Tage implizit durch die Länge gegeben ist und die Gruppennummer als Index.
Benutzeravatar
BlackJack
Moderator
Beiträge: 33025
Registriert: Dienstag 25. Januar 2005, 23:29
Wohnort: Berlin
Kontaktdaten:

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon BlackJack » Mittwoch 8. Februar 2017, 22:48

Ein C-Programm das die Tage für alle Codes berechnet. Läuft grundsätzlich auch auf dem C64, allerdings quälend langsam.
  1. #include <stdbool.h>
  2. #include <stdint.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6.  
  7. uint8_t cross_sum(uint16_t code)
  8. {
  9.     uint8_t sum = 0;
  10.     div_t tmp;
  11.  
  12.     while (code) {
  13.         tmp = div(code, 10);
  14.         code = tmp.quot;
  15.         sum += tmp.rem;
  16.     }
  17.     return sum;
  18. }
  19.  
  20. uint16_t shift_code(uint16_t code, uint8_t digit) {
  21.     return (code % 1000) * 10 + digit;
  22. }
  23.  
  24. bool calculate_days(uint16_t start_code, uint16_t *days) {
  25.     register uint16_t day = 0;
  26.     register uint16_t code = start_code;
  27.     bool is_code_used[10000];
  28.  
  29.     memset(is_code_used, 0, sizeof(is_code_used));
  30.     do {
  31.         code = shift_code(code, cross_sum(code) % 10);
  32.         if (is_code_used[code]) return false;
  33.         is_code_used[code] = true;
  34.         ++day;
  35.     } while (code != start_code);
  36.     *days = day;
  37.     return true;
  38. }
  39.  
  40. int main(void)
  41. {
  42.     uint16_t start_code;
  43.     uint16_t days;
  44.    
  45.     puts("start_code,days");
  46.     for (start_code = 0; start_code <= 9999; ++start_code) {
  47.         if (calculate_days(start_code, &days)) {
  48.             printf("%04u,%u\n", start_code, days);
  49.         } else {
  50.             printf("%04u,inf\n", start_code);
  51.         }
  52.     }
  53.  
  54.     return 0;
  55. }
“Programs must be written for people to read, and only incidentally for machines to execute.” — Abelson & Sussman, SICP (preface to the first edition)
sebastian0202
User
Beiträge: 138
Registriert: Montag 9. Mai 2016, 09:14
Wohnort: Berlin

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon sebastian0202 » Donnerstag 9. Februar 2017, 12:23

Dann weiß ich immer noch nicht wann es sich lohnt ein Objekt zu erstellen.
Vielleicht lese ich die falschen Bücher.

Bei Klassennamen sollte der Anfangsbuchstabe jedes Wortes Groß und aneinander gereiht sein, richtig?
Bei Methoden und Funktionen sollten die Wörter dann kleingeschrieben und mit Unterstrich verbunden sein, richtig?
https://www.python.org/dev/peps/pep-0008/#function-names :lol:

Bei der Formatierung hätte ich ja "%4d" verwenden können, aber was ist, wenn ich anstelle von 4 auf 6 Zahlen auffüllen möchte?
Abhängig von der Anzahl der Stellen des Codes eben. Es wird ja kein %ANZAHL_STELLENd geben.
Deshalb ('0'*anzahl_stellen+str(zahl))[anzahl_stellen*-1:], aber das liest sich ja katastrophal.

Sorted() konnte ich nicht nehmen, da er die Zahlen als Strings sortiert. Und so kommt 57 vor 571 etc.
Habe die Zahlen vor zum string konvertiert um sie als dict Key zu nutzen :oops:

Es sieht schon wirr aus was ich da geschrieben habe :lol:
Da bekomme ich glatt Angst, wenn ich darüber nachdenke wie ich auf Arbeit programmiere.
Dabei habe ich mehrfach über den Code geschaut.

Ich habe mir any und all angeschaut. Keine Ahnung wie ich die Funktionen nutzen soll,
um über mehrere Listen zu iterieren um darin ein Element an beliebiger Stelle zu suchen. Zumal ich dann ja den Index der Gruppe brauche.

@Sirius3
Wie meinst du das mit den Sets?
Wie kann ich denn 99% Rechenzeit sparen?



Ich habe mich eben an einem zweiten Versuch gewagt :?
  1.  
  2. class Code(object):
  3.     def __init__(self, laenge, zahl = -1):
  4.         self._generation = 0
  5.         self.laenge = laenge
  6.         if zahl >= 0:
  7.             self.ziffern = list(map(int, str(zahl)))
  8.         else:
  9.             self.ziffern = [random.randint(0,9) for x in laenge]
  10.         self.codes = [self.get_code()]
  11.  
  12.     def next_code(self, generations = 1):
  13.         while generations > 0:
  14.             zahl = sum(self.ziffern) % 10        
  15.             self.ziffern.append(zahl)
  16.             self.ziffern = self.ziffern[self.laenge*-1:]
  17.             self._generation += 1
  18.             self.codes.append(self.get_code())
  19.             generations -= 1
  20.         return self.get_code()
  21.  
  22.     def get_code(self):
  23.         return int(''.join(map(str, self.ziffern)))
  24.    
  25.     def get_codes(self):
  26.         return self.codes
  27.  
  28.     def get_generation(self):
  29.         return self._generation
  30.  
  31.     def show_generations(self, von=0, bis=None):
  32.         text = ''
  33.         if not bis: bis = self._generation
  34.         for generation in range(von, bis + 1):
  35.             text += "Gen: %s - %i\n" % (generation, self.codes[generation])
  36.         return text
  37.    
  38.     def __str__(self):
  39.         return "Code: %4d - Generation: %i" % (self.get_code(), self._generation)
  40.  
  41. class CodeGruppe(object):
  42.     def __init__(self):
  43.         self.gruppen = []
  44.         self._codes = []
  45.  
  46.     def add_group(self, codes):
  47.         if not self.number_exists(codes[0]):
  48.             self.gruppen.append(codes)
  49.             self._codes.extend(codes)
  50.    
  51.     def number_exists(self, code):
  52.         if code in self._codes:
  53.             return True
  54.         return False
  55.    
  56.     def get_group(self, number):
  57.         return self.gruppen[number]
  58.  
  59.     def get_group_number(self, code):
  60.         if not code in self._codes:
  61.             return False
  62.         for nr in self.get_group_numbers():
  63.             if code in self.gruppen[nr]:
  64.                 return nr
  65.    
  66.     def get_group_numbers(self):
  67.         return [nr for nr, liste in enumerate(self.gruppen)]
  68.  
  69.     def get_group_info(self, *arg):
  70.         text = ''
  71.         if not arg:
  72.             arg = self.get_group_numbers()
  73.         for nr in arg:
  74.             text += "Gruppe: %i laenge: %i\n" % (nr, len(self.gruppen[nr]))
  75.  
  76.         return text
  77.  
  78. def main():
  79.     codegruppe = CodeGruppe()
  80.  
  81.     for i in range(0, 1000):
  82.         if not codegruppe.number_exists(i):
  83.             code = Code(4, i)
  84.             anfang = code.get_code()
  85.  
  86.             while code.get_generation() < 4800:
  87.                 if anfang == code.next_code():
  88.                     break
  89.             codegruppe.add_group(code.get_codes())
  90.    
  91.    
  92.     print(codegruppe.get_group_info())
  93.     number = codegruppe.get_group_number(1986)
  94.     print(codegruppe.get_group_info(number))
  95.     print(codegruppe.get_group(number))
  96.  
  97. if __name__ == '__main__':
  98.     main()
Benutzeravatar
BlackJack
Moderator
Beiträge: 33025
Registriert: Dienstag 25. Januar 2005, 23:29
Wohnort: Berlin
Kontaktdaten:

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon BlackJack » Donnerstag 9. Februar 2017, 13:36

@sebastian0202: Es gibt kein '%0ANZAHL_STELLENd' aber '%0*d'. Und bei `format()` kann man Platzhalter auch verschachteln. Notfalls gäbe es auf Zeichenketten auch noch die `zfill()`-Methode.
  1. In [13]: '%0*d' % (4, 42)
  2. Out[13]: '0042'
  3.  
  4. In [14]: '{0:0{1}d}'.format(42, 4)
  5. Out[14]: '0042'
  6.  
  7. In [15]: str(42).zfill(4)
  8. Out[15]: '0042'


`sorted()` kannst Du natürlich trotzdem nehmen wenn Du es auf die umgewandelten Daten anwendest, also einfach statt der „list comprehension“ die `sorted()`-Funktion mit einem Generatorausdruck als Argument:
sorted_numbers = sorted(int(s) for s in strings)

Bezüglich `any()`/`all()`: Aus diesem hier:
  1.     def add_group(self, zahl, liste):
  2.         gefunden = False
  3.         for group in self.groups:
  4.             if zahl in group:
  5.                 gefunden = True
  6.                 break
  7.  
  8.         if not gefunden:
  9.             self.groups.append(liste)

wird das hier (ungetestet):
  1.     def add_group(self, zahl, liste):
  2.         if all(zahl not in group for group in self.groups):
  3.             self.groups.append(liste)


`Code` macht IMHO schon wieder zu viel. Das `codes`-Attribut hat da nichts zu suchen. `_generation` eigentlich auch nicht, denn ich würde erwarten das `Code` ein Wertobjekt ist und der Wert nur aus dem Zahlwert und der Länge eines Codes besteht. Du packst da gleich noch alle Codes die aus dem Code folgen mit hinein, wobei das aber abhängig davon ist ob und wie viele man davon hat generieren lassen.

`next_code()` würde ich `advance_code()` nennen, weil man nicht nur den nächsten Code bekommen kann, sondern auch beliebig weite Sprünge machen kann. Das und auch das mit dem Zufallswert in der `__init__()` scheint mir hier überflüssig zu sein‽

Wenn `Code` kein Werttyp ist, dann würde ich da einen Iterator draus machen statt eine `next_code()`-Methode zur Verfügung zu stellen. `CodeIterator` wäre dann auch ein passenderer Name IMHO. Denn es ist ja kein Code sondern enthält einen (aktuellen) Code (und eventuell alle seine Vorgänger).

``while generations > 0:`` sollte eine ``for``-Schleife sein.

`get_codes()` und `get_generation()` sind trivialer Getter → weg damit. Aus `get_code()` könnte man ein `property()` machen.

`show_generations()` wäre in Haskell vielleicht der passende Namen, aber in Python eher nicht. Da würde man bei `show_irgendwas()` erwarten das etwas ausgegeben oder angezeigt wird. Die Methode ist vom Inhalt her auch mehr was für die Benutzerinteraktion und nicht für die Programmlogik.

”Magische” Methoden wie `__str__()` erwartet man eigentlich am Anfang der Klasse vor Properties und normalen Methoden und nicht versteckt als letzte Methode.

``self.ziffern[self.laenge*-1:]`` würde ich als ``self.ziffern[-self.laenge:]`` schreiben. Eine `collections.deque` statt einer Liste würde die Sache vereinfachen und eventuell auch effizienter machen.

`CodeGruppe` enthält mehr als eine Gruppe, sollte also `CodeGruppen` heissen. Das `_group` kann aus allen Methodennamen verschwinden, denn es doppelt nur noch mal die Typinformation.

Wenn ``if``/``else`` in den zweigen nur `True` oder `False` zurück gibt, dann ist das Konstrukt unnötig, denn die Bedingung vom ``if`` ist ja schon der Wert den man haben möchte, oder seine Negation, die man mit ``not`` bekommt. Also wird `number_exists()` zu einem Einzeiler: ``return code in self._codes``.

Eine Funktion sollte immer nur einen (Duck)Typ als Ergebnis liefern, also beispielsweise nicht mal `False` und mal eine Zahl. Das lässt sich in diesem Fall auch nur über einen Typtest prüfen, denn `False` ist eine Zahl und hat den gleichen Wert wie 0 beim Vergleichen und allen anderen Operationen die auf `int` definiert sind:
  1. In [21]: isinstance(False, int)
  2. Out[21]: True
  3.  
  4. In [22]: False == 0
  5. Out[22]: True
  6.  
  7. In [23]: False + 42
  8. Out[23]: 42
  9.  
  10. In [24]: False * 42
  11. Out[24]: 0

Statt spezielle Fehlercodes zurück zu geben, verwendet man in Python Ausnahmen. `get_group_number()` sollte eine Auslösen wenn der `code` nicht enthalten ist. `KeyError` würde sich anbieten.

`get_group_numbers()` nutzt `enumerate()` wo ein `range()` ausreichen würde. In Python 3 mit einem `list()` um da tatsächlich eine Liste draus zu machen. Sofern das benötigt wird tatsächlich eine Liste vorliegen zu haben.

Die API von `get_group_info()` ist mir zu magisch. Warum nicht einfach eine Liste mit Nummern statt ein *-Parameter? Was ist der Anwendungsfall für einen Aufruf mit mehr als einer Nummer bei der beim Aufruf *kein* Sternchen gebraucht wird? Der zudem einen besseren Namen als `*arg` haben könnte. Der Leser will nicht wissen das da irgendwelche Argumente übergeben werden können, sondern was sie bedeuten.

Eventuell würde es die API von `CodeGruppen` schöner machen, wenn man da die ”magischen” Methoden für einen Containertyp implementiert. Wohl eine Abbildung in diesem Fall.

Im Hauptprogramm sind 1000, 4, und 4800 Zahlen die zusammenhängen. Wobei 4800 IMHO einer Erklärung bedarf. Warum ist diese Zahl sicher? Wie berechnet die sich aus der Stellenanzahl? Ist der Code robust gegen den Fall das eine Kombination niemals wieder erreicht würde? Was wäre da das Gegenargument?

Zur Frage wann es sich lohnt ein Objekt zu erstellen: Implementiere das doch mal nur mit Funktionen. Dann sieht man in der Regel leichter welche Daten und welche Funktionen man zusammenfassen kann/sollte. Wie gesagt Funktionen sind nicht böse und Klassen sind kein Selbstzweck. Wenn man sie nicht benötigt, sollte man sie auch nicht schreiben und man sollte auch nicht versuchen krampfhaft jede ``def``\inition in eine Klasse stecken zu wollen.

Edit: Man braucht die Fähigkeit Code sinnvoll auf Funktionen aufzuteilen auch bei OOP, denn dort muss man ihn sinnvoll auf Klassen und Methoden aufteilen. Wenn man also schon bei der Aufteilung auf Funktionen Probleme hat, dann wird das bei OOP nur noch schwieriger. Ich erwähne das weil Dein Versuch weiter oben im Thema die ursprüngliche Aufgabe auf Funktionen zu verteilen auch schon nicht gut war. Auch ein Grund erst einmal Funktionen zu üben.
“Programs must be written for people to read, and only incidentally for machines to execute.” — Abelson & Sussman, SICP (preface to the first edition)
Benutzeravatar
BlackJack
Moderator
Beiträge: 33025
Registriert: Dienstag 25. Januar 2005, 23:29
Wohnort: Berlin
Kontaktdaten:

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon BlackJack » Freitag 10. Februar 2017, 00:05

@sebastian0202: Mal ein Beispiel wie man zu Objekten kommen kann. Ausgangspunkt ist eine funktionale Lösung mit Codes die nicht auf 4 Stellen festgelegt sind, denn sonst gibt es hier fast nichts was man zu einem Objekt zusammenfassen kann:
  1. #!/usr/bin/env python
  2. from __future__ import absolute_import, division, print_function
  3.  
  4.  
  5. def get_cross_sum(number):
  6.     result = 0
  7.     while number:
  8.         number, digit = divmod(number, 10)
  9.         result += digit
  10.     return result
  11.  
  12.  
  13. def code_as_string(code_length, code_value):
  14.     return str(code_value).zfill(code_length)
  15.  
  16.  
  17. def shifted_code(code_length, code_value, digit):
  18.     return code_value * 10 % 10**code_length + digit
  19.  
  20.  
  21. def get_next_code(code_length, code_value):
  22.     return shifted_code(code_length, code_value, get_cross_sum(code_value) % 10)
  23.  
  24.  
  25. def calculate_days(code_length, start_code_value):
  26.     days = 0
  27.     code_value = start_code_value
  28.     while True:
  29.         days += 1
  30.         code_value = get_next_code(code_length, code_value)
  31.         if code_value == start_code_value:
  32.             return days
  33.  
  34.  
  35. def main():
  36.     code_length = 4
  37.     for code_value in range(10**code_length):
  38.         print(
  39.             code_as_string(code_length, code_value),
  40.             calculate_days(code_length, code_value)
  41.         )
  42.  
  43.  
  44. if __name__ == '__main__':
  45.     main()


Wie man sieht besteht ein Code aus zwei Komponenten, der Länge und dem Zahlwert, die durch viele Funktionen durchgereicht werden müssen. Das wären im ersten Schritt die Kandidaten die man zu einem Objekt zusammenfassen könnte. Machen wir das mal mit `collections.namedtuple()` (die geänderten Funktionen):
  1. Code = namedtuple('Code', 'length value')
  2.  
  3.  
  4. def code_as_string(code):
  5.     return str(code.value).zfill(code.length)
  6.  
  7.  
  8. def shifted_code(code, digit):
  9.     return Code(code.length, code.value * 10 % 10**code.length + digit)
  10.  
  11.  
  12. def get_next_code(code):
  13.     return shifted_code(code, get_cross_sum(code.value) % 10)
  14.  
  15.  
  16. def calculate_days(start_code):
  17.     days = 0
  18.     code = start_code
  19.     while True:
  20.         days += 1
  21.         code = get_next_code(code)
  22.         if code == start_code:
  23.             return days
  24.  
  25.  
  26. def iter_codes(length):
  27.     return (Code(length, i) for i in range(10**length))
  28.  
  29.  
  30. def main():
  31.     length = 4
  32.     for code in iter_codes(length):
  33.         print(code_as_string(code), calculate_days(code))


Jetzt kann man sich überlegen, welche von den Funktionen zu dem Objekt gehören könnten um das `namedtuple()` zu beerben und um Methoden zu erweitern. `code_as_string()` ist sinnvoll als `__str__()` auf der Klasse. Des weiteren würde ich in diesem Beispiel alle Funktionen dort hin verschieben die ein `Code`-Objekt als Argument bekommen *und* die interne Repräsentation kennen (müssen). Dazu gehört `calculate_days()` *nicht*, denn diese Funktion weiss nicht, das ein `Code` aus Länge und Wert besteht. Die `iter_codes()`-Funktion bekommt zwar keinen Code übergeben, erzeugt aber welche, und wäre damit ein Kandidat für eine Klassenmethode:
  1. class Code(namedtuple('Code', 'length value')):
  2.  
  3.     def __str__(self):
  4.         return str(self.value).zfill(self.length)
  5.  
  6.     def shifted(self, digit):
  7.         return Code(self.length, self.value * 10 % 10**self.length + digit)
  8.  
  9.     def get_next(self):
  10.         return self.shifted(get_cross_sum(self.value) % 10)
  11.  
  12.     @classmethod
  13.     def iter_all(cls, length):
  14.         return (cls(length, i) for i in range(10**length))
  15.  
  16.  
  17. def calculate_days(start_code):
  18.     days = 0
  19.     code = start_code
  20.     while True:
  21.         days += 1
  22.         code = code.get_next()
  23.         if code == start_code:
  24.             return days
  25.  
  26.  
  27. def main():
  28.     length = 4
  29.     for code in Code.iter_all(length):
  30.         print(code, calculate_days(code))
“Programs must be written for people to read, and only incidentally for machines to execute.” — Abelson & Sussman, SICP (preface to the first edition)
karolus
User
Beiträge: 100
Registriert: Samstag 22. August 2009, 22:34

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon karolus » Freitag 10. Februar 2017, 03:03

Hallo

Ich hab mal einen Code als tuple von vier Zahlen (0 … 9) aufgefasst, und das ganze nach Größe und Anzahl von geschlossenen Codefolgen aufgedröselt.

  1. from itertools import product
  2. from collections import Counter
  3.  
  4. alle = set(product(range(10),repeat=4))
  5. seen = set()
  6.  
  7. def next_code(code):
  8.     return code[1:] + (sum(code) % 10,)
  9.  
  10. def main():
  11.     ringcounter = Counter()
  12.     for code in alle:
  13.         if code in seen:
  14.             continue
  15.         codering = []
  16.         start = code[:]
  17.         c = 0
  18.         while True:
  19.             code = next_code(code)
  20.             codering.append(code)
  21.             c+=1
  22.             if code == start:
  23.                 break:
  24.         seen.update(codering)
  25.         ringcounter[c] += 1
  26.     kontrolle = 0
  27.     for key, value in ringcounter.items():
  28.         kontrolle += key * value
  29.         print( "{} Codereihen aus {} Codes".format(value, key))
  30.     print("kontrollsumme ist {}".format(kontrolle))
  31.                
  32.            
  33. main()
karolus
User
Beiträge: 100
Registriert: Samstag 22. August 2009, 22:34

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon karolus » Freitag 10. Februar 2017, 04:28

Hallo

Oder ein wenig übersichtlicher:

  1. from itertools import product
  2. from collections import Counter
  3.  
  4. def next_code(code):
  5.     return code[1:] + (sum(code) % 10,)        
  6.  
  7. def main():
  8.     pool = set(product(range(10),repeat=4))
  9.     ringcounter = Counter()    
  10.     while pool:
  11.         code = pool.pop()
  12.         codering = []
  13.         start = code[:]
  14.         c = 0
  15.         while True:
  16.             code = next_code(code)
  17.             codering.append(code)
  18.             c += 1
  19.             if code == start:
  20.                 break
  21.         pool = pool.difference(codering)
  22.         ringcounter[c] += 1
  23.            
  24.     kontrolle = 0
  25.     for key, value in ringcounter.items():
  26.         kontrolle += key * value
  27.         print( "{} Codereihen aus {} Codes".format(value, key))
  28.     print("kontrollsumme ist {}".format(kontrolle))
karolus
User
Beiträge: 100
Registriert: Samstag 22. August 2009, 22:34

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon karolus » Freitag 10. Februar 2017, 05:23

Version 0.0.3 mit Generatorfunktion:

  1. from itertools import product
  2. from collections import Counter
  3.  
  4. def iter_code(code):
  5.     nextcode = code
  6.     while True:
  7.         nextcode = nextcode[1:] + (sum(nextcode) % 10,)
  8.         if code == nextcode:
  9.             return
  10.         yield nextcode        
  11.  
  12. def main():
  13.     pool = set(product(range(10),repeat=4))
  14.     ringcounter = Counter()    
  15.     while pool:
  16.         code = pool.pop()
  17.         codering = [code]
  18.         for icode in iter_code(code):
  19.             codering.append(icode)
  20.         pool = pool.difference(codering)
  21.         ringcounter[len(codering)] += 1
  22.            
  23.     kontrolle = 0
  24.     for key, value in ringcounter.items():
  25.         kontrolle += key * value
  26.         print( "{} Codereihen aus {} Codes".format(value, key))
  27.     print("kontrollsumme ist {}".format(kontrolle))
sebastian0202
User
Beiträge: 138
Registriert: Montag 9. Mai 2016, 09:14
Wohnort: Berlin

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon sebastian0202 » Freitag 10. Februar 2017, 08:54

@BlackJack
Der Code kann mit einer Leichtigkeit gelesen werden, dass ist wahnsinn.
Jetzt ist mir einiges klarer.
Ich werde mir den Leitfaden ausdrucken und für neue Projekte als Vorlage hinlegen. :mrgreen:
Benutzeravatar
snafu
User
Beiträge: 5163
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon snafu » Freitag 10. Februar 2017, 11:11

Hier eine Variante, die deque() verwendet:
  1. from itertools import count
  2. from collections import deque
  3.  
  4. def count_days(code):
  5.     digits = deque(map(int, code), len(code))
  6.     old = deque(digits)
  7.     for num_days in count(1):
  8.         digits.append(sum(digits) % 10)
  9.         if digits == old:
  10.             return num_days
  11.  
  12. def main():
  13.     num_days = count_days('1986')
  14.     print('Code wiederholt sich nach', num_days, 'Tagen.')
  15.  
  16. if __name__ == '__main__':
  17.     main()

Der Einsatz von deque() hat den Vorteil, dass man beim Verschieben nichts herum kopieren muss. Gleichzeitig erspart einem die Betrachtung der Ziffern als Sequenz die entsprechende Rechnerei. Nur das Hinzufügen der letzten Stelle aus der Quersumme habe ich rechnerisch gelöst, da dies ein "billiger" Einzeiler ist.
shcol (Repo | Doc | PyPi)
Benutzeravatar
BlackJack
Moderator
Beiträge: 33025
Registriert: Dienstag 25. Januar 2005, 23:29
Wohnort: Berlin
Kontaktdaten:

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon BlackJack » Freitag 10. Februar 2017, 12:30

@snafu: Kleiner Bug in der Funktion: die funktioniert nur für Codes ≥1000.

Edit: Und geht auch davon aus das der Code jemals wieder erreicht wird, was man ohne Begründung IMHO nicht so einfach annehmen darf.
“Programs must be written for people to read, and only incidentally for machines to execute.” — Abelson & Sussman, SICP (preface to the first edition)
Benutzeravatar
BlackJack
Moderator
Beiträge: 33025
Registriert: Dienstag 25. Januar 2005, 23:29
Wohnort: Berlin
Kontaktdaten:

Re: AUFGABE: Jeden tag eine Änderung des Bankcodes...

Beitragvon BlackJack » Freitag 10. Februar 2017, 12:42

@karolus: Beim ersten Quelltext ist es äusserst unschön das `alle` und `seen` globale Variablen sind. Wobei `alle` auch gar kein `set` sein muss, denn es wird im `Code` ja nur darüber iteriert. Das ``product(range(10),repeat=4)`` hätte man gleich in die ``for``-Schleife schreiben können.

Der Code geht davon aus das man disjunkte Ringe/Kreise hat. Das muss man ja schon vorher wissen, oder irgendwie begründen können, dass der Graph so aussieht.
“Programs must be written for people to read, and only incidentally for machines to execute.” — Abelson & Sussman, SICP (preface to the first edition)

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder