Das ist ein großes Thema! Man kann ein langes Buch darüber schreiben, und mehrere Leute haben das auch getan (suche bei Amazon nach „unicode book“, um ein paar Beispiele zu sehen).
Der Umgang mit Zeichen in Computern war in früheren Jahrzehnten viel einfacher, als Programmierer annahmen, Englisch sei die einzige wichtige Sprache. Also: 26 Buchstaben, Groß- und Kleinschreibung, 10 Ziffern, ein paar Satzzeichen, dazu ein Code (0x07) für einen Signalton, und das alles passte in 7 Bit: der ASCII-Zeichensatz.
Natürlich fragten sich die Leute bald, was mit à, ä und Ł ist, dann fragten andere nach ऄ, ஹ und ญ, und die jungen Leute wollten Emojis 😱. Was tun?
Um es kurz zu machen: Viele kluge und geduldige Menschen mussten jahrelang in Ausschüssen sitzen und die Details des Unicode-Zeichensatzes und von Kodierungen wie UTF-8 ausarbeiten, und jede Menge Software musste sehr aufwendig neu geschrieben werden. Außerdem wurden viele neue Bugs eingebaut.
Damit nicht alles kaputtgeht, sorgt das Design von Unicode/UTF-8 dafür, dass die ersten 127 Codes mit ASCII identisch sind (sogar der Signalton).
Sprachen, die nach etwa 2005 entworfen wurden, haben den großen Vorteil, dass bereits ein einigermaßen stabiler Unicode-Standard existierte.
Julia (erstmals 2012 veröffentlicht) konnte davon ausgehen, dass alles Unicode sein würde: Zeichen, Strings, Variablen- und Funktionsnamen, mathematische Operatoren ...
Um das Handbuch zu zitieren: „Julia macht den Umgang mit reinem ASCII-Text einfach und effizient, und der Umgang mit Unicode ist so einfach und effizient wie möglich.“ Beachte das „wie möglich“, es ist ein wichtiger Teil der Aussage.
Zeichenliterale schreibt man in einfache Anführungszeichen, und sie unterscheiden sich von Strings, die in doppelte Anführungszeichen gesetzt werden.
Für Leute aus der C/C++-Welt ist das offensichtlich, für Python- und JavaScript-Programmierer aber möglicherweise verwirrend.
julia> a = 'a' # Roman alphabet
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
julia> typeof(a)
Char
julia> jha = 'झ' # Devanagari alphabet
'झ': Unicode U+091D (category Lo: Letter, other)
julia> typeof(jha)
Char
julia> '❤' # heart emoji
'❤': Unicode U+2764 (category So: Symbol, other)
An den Beispielen sehen wir, dass der Typ Char ist, und Julia hat weitere Informationen über die Kategorie eines Zeichens.
Bei genauerem Hinsehen zeigt sich, dass diese Zeichen mit 4 Hexadezimalziffern dargestellt werden können. Der vollständige Zeichensatz braucht bis zu 6 Hexadezimalziffern.
Diese Zahlen heißen „Codepunkte“ und reichen derzeit von U+0000 bis U+10FFFF.
Sie werden in der REPL angezeigt, aber im Code verwendest du codepoints(), um sie zu erhalten.
Die Umwandlung zwischen Char und Int ist einfach:
julia> Int('a')
97
julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
Der Compiler erlaubt manche Formen von Ganzzahlarithmetik mit Chars:
julia> 'b' - 'a' # interval, in alphabetic order
1
julia> 'b' + 'a'
ERROR: MethodError: no method matching +(::Char, ::Char)
julia> 'a' + 5
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)
julia> 'f' + ('A' - 'a') # same as `uppercase('f')`
'F': ASCII/Unicode U+0046 (category Lu: Letter, uppercase)
Viele Funktionen, die mit Strings arbeiten, können auch einen Char als Eingabewert verarbeiten.
uppercase() und lowercase().isuppercase() und islowercase().isletter() deckt viele Alphabete abisdigit(), prüft streng auf 0:9isnumeric(), weiter gefasst als isdigit, also true für ¾ und verschiedene nichteuropäische Schriftsystemeisxdigit(), Hexadezimalziffernisascii(), für Zeichen aus der Zeit vor Unicodeispunct(), Satzzeichenisspace(), jedes Whitespace-Zeichenisprint(), druckbare Zeichen (das Gegenteil ist iscntrl())islowercase('A') # false
uppercase('γ') # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@') # true
isdigit('A') # false
isxdigit('A') # true
Um einen String in einen Char-Vektor umzuwandeln, kannst du collect() verwenden.
Für den umgekehrten Weg vom Char-Vektor zum String gibt es den Konstruktor String().
julia> s = "abcde"
"abcde"
julia> cv = collect(s)
5-element Vector{Char}:
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)
julia> String(cv)
"abcde"
Das funktioniert mit beliebigen Zeichen, nicht nur mit ASCII.
julia> collect("❤,😱")
3-element Vector{Char}:
'❤': Unicode U+2764 (category So: Symbol, other)
',': ASCII/Unicode U+002C (category Po: Punctuation, other)
'😱': Unicode U+1F631 (category So: Symbol, other)
Beachte, dass der Konstruktor String() auf einen Vector wirkt.
Um ein einzelnes Char in einen String mit einem Zeichen zu casten, verwendest du die Funktion string() mit kleinem s.
julia> string('a')
"a"
Alles bisher in diesem Dokument wirkt relativ einfach. Muss man sich also wirklich keine großen Sorgen machen?
Leider ist das zu optimistisch!
Eine Komplikation ergibt sich aus der Notwendigkeit von „bis zu“ 6 Hexadezimalziffern pro Codepunkt. Das bedeutet, dass verschiedene Zeichen unterschiedlich viel Speicherplatz benötigen, wenn sie in UTF-8 kodiert werden.
Ein Byte kann nur (vorzeichenlose) Zahlen bis 255 speichern, also zwei Hexadezimalziffern. Deshalb verwendet UTF-8 eine variable Anzahl von Bytes (1 bis 4), um ein Char zu speichern.
Diese heißen „Codeeinheiten“, und die Funktion ncodeunits() gibt die Anzahl zurück, die ein gegebenes Zeichen benötigt.
julia> codepoint(jha) # jha 'झ' is defined in an earlier example
0x0000091d
julia> ncodeunits(jha)
3
julia> ncodeunits('a') # ASCII character
1
julia> ncodeunits('😱') # emoji
4
Außerdem hat nicht alles, was auf dem Bildschirm dargestellt werden kann, einen eigenen eindeutigen Codepunkt. Manche optisch unterscheidbaren Zeichen gelten als von anderen abgeleitet, sodass Unicode sie als Basiszeichen mit einem Modifikator behandelt.
Dieses Problem betrifft Strings und erschwert dort die Indexierung.
In dieser Übung implementierst du einen Teil einer Sammlung von Hilfsroutinen, die einem Entwickler dabei helfen, Bezeichnernamen aufzuräumen.
In den 6 Aufgaben baust du nach und nach die Funktionen transform zum Umwandeln einzelner Zeichen und clean zum Umwandeln von Strings auf.
Ein gültiger Bezeichner besteht aus null oder mehr Buchstaben, Unterstrichen, Bindestrichen, Fragezeichen und Emojis.
Wenn der clean-Funktion ein leerer String übergeben wird, soll ein leerer String zurückgegeben werden.
Implementiere die transform-Funktion, um alle Bindestriche durch Unterstriche zu ersetzen.
julia> transform('-')
"_"
Entferne alle Whitespace-Zeichen. Dazu gehören auch führende und abschließende Whitespace-Zeichen.
julia> transform(' ')
""
Ändere die transform-Funktion so, dass sie camelCase in kebab-case umwandelt.
julia> transform('D')
"-d"
Ändere die transform-Funktion so, dass sie alle Zeichen weglässt, die numerisch sind.
julia> transform('7')
""
Ändere die transform-Funktion so, dass sie alle griechischen Buchstaben im Bereich 'α' bis 'ω' ersetzt.
julia> transform('β')
"?"
Implementiere die clean-Funktion, um diese Operationen auf einen gesamten String anzuwenden.
Zeichen, die nicht unter diese Regeln fallen, sollen unverändert durchgeleitet werden.
julia> clean(" a2b Cd-ω😀 ")
"ab-cd_?😀"
Melde dich bei Exercism an, um Julia mit 35 Konzepte128 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.