Ein String ist in Julia eine geordnete Folge von Zeichen. Wie das Handbuch anmerkt: „Natürlich fängt der eigentliche Ärger an, wenn man fragt, was ein Zeichen ist.“
Ein Großteil dieser Komplexität wird im Chars-Konzept ausführlicher behandelt, aber kurz gesagt:
Im Allgemeinen werden Strings in doppelte Anführungszeichen " " gesetzt.
Einfache Anführungszeichen ' ' werden nur für Chars verwendet.
Man kann auch dreifache doppelte Anführungszeichen """ """ verwenden, wodurch " innerhalb des Strings ohne Escaping möglich ist.
Wichtiger noch: Bei mehrzeiligen Strings wird unerwünschte zusätzliche Einrückung entfernt.
julia> """
Multiline
String
"""
"Multiline\nString\n"
Wie die meisten Sprachen seit C verwendet Julia einen Backslash \ zum Escapen:
\n im vorigen Beispiel.\$ (siehe unten).Ein String ist eine iterierbare Sammlung, deshalb funktioniert eine for ... in-Schleife, ohne dass man ihn in einen Vektor von Zeichen umwandeln muss.
julia> s = "Julia"
"Julia"
# admittedly, this is a silly example
julia> for c in s; print(c, "; "); end
J; u; l; i; a;
Das Iterieren auf diese Weise hat den Vorteil, dass Julia Zeichengrenzen korrekt behandelt, auch bei Nicht-ASCII-Strings. Wie wichtig das ist, wird im nächsten Abschnitt klarer.
Grundsätzlich lassen sich Strings genau wie Vektoren indizieren:
julia> s[1], s[5]
('J', 'a')
Bei einem ASCII-String wie „Julia“ funktioniert das offensichtlich, aber damit ignoriert man die meisten Sprachen der Welt.
Betrachte eines der Zeichen in Beowulf, das vor etwa 15 Jahrhunderten im Altenglischen geschrieben wurde.
„Hrōðgār“ besteht eindeutig aus 7 Zeichen, aber nicht alle davon sind ASCII.
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> isascii(king)
false
# collect() converts to a Char vector:
julia> collect(king)
7-element Vector{Char}:
'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
'ō': Unicode U+014D (category Ll: Letter, lowercase)
'ð': Unicode U+00F0 (category Ll: Letter, lowercase)
'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
'ā': Unicode U+0101 (category Ll: Letter, lowercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
Die Indizierung eines solchen Strings beginnt gut, bricht aber irgendwann zusammen:
julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)
julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'
Das Problem ist, dass eine solche Indizierung Bytes zählt, die bei Nicht-ASCII-Strings keine 1:1-Beziehung zu den Zeichen haben.
Ein Unicode/UTF-8-Zeichen kann bis zu 4 Bytes zu seiner Darstellung benötigen, und das Chars-Konzept geht näher darauf ein.
Viele Sprachen verwenden + als Operator zum Verketten von Strings, Julia aber nicht.
Immerhin ist die Fehlermeldung hilfreich.
julia> "s1 " + "s2"
ERROR: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with *
julia> "s1 " * "s2"
"s1 s2"
Die Funktion string() verkettet eine beliebige Anzahl von Strings.
julia> string("lots ", "of ", "bits")
"lots of bits"
Die Funktion join() macht dasselbe und nimmt außerdem einen Vektor von Strings und verkettet sie mit einem gegebenen Trennzeichen (standardmäßig "").
julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"
Beachte, dass Strings unveränderlich sind und dass Verketten ein Kopieren bedeutet. Deshalb ist wiederholtes Verketten innerhalb einer Schleife ineffizient; besser sammelst du die Fragmente in einem Vektor und fügst sie erst am Ende zusammen.
Ganz einfach:
julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"
Viele Sprachen haben eine Möglichkeit, berechnete Werte in Strings einzufügen, doch darüber, welche Syntax dafür am besten ist, herrscht erstaunliche Uneinigkeit.
In Julia steht vor interpolierten Werten ein $.
julia> myvar = 42
42
julia> "myvar evaluates to $myvar"
"myvar evaluates to 42"
julia> r = 5.3
5.3
julia> "A circle of radius $r has area $(π * r^2)"
"A circle of radius 5.3 has area 88.24733763933729"
Runde Klammern sind erforderlich, wenn das Weglassen zu Mehrdeutigkeiten führen würde, bei einem einzelnen Bezeichner, auf den ein Leerzeichen folgt, sind sie optional.
Um ein $ in den String aufzunehmen, escape es als \$.
Beachte die Einschränkung, dass die Interpolation derzeit keine Formatierung erlaubt, etwa die Anzahl der anzuzeigenden Nachkommastellen.
Mögliche Workarounds:
round() packen.@sprintf() wird weiter unten beschrieben.@sprintf()Für eine feinere Kontrolle über den Zusammenbau von Strings übernimmt Julia die Funktion sprintf aus C (und aus mehreren späteren Sprachen: Wikipedia listet etwa 30 auf).
In Julia ist das als macro im Modul Printf implementiert, daher das Präfix @.
julia> using Printf
julia> r = 5.3
5.3
julia> @sprintf("A circle of radius %.1f has area %.2f", r, π * r^2)
"A circle of radius 5.3 has area 88.25"
Der Einfachheit halber konzentriert sich dieser Abschnitt auf Funktionen, die einen neuen String zurückgeben und die Eingabe unverändert lassen.
Viele haben ein Gegenstück mit dem Suffix !, das die Eingabe direkt verändert.
Wie lang ist ein String? Manchmal hängt das davon ab, wie du zählst.
Der einfache Fall sind ASCII-Strings, bei denen jedes Zeichen 1 Byte belegt.
julia> string_asc = "Julia"
"Julia"
julia> length(string_asc)
5
julia> sizeof(string_asc)
5
Die Funktion length() gibt die Anzahl der Zeichen zurück, die Funktion sizeof() die Anzahl der Bytes.
Bei anderen Zeichensätzen wird dieser Unterschied wichtig:
julia> king = "Hrōðgār"
"Hrōðgār"
julia> length(king)
7
julia> sizeof(king)
10
Eine häufige Operation beim Programmieren besteht darin, einen langen String anhand eines Trennstrings aus einem oder mehreren Zeichen in Stücke zu zerlegen.
Am allgemeinsten verwenden wir die Funktion split().
Das Trennzeichen ist standardmäßig (beliebiger) Whitespace, aber es können auch andere angegeben werden.
julia> split("my little string")
3-element Vector{SubString{String}}:
"my"
"little"
"string"
julia> split("My_snake_case_string", "_")
4-element Vector{SubString{String}}:
"My"
"snake"
"case"
"string"
Diese Beispiele zerlegen einen String in kleinere Strings. Einen String in Zeichen zu zerlegen ist eine andere Operation.
julia> collect("input string")
12-element Vector{Char}:
'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
'p': ASCII/Unicode U+0070 (category Ll: Letter, lowercase)
'u': ASCII/Unicode U+0075 (category Ll: Letter, lowercase)
't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
's': ASCII/Unicode U+0073 (category Ll: Letter, lowercase)
't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
Enthält ein String einen bestimmten Teilstring?
Seltsamerweise gibt es zwei Funktionen, um das zu prüfen: occursin() und contains() unterscheiden sich nur in der Reihenfolge ihrer Argumente.
julia> needle = "Julia"
"Julia"
julia> haystack = "Python, Julia and R"
"Python, Julia and R"
julia> occursin(needle, haystack)
true
julia> contains(haystack, needle)
true
Genauer bezüglich der Position:
julia> startswith(haystack, "Python")
true
julia> endswith(haystack, "Python")
false
Um die Start- und Endindizes eines Teilstrings zu erhalten, gibt es mehrere Funktionen:
julia> findfirst(needle, haystack)
9:13
Um Teilstrings zu ersetzen, liste die Änderungen mit der Syntax x => y auf.
julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"
Seit Julia 1.7 kann die Funktion replace eine beliebige Anzahl von Änderungen entgegennehmen, mit der Garantie, dass kein Zeichen während des Ersetzens mehr als einmal geändert wird.
Die Syntax x => y heißt pair, ein wichtiger Typ in Julia, der im Dicts and Pairs-Konzept ausführlicher behandelt wird.
Hinweis: Der Einfachheit halber verwenden alle Beispiele oben String-Literale. Viele der Funktionen sind allgemeiner und funktionieren auch mit regulären Ausdrücken. Wir kommen in einem späteren Konzept darauf zurück.
Oft müssen wir führenden und/oder abschließenden Whitespace aus einem String entfernen.
Dafür gibt es die Funktionen lstrip() (links), rstrip() (rechts) und strip() (beides).
julia> strip("\n\t useful_bit\n\n")
"useful_bit"
Diese Funktionen haben recht selbsterklärende Namen:
julia> s = repeat("aBcD ", 5)
"aBcD aBcD aBcD aBcD aBcD "
julia> uppercase(s)
"ABCD ABCD ABCD ABCD ABCD "
julia> lowercase(s)
"abcd abcd abcd abcd abcd "
julia> titlecase(s) # Initial letter of each word uppercase
"Abcd Abcd Abcd Abcd Abcd "
Zahlen und ihre String-Darstellung ineinander umzuwandeln ist oft nützlich.
Von der Zahl zum String ist einfach, allerdings braucht eine andere Basis als die Standardbasis 10 einen zusätzlichen Parameter:
julia> string(42)
"42"
julia> string(42, base=16)
"2a"
julia> string(42, base=2)
"101010"
Einen String zu parsen, um einen Zahlenwert zu erhalten, ist etwas komplizierter und fehleranfälliger. Die Angabe des Ziel-Zahlentyps ist Pflicht. Die Angabe der Basis ist optional und standardmäßig 10.
julia> parse(Int, "42")
42
julia> parse(Float64, "42")
42.0
julia> parse(Int, "42?")
ERROR: ArgumentError: invalid base 10 digit '?' in "42?"
julia> parse(Int, "42 ") # whitespace may be tolerated
42
Manchmal ist es nützlich, die übliche Interpolation und das Escaping innerhalb von Strings zu unterdrücken.
Dazu stellst du dem öffnenden Anführungszeichen raw voran.
julia> x = 10
10
julia> "x = $x if we use \$ for interpolation"
"x = 10 if we use \$ for interpolation"
julia> raw"x = $x if we use \$ for interpolation"
"x = \$x if we use \\\$ for interpolation"
Reguläre Ausdrücke haben ihre eigene komplizierte Syntax, und Julia bietet zwei hilfreiche String-Typen: r" " für den Regex und s" " für die Arbeit mit erfassten Fragmenten.
Verschiedene andere spezielle Strings sind beim Programmieren im Exercism-Stil weniger verbreitet, werden aber beim Erstellen von Bibliotheken häufig verwendet.
v"x.y.z" behandeln die Major.Minor.Patch-Nummerierung für Software-Releases.b" " umgehen die Unicode-Zeichengrenzen und arbeiten auf Byte-Ebene.