--- engine: julia --- # Ein- und Ausgabe ```{julia} #| error: false #| echo: false #| output: false # https://github.com/JuliaLang/julia/blob/master/base/show.jl#L516-L520 # https://github.com/JuliaLang/julia/blob/master/base/show.jl#L3073-L3077 using InteractiveUtils import QuartoNotebookWorker Base.stdout = QuartoNotebookWorker.with_context(stdout) myactive_module() = Main.Notebook Base.active_module() = myactive_module() ``` ## Konsole Das Betriebssystem stellt für ein Programm üblicherweise 3 Kanäle _(streams)_ zur Verfügung: - Standardeingabekanal `stdin` - Standardausgabekanal `stdout` und - Standardfehlerausgabekanal `stderr`. Wenn das Programm in einem Terminal (oder Konsole bzw. Shell) gestartet wird, kann das Programm über `stdin` die Tastatureingaben einlesen und Ausgaben über `stdout` sowie `stdout` erscheinen im Terminal. - Schreiben nach `stdout`: `print()`,`println()`,`printstyled()` - Schreiben nach `stderr`: `print(strerr,...)`, `println(stderr,...)`, `printstyled(stderr,...)` - Lesen von `stdin`: `readline()` ### Eingaben Die Sprache _Python_ stellt eine Funktion `input()` zur Verfügung: ```{.python} ans = input("Bitte eine positive Zahl eingeben!") ``` Die Funktion gibt den Prompt aus, wartet auf eine Eingabe und liefert die Eingabe als `string` zurück. In Julia kann man diese Funktion so implementieren: ```{julia} function input(prompt = "Eingabe:") println(prompt) flush(stdout) return chomp(readline()) end ``` **Anmerkungen** - Schreibanweisungen werden von modernen Betriebssystemen gebuffert. Mit `flush(stdout)` wird die Leerung des Buffers und sofortige Schreiboperation erzwungen. - `readline()` liefert einen String zurück, der mit einer Newline `\n` endet. Die Funktion `chomp()` entfernt einen eventuellen Zeilenumbruch vom Ende eines Strings. ```{julia} #| eval: false a = input("Bitte 2 Zahlen eingeben!") ``` ```{julia} #| echo: false a = "34 56" ``` ### Verarbeitung der Eingabe > `split(str)` zerlegt einen String in "Wörter" und liefert einen _(array of strings)_: ```{julia} av = split(a) ``` > `parse(T, str)` versucht, `str` in den Typ `T` umzuwandeln: ```{julia} v = parse.(Int, av) ``` `parse()` erzeugt einen Fehler, wenn der String sich nicht als Wertangabe von Typ `T` parsen lässt. Man kann den Fehler mit `try/catch` abfangen oder die Funktion `tryparse(T, str)` verwenden, die in so einem Fall `nothing` zurückgibt - worauf man dann z.B. mit `isnothing()` testen kann. ### Einzelne Tastenanschläge einlesen - `readline()` u.ä. warten auf den Abschluss der Eingabe durch Drücken der `Enter`-Taste. - Techniken zum Einlesen einzelner _keystrokes_ findet man hier: - [https://stackoverflow.com/questions/56888266/how-to-read-keyboard-inputs-at-every-keystroke-in-julia](https://stackoverflow.com/questions/56888266/how-to-read-keyboard-inputs-at-every-keystroke-in-julia) - [https://stackoverflow.com/questions/60954235/how-can-i-test-whether-stdin-has-input-available-in-julia](https://stackoverflow.com/questions/60954235/how-can-i-test-whether-stdin-has-input-available-in-julia) ## Formatierte Ausgabe mit dem `Printf`-Makro Oft möchte man Zahlen oder Strings mit einer strikten Formatvorgabe - Gesamtlänge, Nachkommastellen, rechts/linksbündig usw - ausgeben. Dazu definiert das Paket `Printf` die Makros `@sprintf` und `@printf`, welche sehr ähnlich wie die gleichnamigen C-Funktionen arbeiten. ```{julia} using Printf x = 123.7876355638734 @printf("Ausgabe rechtsbündig mit max. 10 Zeichen Platz und 3 Nachkommastellen: x= %10.3f", x) ``` Das erste Argument ist ein String, der Platzhalter (hier: `%10.3`) für auszugebende Variablen enthält; gefolgt von diesen Variablen als weitere Argumente. Platzhalter haben die Form ``` %[flags][width][.precision]type ``` wobei die Angaben in eckigen Klammern alle optional sind. **Typangaben im Platzhalter** | | | |:--|:------------| |`%s`| `string`| |`%i`| `integer`| |`%o`| `integer octal (base=8)`| |`%x, %X`| `integer hexadecimal (base=16) with digits 0-9abcdef or 0-9ABCDEF, resp.`| |`%f`| `floating point number`| |`%e`| `floating point number, scientific representation`| |`%g`| `floating point, uses %f or %e depending on value`| : {.striped .hover} **Flags** | | | |:----|:-----| |Pluszeichen| rechtsbündig (Standard)| |Minuszeichen| linksbündig| |Null| mit führenden Nullen| : {.striped .hover} **Width** ``` Anzahl der minimal verwendeten Zeichen (wenn nötig, werden auch mehr genommen) ``` ### Beispiele: ```{julia} using Printf # Paket laden nicht vergessen! ``` ```{julia} @printf("|%s|", "Hallo") # string mit Platzhalter für String ``` Die senkrechten Striche sind nicht Teil des Platzhalters. Sie sollen die Begrenzung des Ausgabefeldes anzeigen. ```{julia} @printf("|%10s|", "Hallo") # Minimallänge, rechtsbündig ``` ```{julia} @printf("|%-10s|", "Hallo") # linksbündig ``` ```{julia} @printf("|%3s|", "Hallo") # Längenangabe kann überschritten werden # besser eine 'kaputt formatierte' Tabelle als falsche Werte! ``` ```{julia} j = 123 k = 90019001 l = 3342678 @printf("j= %012i, k= %-12i, l = %12i", j, k, l) # 0-Flag für führende Nullen ``` `@printf` und `@sprintf` können wie alle Makros wie Funktionen aufgerufen werden: ```{julia} @printf("%i %i", 22, j) ``` -- oder wie Makros, also ohne Funktionsklammern und ohne Komma: ```{julia} @printf "%i %i" 22 j ``` `@printf` kann als erstes Argument noch einen Stream übergeben bekommen. Ansonsten besteht die Argumentliste aus - Formatstring mit Platzhaltern - Variablen in der Reihenfolge der Platzhalter, in Anzahl und Typ zu den Platzhaltern passend ```{julia} @printf(stderr, "Erstes Resultat: %i %s\nZweites Resultat %i", j, "(geschätzt)" ,k) ``` Das Makro `@sprintf` druckt nichts, sondern liefert den ausgefüllten formatierten String zurück: ```{julia} str = @sprintf("x = %10.6f", π ); ``` ```{julia} str ``` ### Formatierung der Gleitkommazahlen: Bedeutung des _Precision_-Wertes: - `%f` und `%e`-Format: maximale Anzahl der Nachkommastellen - `%g`-Format: maximale Anzahl von ausgegebenen Ziffern (Vor- + Nachkommastellen) ```{julia} x = 123456.7890123456 @printf("%20.4f %20.4e", x, x) # 4 Nachkommastellen ``` ```{julia} @printf("%20.7f %20.7e", x, x) # 7 Nachkommastellen ``` ```{julia} @printf("%20.7g %20.4g", x, x) # insgesamt 7 bzw. 4 Stellen ``` ## Dateioperationen Dateien werden - geöffnet $\Longrightarrow$ dabei ensteht ein neues _stream_-Objekt (zusätzlich zu `stdin, stdout, stderr`) - dann kann dieser _stream_ gelesen und geschrieben werden - geschlossen $\Longrightarrow$ _stream_-Objekt wird von Datei getrennt ```{.julia} stream = open(path, mode) ``` - path: Dateiname/pfad - mode: ``` "r" read, öffnet am Dateianfang "w" write, öffnet am Dateianfang (Datei wird neu angelegt oder überschrieben) "a" append, öffnet zum Weiterschreiben am Dateiende ``` Schreiben wir mal eine Datei: ```{julia} file = open("datei.txt", "w") ``` ```{julia} @printf(file, "%10i\n", k) ``` ```{julia} println(file, " zweite Zeile") ``` ```{julia} close(file) ``` Schauen wir uns die Datei an: ```{julia} ;cat datei.txt ``` ...und jetzt öffnen wir sie wieder zum Einlesen: ```{julia} stream = open("datei.txt", "r") ``` `readlines(stream)` liefert alle Zeilen einer Textdatei als Vector von Strings. `eachline(stream)` liefert einen Iterator über die Zeilen der Datei. ```{julia} n = 0 for line in eachline(stream) # Lese zeilenweise n += 1 println(n, line) # Drucke mit Zeilennummer end close(stream) ``` ## Pakete für Dateiformate Für die Ein- und Ausgabe in den verschiedensten Dateiformaten existieren Julia-Pakete, z.B. - [PrettyTables.jl](https://ronisbr.github.io/PrettyTables.jl/stable/) Ausgabe von formatierten Tabellen - [DelimitedFiles.jl](https://docs.julialang.org/en/v1/stdlib/DelimitedFiles/) Ein- und Ausgabe von Matrizen u.ä. - [CSV.jl](https://csv.juliadata.org/stable/) Ein- und Ausgabe von Dateien mit "comma-separated values" u.ä. - [XLSX.jl](https://felipenoris.github.io/XLSX.jl/stable/tutorial/) Ein- und Ausgabe von Excel-Dateien und viele andere mehr... ### DelimitedFiles.jl Dieses Paket ermöglicht das bequeme Abspeichern/Einlesen von Matrizen. Dazu stellt es die Funktionen `writedlm()` und `readdlm()` zur Verfügung. ```{julia} using DelimitedFiles ``` Wir erzeugen eine 200×3-Matrix von Zufallszahlen ```{julia} A = rand(200,3) ``` und speichern diese ```{julia} f = open("data2.txt", "w") writedlm(f, A) close(f) ``` Die geschriebene Datei fängt so an: ```{julia} ;head data2.txt ``` Das Wiedereinlesen ist noch einfacher: ```{julia} B = readdlm("data2.txt") ``` Noch ein Punkt: Beim Umgang mit Dateien wird in Julia oft die `do`-Notation verwendet, s. @sec-do. Dazu nutzt man, dass `open()` auch Methoden hat, bei denen das 1. Argument eine `function(iostream)` ist. Diese wird dann auf den _stream_ angewendet und dieser abschliessend automatisch geschlossen. Die `do`-Notation erlaubt es, diese Funktion anonym nach dem `do` zu definieren: ```{julia} open("data2.txt", "w") do io writedlm(io, A) end ``` ### CSV und DataFrames - Das CSV-Format wird oft benutzt, um Tabellen in einer nicht nur mit MS Excel lesbaren Form zur Verfügung zu stellen. - Ein Beispiel ist die Wetter- und Klimadatenbank _Meteostat_. - Das Paket [DataFrames.jl](https://dataframes.juliadata.org/stable/) stellt Funktionen zum bequemen Umgang mit tabellarischen Daten zur Verfügung. ```{julia} using CSV, DataFrames, Downloads # Wetterdaten von Westerland, s. https://dev.meteostat.net/bulk/hourly.html url = "https://bulk.meteostat.net/v2/hourly/10018.csv.gz" http_response = Downloads.download(url) file = CSV.File(http_response, header=false); ``` Die Daten sehen so aus: ```{julia} # https://dev.meteostat.net/bulk/hourly.html#endpoints # # Spalte 1 Datum # 2 Uhrzeit (Stunde) # 3 Temp # 5 Luftfeuchtigkeit # 6 Niederschlag # 8 Windrichtung # 9 Windstärke df = DataFrame(file) ``` ```{julia} #| error: false #| echo: false #| output: false #| eval: false describe(df) ``` Zum bequemen Plotten und zum Umgang mit den Datums- und Zeitformaten in der Wettertabelle laden wir noch 2 Helferlein: ```{julia} using StatsPlots, Dates ``` Wir erzeugen eine neue Spalte, die Datum (aus Spalte 1) und Uhrzeit (aus Spalte 2) kombiniert: ```{julia} # neue Spalte mit Sp.1 und 2 (date & time) kombiniert df[!, :datetime] = DateTime.(df.Column1) .+ Hour.(df.Column2); ``` ```{julia} #| error: false #| echo: false #| output: false #| eval: false @df df plot(:datetime, :Column3) ``` Und nun zum Plot: ```{julia} @df df plot(:datetime, [:Column9, :Column6, :Column3], xlims = (DateTime(2023,9,1), DateTime(2024,5,30)), layout=(3,1), title=["Wind" "Regen" "Temp"], legend=:none, size=(800,800)) ```