@Buchfink: Ich habe darüber noch mal nachgedacht, und ein Beispiel gesucht für etwas in BASIC, weil der OP ja schon mal von BASIC und GOTO sprach. Beim Advent of Code 2015, Tag 18 war die Aufgabe eine quadratisches Gitter aus Lichtern nach bestimmten Regeln zu animieren (Conway lässt grüssen) und im zweiten Teil gibt es zwei verschiedene Arten von Lichtern — neben den normalen welche die ständig an sind, und die sich auch nicht ausschalten lassen. Die Aufgabe in Kurzform:
Teil 1:
Lichterquadrat bei dem die Anfangskonfiguration als Puzzle-Eingabe gegeben ist, das eine gegebene Anzahl von Schritten nach folgenden Regeln animiert werden soll:
- Ein Licht das an ist, bleibt an, falls 2 oder 3 Nachbarlichter an sind, sonst geht es aus.
- Ein Licht das aus ist, geht an, falls genau 3 Nachbarlichter an sind, sonst bleibt es aus.
Die Regeln gelten von einem Schritt zum nächsten für alle Lichter simultan, dass heisst das Schalten eines Lichts im aktuellen Schritt, hat keinen Einfluss auf die Anzahl der eingeschalteten Nachbarn für seine Nachbarlichter.
Nachbarn sind die 8 Felder direkt um ein Licht herum. Bei Lichtern am Rand zählen nicht vorhandene Nachbarn nachvollziebarerweise als ausgeschaltet.
Am Ende der vorgegebenen Anzahl von Schritten soll die Gesamthelligkeit berechnet werden, was laut Aufgabe der Anzahl der dann eingeschalteten Lichter entspricht.
Teil 2:
Das selbe wie in Teil 1, nur das jetzt die vier Lichter in den Ecken immer eingeschaltet sind, und sich nicht ausschalten lassen.
In der Aufgabe ist ein kleines 6×6 Beispiel zur Verdeutlichung und zum testen der eigenen Lösung geeignet. "#" ist ein eingeschaltetes Licht und "." ein ausgeschaltetes. Testlauf mit Debugausgaben vom Programm (der erste ”Initial state” entspricht dem Inhalt der Eingabedatei):
Code: Alles auswählen
$ ./day18_fb -h
Usage: ./day18_fb -h | [-s steps] [-d] [filename]
-h print help and exit
-s steps set number of steps (default 100)
-d print debug output
The default filename is input.txt.
$ ./day18_fb -s 5 -d test_input.txt
Initial state:
.#.#.#
...##.
#....#
..#...
#.#..#
####..
[Brightness: 15]
After 1 step:
..##..
..##.#
...##.
......
#.....
#.##..
[Brightness: 11]
After 2 steps:
..###.
......
..###.
......
.#....
.#....
[Brightness: 8]
After 3 steps:
...#..
......
...#..
..##..
......
......
[Brightness: 4]
After 4 steps:
......
......
..##..
..##..
......
......
[Brightness: 4]
After 5 steps:
......
......
..##..
..##..
......
......
[Brightness: 4]
Brightness after 5 steps without stuck corners: 4
Initial state:
##.#.#
...##.
#....#
..#...
#.#..#
####.#
[Brightness: 17]
After 1 step:
#.##.#
####.#
...##.
......
#...#.
#.####
[Brightness: 18]
After 2 steps:
#..#.#
#....#
.#.##.
...##.
.#..##
##.###
[Brightness: 18]
After 3 steps:
#...##
####.#
..##.#
......
##....
####.#
[Brightness: 18]
After 4 steps:
#.####
#....#
...#..
.##...
#.....
#.#..#
[Brightness: 14]
After 5 steps:
##.###
.##..#
.##...
.##...
#.#...
##...#
[Brightness: 17]
Brightness after 5 steps with stuck corners: 17
Das Programm hat einen abstrakten Typ `TBaseLight` für ein einzelnes Licht mit Methoden zum Umwandeln in eine Zeichenkette ("#" oder ".") einem abstrakten Property um den Zustand zu setzen und abzufragen, einem Property um die Helligkeit abzufragen (0 oder 1) und eine Methode um den Zustand über eine Zeichenkette ("#" oder ".") zu setzen.
Davon abgeleitet sind dann `TLight` und `TStuckLight` die jeweils das Zustandsproperty entsprechend implementieren. Also ein einfaches Beispiel für Polymorphie über Vererbung/Untertypen.
Bei der Implementierung ist mir noch ein Vorteil aufgefallen, der einfacher als Polymorphie ist: Klassen/Typen/Objekte sind Namensräume. Man muss nicht mehr so stark aufpassen eindeutige Prozedur oder Funktionsnamen zu wählen, weil `TBaseLight.brightness` eine andere Funktion als `TLightGrid.brightness` ist, man sich beim Aufruf aber auf `brightness` für beide beschränken kann, weil der Compiler aus dem Aufrufkontext weiss was gemeint ist. Wobei das natürlich streng genommen nicht auf OOP beschränkt ist, denn das könnte man auch über das Überladen von Funktionen/Prozeduren via Signatur erreichen. FreeBASIC könnte das sogar.
In FreeBASIC ist auch die folgende Lösung der Aufgabe geschrieben. Das ist als freier Compiler für QBasic-Quelltexte gestartet, aber mittlerweise um einige Features erweitert. Unter anderem Typen als ”Klassen” mit Properties und Methoden (und Zugriffsschutz).
Code: Alles auswählen
'
' Advent of Code 2015 - day 18
'
' Basic light type with boolean state and readonly brightness properties. The
' state can be set with a string and the object can be cast to a suitable
' string.
Type TBaseLight Extends Object
Const ON = "#"
Const OFF = "."
Declare Const Operator Cast() As String
Declare Const Abstract Property state As Boolean
Declare Abstract Property state(value As Boolean)
Declare Const Property brightness As Integer
' Set state from given string. Returns TRUE if the string is a valid state
' representation, FALSE otherwise.
Declare Function SetFromString(value As Const String) As Boolean
End Type
Const Operator TBaseLight.Cast() As String
Return IIf(state, ON, OFF)
End Operator
Const Property TBaseLight.brightness As Integer
Return IIf(state, 1, 0)
End Property
Function TBaseLight.SetFromString(value As Const String) As Boolean
Dim ok As Boolean
ok = TRUE
Select Case value
Case ON
state = TRUE
Case OFF
state = FALSE
Case Else
ok = FALSE
End Select
Return ok
End Function
' Normal light.
Type TLight Extends TBaseLight
Declare Const Property state As Boolean Override
Declare Property state(value As Boolean) Override
Private:
_state As Boolean
End Type
Const Property TLight.state As Boolean
Return _state
End Property
Property TLight.state(value As Boolean)
_state = value
End Property
' A stuck light. Reading the state is always TRUE, writing it has no effect.
Type TStuckLight Extends TBaseLight
Declare Const Property state As Boolean Override
Declare Property state(value As Boolean) Override
End Type
Const Property TStuckLight.state As Boolean
Return TRUE
End Property
Property TStuckLight.state(value As Boolean)
' Setting the state has no effect on a stuck light.
End Property
' A square grid of lights.
'
' The file format is one line per row with "." for a light in off state and "#"
' for a light in on state. Such a file can be loaded with the `Load` method.
'
Type TLightGrid
Declare Destructor
' Cast the this into a string of the format that `Load` expects in a file.
Declare Const Operator Cast() As String
' Load the state from given `filename`. If `hasStuckCorners` is TRUE then the
' lights in the four corners are always on, regardless of the value in the
' file.
Declare Sub Load(filename As Const String, hasStuckCorners As Boolean = FALSE)
' Advance the grid state one step.
'
' The rules are:
'
' * A light that is on, stays on if 2 or 3 neighbours are on,
' otherwise it goes off.
' * A light that is off, goes on if exactly 3 neighbours are on,
' otherwise it stays off.
'
' These rules are applied simultaneously to all lights.
Declare Sub DoStep
' The overall brightness of the light grid.
Declare Const Property brightness As Integer
Private:
size As Integer
lights(Any, Any) As TBaseLight Ptr
' Free the array/memory of the lights. Pulled into an own method because the
' destructor and `Load` need this.
Declare Sub Reset
' Get the light state at given coordinates. If coordinates are outside the
' grid, return FALSE.
Declare Const Function GetStateAt(i As Integer, j As Integer) As Boolean
' Test wether the given coordinates are a corner of the grid.
Declare Const Function IsCorner(i As Integer, j As Integer) As Boolean
' Count the neighbouring lights that are on. This considers all eight
' neighbours around the given coordinates. Off grid positions are
' considered being off.
Declare Const Function CountLitNeightbours(i As Integer, j As Integer) As Integer
End Type
Sub TLightGrid.Reset
Dim i As Integer, j As Integer
If size > 0 Then
For i = 1 To size
For j = 1 To size: Delete lights(i, j): Next
Next
End If
size = 0: Erase lights
End Sub
Destructor TLightGrid
Reset
End Destructor
Const Operator TLightGrid.Cast() As String
Dim result As String, i As Integer, j As Integer, k As Integer
' Reserve space for a `size` by `size` character grid with a line ending
' character at the end of each row.
result = Space(size * (size + 1))
k = 1
For i = 1 To size
For j = 1 To size
Mid(result, k) = *lights(i, j)
k = k + 1
Next
Mid(result, k) = Chr(10)
k = k + 1
Next
Return result
End Operator
Const Function TLightGrid.GetStateAt(i As Integer, j As Integer) As Boolean
If 1 <= i And i <= size And 1 <= j And j <= size Then
Return lights(i, j)->state
Else
Return FALSE
End If
End Function
Const Function TLightGrid.IsCorner(i As Integer, j As Integer) As Boolean
Return i = 1 And j = 1 _
Or i = 1 And j = size _
Or i = size And j = 1 _
Or i = size And j = size
End Function
Sub TLightGrid.Load(filename As Const String, hasStuckCorners As Boolean)
Dim f As Long, row As String, i As Integer, j As Integer, c As String
Dim light As TBaseLight Ptr
If size > 0 Then Reset
f = FreeFile
Open filename For Input As #f
Line Input #f, row
size = Len(row)
ReDim lights(1 To size, 1 To size)
For i = 1 To size
For j = 1 To size
If hasStuckCorners And IsCorner(i, j) Then
light = New TStuckLight
Else
light = New TLight
End If
c = Mid(row, j, 1)
If Not light->SetFromString(c) Then
Print row
Print Spc(j - 1); "^"
Print "Unexpected ";
If c = "" Then
Print "end of file";
Else
Print "character '"; c; "'";
End If
Print " at line"; i
End 1
End If
lights(i, j) = light
Next
Line Input #f, row
Next
Close #f
End Sub
Const Function TLightGrid.CountLitNeightbours(i As Integer, j As Integer) As Integer
Dim result As Integer, di As Integer, dj As Integer
result = 0
If size > 0 Then
For di = -1 To 1
For dj = -1 To 1
If Not (di = 0 And dj = 0) Then
If GetStateAt(i + di, j + dj) Then result = result + 1
End If
Next
Next
End If
Return result
End Function
Sub TLightGrid.DoStep
Dim newStates(1 To size, 1 To size) As Boolean
Dim i As Integer, j As Integer, count As Integer
If size > 0 Then
' Calculate new states.
For i = 1 To size
For j = 1 To size
count = CountLitNeightbours(i, j)
If GetStateAt(i, j) Then
newStates(i, j) = count = 2 Or count = 3
Else
newStates(i, j) = count = 3
End If
Next
Next
' Set new states.
For i = 1 To size
For j = 1 To size
lights(i, j)->state = newStates(i, j)
Next
Next
End If
End Sub
Const Property TLightGrid.brightness As Integer
Dim i As Integer, j As Integer, result As Integer
result = 0
If size > 0 Then
For i = 1 To size
For j = 1 To size
result = result + lights(i, j)->brightness
Next
Next
End If
Return result
End Property
' Print the step number, the grid state, and the grid's brightness for debugging
' purposes.
Sub PrintLights(lights As Const TLightGrid, stepNumber As Integer = 0)
Print
If stepNumber = 0 Then
Print "Initial state:"
Else
Print "After"; stepNumber; " step"; IIf(stepNumber > 1, "s", ""); ":"
End If
Print lights;
Print "[Brightness:"; lights.brightness; "]"
End Sub
'
' The main program.
'
Const DEFAULT_STEP_COUNT = 100, DEFAULT_FILENAME = "input.txt"
Dim filename As String, stepCount As Integer, debug As Boolean
filename = DEFAULT_FILENAME
stepCount = DEFAULT_STEP_COUNT
debug = FALSE
Scope
Dim i As Integer
i = 1
Do While Command(i) <> ""
Select Case Command(i)
Case "-h"
Print "Usage: "; Command(0); " -h | [-s steps] [-d] [filename]"
Print
Print " -h print help and exit"
Print "-s steps set number of steps (default"; DEFAULT_STEP_COUNT; ")"
Print " -d print debug output"
Print
Print "The default filename is "; DEFAULT_FILENAME; "."
End 0
Case "-s"
i = i + 1
stepCount = Val(Command(i))
Case "-d"
debug = TRUE
Case Else
If Left(Command(i), 1) = "-" Then
Print "Unknown option: "; Command(i); "."
Print "Try -h for help."
End 1
End If
filename = Command(i)
End Select
i = i + 1
Loop
End Scope
Scope
Dim lights As TLightGrid, i As Integer, hasStuckCorners As Integer
For hasStuckCorners = 0 To 1
lights.Load filename, hasStuckCorners
If debug Then PrintLights lights
If stepCount > 0 Then
For i = 1 To stepCount
lights.DoStep
If debug Then PrintLights lights, i
Next
End If
Print "Brightness after"; stepCount; " steps "; _
IIf(hasStuckCorners, "with", "without"); " stuck corners:"; _
lights.brightness
Next
End Scope
Konkreter Fall beim Erstkontakt mit ”OOP” war bei mir Informatikunterricht mit Turbo Pascal. Der Lehrplan sah die Entwicklung einer Adressverwaltung mit Units und RECORDs vor. Und das haben wir auch so durchgezogen. Und ganz am Ende hat uns der Lehrer dann die tollen neuen OOP-Konstrukte in Turbo Pascal 5.x gezeigt, in dem er das Program ”objectorientiert” umgeschrieben hat. Also letztlich aus den RECORDs halt Objekte gemacht, was syntaktisch halt ein kleines bisschen anders aussah, aber ich hatte so überhaupt nicht verstanden warum der so aus dem Häusschen war, denn so hat man natürlich nicht wirklich einen Vorteil gesehen, denn letztendlich war es ja das gleiche Programm.
Was mir zu dem Zeitpunkt natürlich auch noch nicht klar war, war das OOP ein Paradigma ist und keine Spracheigenschaft. Letztlich war die prozedurale Version von dem Programm schon sehr nah an der Idee von OOP dran. Was wohl auch ein Grund war, warum ich den Quantensprung in der OOP-Variante nicht erkennen konnte, denn der war halt wirklich sehr klein.
Klick gemacht hat es dann erst bei Java. Das ist ja die Holzhammermethode weil da *alles* in Klassen gepresst werden muss.
Hm, ob bei `__str__()` und `print()` der funktionale Part zum Tragen kommt ist eine gute Frage. Letztlich ist Python “durch und durch“ objektorientiert, denn jeder Typ ist eine Klasse und jeder Wert ist ein Objekt. Das schliesst ja Funktionen mit ein. Auch das sind Objekte, die halt *eine* sehr wichtige Methode haben: `__call__()`. Denn ``something(42)`` steht ja letztlich die Kurzform für ``something.__call__(42)``. Und ob `something` nun für ein Objekt vom Typ `function` steht, oder irgendwas anderes das `__call__()` implementiert, ist ja letztlich egal. Wenn es ``def`` für Funktionen nicht gäbe, sondern nur für Methoden, würde man halt mehr Boilerplate schreiben:
Code: Alles auswählen
class Something:
def __call__(self, argument):
...
something = Something()
# statt einfach nur
def something(argument):
...
Ist der erste Code jetzt irgendwie objektorientierter, nur weil man da den Boilerplate mit der Klasse schreiben muss? Und wird es plötzlich funktional, wenn man den Boilerplate weglassen kann?
Die Funktionen die in der Regel einer ”magischen“ Methode entsprechen wie `str()`, `repr()`, `len()`, und so weiter, kann man auch als Operatoren auffassen. Das war mal die Antwort von Guido warum `len()` eine Funktion ist. Er sah das eher als Operator, den man überladen kann, und der einen Namen hat, statt dafür irgendein kryptisches Zeichen als extra Syntax zu definieren.