- Offizieller Beitrag
Hi,
einige von euch werden wohl auch durch L3viathans Rätsel, wie ich, erstmalig mit der Programmiersprache Brainfuck Kontakt gehabt haben.
Ich finde diese recht originell, wenn auch nicht unbedingt sehr sinnvoll, wird dem Namen gerecht. Wenn man das Prinzip verstanden hat, ist die Sprache nicht mal schwer, nur extrem unübersichtlich. Ich habe mal einen BF-Text-Interpreter geschrieben, der in BF-Code dargestellten Text in eine lesbare Ausgabe verwandelt.
Der erste String ist optimiert erstellt (relativ adressiert), der zweite ist ohne jede Optimierung. Ein deutlicher Unterschied.
Viel Spaß damit.
Edit:
Kleiner Nachtrag noch. Der Interpreter arbeitet nicht mit geschachtelten Schleifen. Diese sollten aber selbst bei super-optimierten Texten nicht auftreten.
Edit 2:
Einen Bug gefixt (Pointerinitialisierung).
Edit 3: NEU
Erweitert um die Funktion _AsciiToBFcode(). Hiermit läßt sich normaler Text in BrainFuck-Code ausgeben.
Es gibt unendlich viele Möglichkeiten von BF-Code, der letztlich denselben Ascii-String ausgibt.
Ich habe mir folgendes Verfahren entwickelt:
- Ermitteln der Ascii-Werte für jedes Zeichen
- Runden der Ascii-Werte auf glatte Zehner
- für jeden unterschiedlichen glatten Zehnerwert wird eine Zelle belegt und mit diesem Wert befüllt
jetzt wird Zeichen für Zeichen durchgegangen:
- Pointer auf Zelle mit zugehörigem gerundeten Zehnerwert
- je nach Differenz zum tatsächlichem Ascii-Wert in/dekrementieren
- Zeichenausgabe
- Zelle auf den vorigen Wert zurücksetzen
Sicher kann man noch weiter optimieren, aber für den Anfang finde ich es schon recht gut gelungen.
Edit 4:
Ich habe die Ascii-to-BF Funktion noch 2-mal verbessert. Bei langen Texten nun eine Verkürzung des Codes um 50-70 % im Vergleich zur ersten Variante.
Ich setzte jetzt in den Referenzzellen den Wert nach Nutzung nicht zurück, sondern speichere den zuletzt genutzten Wert. Wird die Referenzzelle wieder genutzt bildet sich der Wert zum In/Dekrementieren aus Differenz Ascci-Wert und letztem in der Zelle genutzten Wert. Das ist doch wesentlich effektiver als in den alten Varianten.
Ich habe alle 3 Versionen im Skript belassen, da könnt ihr ja vergleichen.
Einen Bug habe ich auch noch gefixt in der Interpreter-Hilfsfunktion. Waren mehrere gleiche Zeichen am Stringende, trat ein Indexfehler auf.
BF-Text-Interpreter
#cs
Der C-Sourcecode eines in Brainfuck geschriebenen Programms hat nur eine minimallänge von 1055 Bytes - also rund 1KB. Brainfuck arbeitet (standardmäßig) auf einem 30000 Bytes großen Array und einem Pointer, der irgendwo in das Array zeigt. Der Pointer kann vor- und zurückgesetzt werden, die Werte unter dem Counter können hoch- und runtergezählt, eingelesen und ausgegeben werden. Außerdem gibt es ein sehr hübsches Schleifenkonstrukt.
Befehle
[/autoit] [autoit][/autoit] [autoit]Brainfuck kommt mit sage und schreibe 8 Befehlen aus. Daher kommt auch die Bezeichnung "minimalistische" Programmiersprache da die Sprache über ein Minimum an Befehlen - in diesem Fall 8 - verfügt.
[/autoit] [autoit][/autoit] [autoit]Befehl C Equivalent Beschreibung
> ++ptr; Setzt den Pointer um 1 weiter
< --ptr; Setzt den Pointer um 1 zurück
+ ++*ptr; Den Wert des Elements im Array um 1 erhöhen
- --*ptr; Den Wert des Elements im Array um 1 verringern
[ while(*ptr){ Starte einer Schleife - Abbruchbedingung ist, das der Wert des Elements im Array unter dem Pointer 0 beträgt
] } Ende einer Schleife
. putchar(*ptr); ASCII-Code des Wertes unter dem Pointer ausgeben
, *ptr=getchar(); Zeichen einlesen und im Array unter dem Pointer speichern
Jedes Array und jedes Element eines Arrays wird, mit dem ersten Setzen des Pointers auf dieses Element mit 0 initialisiert. Der Pointer selbst steht zu Beginn ebenfalls immer auf 0.
#ce
#include-once
#include <Array.au3>
#include <String.au3>
Local $sBF_1 = '>++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.'
Local $sBF_2 = '>++++++++++[>+>+++>+++++>+++++++>++++++++++>+++++++++++>++++++++++++<<<<<<<-]>>>>-----.>>>---.-.<+.<<++++++++.>>>.<<<<<++.>>>>--.<+..>>.-.<<<<<.>>-------.>>+++++.<----.>---------.+++++.<<++++.>>>++.<<++.>---.<<<----.<<+++.---.'
ConsoleWrite('$sBF_1 & $sBF_2:' & @CRLF)
ConsoleWrite(_BFtextInterpreter($sBF_1) & _BFtextInterpreter($sBF_2) & @CRLF)
$BFcode = _AsciiToBFcode( _
'Befehl C Equivalent Beschreibung' & @CRLF & _
'> ++ptr; Setzt den Pointer um 1 weiter' & @CRLF & _
'< --ptr; Setzt den Pointer um 1 zurück' & @CRLF & _
'+ ++*ptr; Den Wert des Elements im Array um 1 erhöhen' & @CRLF & _
'- --*ptr; Den Wert des Elements im Array um 1 verringern' & @CRLF & _
'[ while(*ptr){ Starte einer Schleife - Abbruchbedingung ist, das der Wert des Elements im Array unter dem Pointer 0 beträgt' & @CRLF & _
'] } Ende einer Schleife' & @CRLF & _
'. putchar(*ptr); ASCII-Code des Wertes unter dem Pointer ausgeben' & @CRLF & _
', *ptr=getchar(); Zeichen einlesen und im Array unter dem Pointer speichern' & @CRLF )
ConsoleWrite('BF-Beschreibung als BF-Code:' & @CRLF & $BFcode & @crlf)
ConsoleWrite('und zurück:' & @crlf & _BFtextInterpreter($BFcode) & @CRLF)
ConsoleWrite('ASCII-to-BF in 3 Versionen:' & @CRLF)
ConsoleWrite(_AsciiToBFcode_1('Hello World!') & @CRLF)
ConsoleWrite(_AsciiToBFcode_2('Hello World!') & @CRLF)
ConsoleWrite(_AsciiToBFcode('Hello World!') & @CRLF) ; aktuelle Version
Func _BFtextInterpreter($sBFcode)
Local $aCode = StringSplit(StringStripWS($sBFcode, 8), '', 2)
Local $sOut = '', $index = 0, $count, $iLoop = -1
Local $a[30000], $pt = 0
Do
If $index < UBound($aCode) -1 Then
$count = __CountEvenNeighbour($aCode, $index)
Else
$count = 0
EndIf
Switch $aCode[$index]
Case '>'
$pt += 1 + $count
$index += 1 + $count
Case '<'
$pt -= (1 + $count)
$index += 1 + $count
Case '+'
$a[$pt] += 1 + $count
$index += 1 + $count
Case '-'
$a[$pt] -= 1 + $count
$index += 1 + $count
Case '['
$iLoop = $index +1
$index += 1 + $count
Case ']'
If $a[$pt] = 0 Then
$iLoop = -1
$index += 1 + $count
Else
$index = $iLoop
EndIf
Case '.'
$sOut &= Chr($a[$pt])
$index += 1
Case ',' ; erwartet Input von Standardeingabe
;~ $a[$pt] = Asc('Input')
;~ $index += 1
EndSwitch
Until $index = UBound($aCode)
Return $sOut
EndFunc ;==>_BFtextInterpreter
Func __CountEvenNeighbour(ByRef $aCode, $iStart)
Local $counter = 0
While $aCode[$iStart +$counter +1] == $aCode[$iStart]
$counter += 1
If $iStart +$counter +1 = UBound($aCode) Then ExitLoop
WEnd
Return $counter
EndFunc ;==>__CountEvenNeighbour
Func _AsciiToBFcode($sString) ; Version 3
Local $aString = StringSplit($sString, '', 2)
Local $aWork[UBound($aString)][3], $aRounded[1][2]
; === ASCII-Werte ermitteln und die zugehörigen gerundeten Zehner-ASCII-Werte
For $i = 0 To UBound($aString) -1
$aWork[$i][0] = Asc($aString[$i]) ; ASCII-Wert des Zeichens
$aWork[$i][1] = Round($aWork[$i][0] /10) *10 ; auf Zehnerwert auf/abgerundeter ASCII-Wert
$aWork[$i][2] = $i ; Position im Ausgangsstring
Next
; === nach ASCII-Werten aufsteigend sortieren
_ArraySort($aWork)
; === gerundete Zehner-ASCII-Werte zusammenfassen
$aRounded[0][0] = $aWork[0][1] ; gerundeter Zehner-ASCII-Wert
$aRounded[0][1] = $aWork[0][1] ; momentan genutzter Wert
For $i = 0 To UBound($aWork) -1
If $i < UBound($aWork) -1 Then
If $aWork[$i][1] <> $aWork[$i+1][1] Then
ReDim $aRounded[UBound($aRounded)+1][2]
$aRounded[UBound($aRounded)-1][0] = $aWork[$i+1][1]
$aRounded[UBound($aRounded)-1][1] = $aWork[$i+1][1]
EndIf
EndIf
Next
; === wieder nach Reihenfolge Zeichen sortieren
_ArraySort($aWork, 0, 0,0,2)
; === je eine Zelle auf einen einmal vorkommenden gerundeten Zehner-ASCII-Wert setzen
Local $sBF = '>++++++++++[' ; Faktor 10 vor der Schleife erstellen
For $i = 0 To UBound($aRounded) -1
$sBF &= '>' & _StringRepeat('+', $aRounded[$i][0]/10) ; Faktor für Zehner-Multiplikation
Next
$sBF &= _StringRepeat('<', UBound($aRounded)) & '-]' ; zurück auf Schleifenzähler und dekrementieren
$sBF &= '>' ; Pointer auf erste Wertezelle
Local $index, $index_last = 0, $step = 0, $diff = 0, $nextStep
For $i = 0 To UBound($aString) -1 ; durch Eingabestring iterieren
$index = _ArraySearch($aRounded, $aWork[$i][1])
$diff = $index - $index_last
$index_last = $index
Select
Case $diff < 0
$nextStep = _StringRepeat('<', Abs($diff))
Case 0
$nextStep = ''
Case Else
$nextStep = _StringRepeat('>', $diff)
EndSelect
$sBF &= $nextStep
$diff = $aWork[$i][0] - $aRounded[$index][1] ; Differenz zw. tatsächlichem Wert und zuletzt genutztem Wert
; === Zelle unter Pointer um $diff in/dekrementieren, Zeichen ausgeben
Select
Case $diff < 0
$nextStep = _StringRepeat('-', Abs($diff))
Case 0
$nextStep = ''
Case Else
$nextStep = _StringRepeat('+', $diff)
EndSelect
$aRounded[$index][1] += $diff ; zuletzt genutzten Wert in Zelle aktualisieren
$sBF &= $nextStep & '.'
Next
Return $sBF
EndFunc ;==>_AsciiToBFcode
Func _AsciiToBFcode_2($sString) ; Version 2
Local $aString = StringSplit($sString, '', 2)
Local $aWork[UBound($aString)][3], $aRounded[1][2]
; === ASCII-Werte ermitteln und die zugehörigen gerundeten Zehner-ASCII-Werte
For $i = 0 To UBound($aString) -1
$aWork[$i][0] = Asc($aString[$i]) ; ASCII-Wert des Zeichens
$aWork[$i][1] = Round($aWork[$i][0] /10) *10 ; auf Zehnerwert auf/abgerundeter ASCII-Wert
$aWork[$i][2] = $i ; Position im Ausgangsstring
Next
; === nach ASCII-Werten aufsteigend sortieren
_ArraySort($aWork)
; === gerundete Zehner-ASCII-Werte zusammenfassen
$aRounded[0][0] = $aWork[0][1]
$aRounded[0][1] = 1
For $i = 0 To UBound($aWork) -1
If $i < UBound($aWork) -1 Then
If $aWork[$i][1] <> $aWork[$i+1][1] Then
ReDim $aRounded[UBound($aRounded)+1][2]
$aRounded[UBound($aRounded)-1][0] = $aWork[$i+1][1]
$aRounded[UBound($aRounded)-1][1] = 1
Else
$aRounded[UBound($aRounded)-1][1] += 1
EndIf
EndIf
Next
; === wieder nach Reihenfolge Zeichen sortieren
_ArraySort($aWork, 0, 0,0,2)
; === je eine Zelle auf einen einmal vorkommenden gerundeten Zehner-ASCII-Wert setzen
Local $sBF = '>++++++++++[' ; Faktor 10 vor der Schleife erstellen
For $i = 0 To UBound($aRounded) -1
$sBF &= '>' & _StringRepeat('+', $aRounded[$i][0]/10) ; Faktor für Zehner-Multiplikation
Next
$sBF &= _StringRepeat('<', UBound($aRounded)) & '-]' ; zurück auf Schleifenzähler und dekrementieren
$sBF &= '>' ; Pointer auf erste Wertezelle
Local $index, $index_last = 0, $step = 0, $diff = 0, $nextStep, $backStep, $lastback, $nextIsSame = False
For $i = 0 To UBound($aString) -1 ; durch Eingabestring iterieren
$index = _ArraySearch($aRounded, $aWork[$i][1])
$diff = $index - $index_last
$index_last = $index
Select
Case $diff < 0
$nextStep = _StringRepeat('<', Abs($diff))
Case 0
$nextStep = ''
Case Else
$nextStep = _StringRepeat('>', $diff)
EndSelect
$sBF &= $nextStep
$diff = $aWork[$i][0] - $aWork[$i][1] ; Differenz zw. tatsächlichem Wert und Round-Wert
; === Zelle unter Pointer um $diff in/dekrementieren, Zeichen ausgeben, Wert zurücksetzen
; === nur wenn Folgezeichen <> aktuellem Zeichen
If Not $nextIsSame Then
Select
Case $diff < 0
$nextStep = _StringRepeat('-', Abs($diff))
$backStep = _StringRepeat('+', Abs($diff))
Case 0
$nextStep = ''
Case Else
$nextStep = _StringRepeat('+', $diff)
$backStep = _StringRepeat('-', $diff)
EndSelect
$lastback = $backStep
EndIf
$aRounded[$index][1] -= 1
; === wenn Zelle letztmalig verwendet wurde, Wert in Zelle nicht mehr zurücksetzen
If $aRounded[$index][1] = 0 Then $backStep = ''
; === wenn Folgezeichen identisch, dann Wert auch nicht zurücksetzen
If ($i < UBound($aString) -2) And ($aString[$i] == $aString[$i+1]) Then
$nextIsSame = True
$lastback = $backStep
$backStep = ''
ElseIf ($i < UBound($aString) -2) And ($aString[$i] <> $aString[$i+1]) Then
$nextIsSame = False
$backStep = $lastback
EndIf
$sBF &= $nextStep & '.' & $backStep
Next
Return $sBF
EndFunc ;==>_AsciiToBFcode
Func _AsciiToBFcode_1($sString) ; Version 1
Local $aString = StringSplit($sString, '', 2)
Local $aWork[UBound($aString)][3], $sUniqueRounded
; === ASCII-Werte ermitteln und die zugehörigen gerundeten Zehner-ASCII-Werte
For $i = 0 To UBound($aString) -1
$aWork[$i][0] = Asc($aString[$i]) ; ASCII-Wert des Zeichens
$aWork[$i][1] = Round($aWork[$i][0] /10) *10 ; auf Zehnerwert auf/abgerundeter ASCII-Wert
$aWork[$i][2] = $i ; Position im Ausgangsstring
Next
; === nach ASCII-Werten aufsteigend sortieren
_ArraySort($aWork)
; === gerundete Zehner-ASCII-Werte zusammenfassen
$sUniqueRounded = $aWork[UBound($aWork)-1][1]
For $i = UBound($aWork) -1 To 0 Step -1
If $i > 0 Then
If $aWork[$i][1] <> $aWork[$i-1][1] Then $sUniqueRounded = $aWork[$i-1][1] & ',' & $sUniqueRounded
EndIf
Next
; === wieder nach Reihenfolge Zeichen sortieren
_ArraySort($aWork, 0, 0,0,2)
Local $aUnique = StringSplit($sUniqueRounded, ',', 2) ; Array mit gerundeten Zehner-ASCII-Werten
; === je eine Zelle auf einen einmal vorkommenden gerundeten Zehner-ASCII-Wert setzen
Local $sBF = '>++++++++++[' ; Faktor 10 vor der Schleife erstellen
For $i = 0 To UBound($aUnique) -1
$sBF &= '>' & _StringRepeat('+', $aUnique[$i]/10) ; Faktor für Zehner-Multiplikation
Next
$sBF &= _StringRepeat('<', UBound($aUnique)) & '-]' ; zurück auf Schleifenzähler und dekrementieren
$sBF &= '>' ; Pointer auf erste Wertezelle
Local $index, $index_last = 0, $step = 0, $diff = 0, $nextStep, $backStep
For $i = 0 To UBound($aString) -1
$index = _ArraySearch($aUnique, $aWork[$i][1])
$diff = $index - $index_last
$index_last = $index
Select
Case $diff < 0
$nextStep = _StringRepeat('<', Abs($diff))
Case 0
$nextStep = ''
Case Else
$nextStep = _StringRepeat('>', $diff)
EndSelect
$sBF &= $nextStep
$diff = $aWork[$i][0] - $aWork[$i][1] ; Differenz zw. tatsächlichem Wert und Round-Wert
; === Zelle unter Pointer um $diff in/dekrementieren, Zeichen ausgeben, Wert zurücksetzen
Select
Case $diff < 0
$nextStep = _StringRepeat('-', Abs($diff))
$backStep = _StringRepeat('+', Abs($diff))
Case 0
$nextStep = ''
Case Else
$nextStep = _StringRepeat('+', $diff)
$backStep = _StringRepeat('-', $diff)
EndSelect
If $i = UBound($aString) -1 Then $backStep = ''
$sBF &= $nextStep & '.' & $backStep
Next
Return $sBF
EndFunc ;==>_AsciiToBFcode