JuliaKurs23/chapters/13_IO.qmd

480 lines
11 KiB
Plaintext
Raw Normal View History

2024-05-13 00:38:46 +02:00
---
engine: julia
---
# Ein- und Ausgabe
2024-05-13 00:38:46 +02:00
```{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
2024-05-13 00:38:46 +02:00
using InteractiveUtils
import QuartoNotebookWorker
Base.stdout = QuartoNotebookWorker.with_context(stdout)
myactive_module() = Main.Notebook
Base.active_module() = myactive_module()
2024-05-13 00:38:46 +02:00
```
2024-05-31 20:30:57 +02:00
2024-05-12 19:50:45 +02:00
## Konsole
Das Betriebssystem stellt für ein Programm üblicherweise 3 Kanäle _(streams)_ zur Verfügung:
- Standardeingabekanal `stdin`
- Standardausgabekanal `stdout` und
- Standardfehlerausgabekanal `stderr`.
2024-05-12 19:50:45 +02:00
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.
2024-05-12 19:50:45 +02:00
- Schreiben nach `stdout`: `print()`,`println()`,`printstyled()`
- Schreiben nach `stderr`: `print(strerr,...)`, `println(stderr,...)`, `printstyled(stderr,...)`
- Lesen von `stdin`: `readline()`
### Eingaben
2024-05-12 19:50:45 +02:00
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.
2024-05-12 19:50:45 +02:00
In Julia kann man diese Funktion so implementieren:
2024-05-12 19:50:45 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
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.
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-31 20:30:57 +02:00
#| eval: false
2024-05-12 19:50:45 +02:00
a = input("Bitte 2 Zahlen eingeben!")
```
2024-05-31 20:30:57 +02:00
```{julia}
#| echo: false
a = "34 56"
```
### Verarbeitung der Eingabe
> `split(str)` zerlegt einen String in "Wörter" und liefert einen _(array of strings)_:
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
av = split(a)
```
> `parse(T, str)` versucht, `str` in den Typ `T` umzuwandeln:
2024-05-12 19:50:45 +02:00
```{julia}
v = parse.(Int, av)
2024-05-12 19:50:45 +02:00
```
`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.
2024-05-12 19:50:45 +02:00
### 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:
2024-05-12 19:50:45 +02:00
- [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)
2024-05-12 19:50:45 +02:00
```
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
2024-05-12 19:50:45 +02:00
```
%[flags][width][.precision]type
2024-05-12 19:50:45 +02:00
```
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**
2024-05-12 19:50:45 +02:00
```
Anzahl der minimal verwendeten Zeichen (wenn nötig, werden auch mehr genommen)
```
### Beispiele:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
using Printf # Paket laden nicht vergessen!
```
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@printf("|%s|", "Hallo") # string mit Platzhalter für String
```
Die senkrechten Striche sind nicht Teil des Platzhalters. Sie sollen die Begrenzung des Ausgabefeldes anzeigen.
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@printf("|%10s|", "Hallo") # Minimallänge, rechtsbündig
```
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@printf("|%-10s|", "Hallo") # linksbündig
```
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@printf("|%3s|", "Hallo") # Längenangabe kann überschritten werden
# besser eine 'kaputt formatierte' Tabelle als falsche Werte!
2024-05-12 19:50:45 +02:00
```
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
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:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@printf("%i %i", 22, j)
```
-- oder wie Makros, also ohne Funktionsklammern und ohne Komma:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@printf "%i %i" 22 j
```
`@printf` kann als erstes Argument noch einen Stream übergeben bekommen.
2024-05-12 19:50:45 +02:00
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:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
str = @sprintf("x = %10.6f", π );
```
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
str
```
### Formatierung der Gleitkommazahlen:
2024-05-12 19:50:45 +02:00
Bedeutung des _Precision_-Wertes:
- `%f` und `%e`-Format: maximale Anzahl der Nachkommastellen
- `%g`-Format: maximale Anzahl von ausgegebenen Ziffern (Vor- + Nachkommastellen)
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
x = 123456.7890123456
@printf("%20.4f %20.4e", x, x) # 4 Nachkommastellen
```
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@printf("%20.7f %20.7e", x, x) # 7 Nachkommastellen
```
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@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`)
2024-05-12 19:50:45 +02:00
- dann kann dieser _stream_ gelesen und geschrieben werden
- geschlossen $\Longrightarrow$ _stream_-Objekt wird von Datei getrennt
2024-05-12 19:50:45 +02:00
```{.julia}
2024-05-12 19:50:45 +02:00
stream = open(path, mode)
```
- path: Dateiname/pfad
- mode:
2024-05-12 19:50:45 +02:00
```
"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:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
file = open("datei.txt", "w")
2024-05-12 19:50:45 +02:00
```
```{julia}
@printf(file, "%10i\n", k)
2024-05-12 19:50:45 +02:00
```
2024-05-27 21:56:40 +02:00
```{julia}
println(file, " zweite Zeile")
2024-05-12 19:50:45 +02:00
```
2024-05-27 21:56:40 +02:00
```{julia}
close(file)
2024-05-12 19:50:45 +02:00
```
Schauen wir uns die Datei an:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
;cat datei.txt
```
...und jetzt öffnen wir sie wieder zum Einlesen:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
stream = open("datei.txt", "r")
2024-05-12 19:50:45 +02:00
```
`readlines(stream)` liefert alle Zeilen einer Textdatei als Vector von Strings.
`eachline(stream)` liefert einen Iterator über die Zeilen der Datei.
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
n = 0
for line in eachline(stream) # Lese zeilenweise
2024-05-12 19:50:45 +02:00
n += 1
println(n, line) # Drucke mit Zeilennummer
2024-05-12 19:50:45 +02:00
end
close(stream)
2024-05-12 19:50:45 +02:00
```
## Pakete für Dateiformate
Für die Ein- und Ausgabe in den verschiedensten Dateiformaten existieren Julia-Pakete, z.B.
2024-05-12 19:50:45 +02:00
- [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.ä.
2024-05-12 19:50:45 +02:00
- [XLSX.jl](https://felipenoris.github.io/XLSX.jl/stable/tutorial/) Ein- und Ausgabe von Excel-Dateien
und viele andere mehr...
### DelimitedFiles.jl
2024-05-12 19:50:45 +02:00
Dieses Paket ermöglicht das bequeme Abspeichern/Einlesen von Matrizen. Dazu stellt es die Funktionen `writedlm()` und `readdlm()` zur
Verfügung.
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
using DelimitedFiles
```
Wir erzeugen eine 200×3-Matrix von Zufallszahlen
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
A = rand(200,3)
```
und speichern diese
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
f = open("data2.txt", "w")
writedlm(f, A)
close(f)
```
Die geschriebene Datei fängt so an:
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
;head data2.txt
```
Das Wiedereinlesen ist noch einfacher:
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
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:
2024-05-12 19:50:45 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
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.
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
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);
2024-05-12 19:50:45 +02:00
```
Die Daten sehen so aus:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
# https://dev.meteostat.net/bulk/hourly.html#endpoints
#
2024-05-12 19:50:45 +02:00
# Spalte 1 Datum
# 2 Uhrzeit (Stunde)
# 3 Temp
# 5 Luftfeuchtigkeit
# 6 Niederschlag
# 8 Windrichtung
# 9 Windstärke
df = DataFrame(file)
```
2024-05-27 21:56:40 +02:00
```{julia}
#| error: false
#| echo: false
#| output: false
#| eval: false
2024-05-12 19:50:45 +02:00
describe(df)
```
Zum bequemen Plotten und zum Umgang mit den Datums- und Zeitformaten in der Wettertabelle
laden wir noch 2 Helferlein:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
using StatsPlots, Dates
2024-05-12 19:50:45 +02:00
```
2024-06-05 13:14:30 +02:00
Wir erzeugen eine neue Spalte, die Datum (aus Spalte 1) und Uhrzeit (aus Spalte 2) kombiniert:
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
# neue Spalte mit Sp.1 und 2 (date & time) kombiniert
df[!, :datetime] = DateTime.(df.Column1) .+ Hour.(df.Column2);
```
2024-05-27 21:56:40 +02:00
```{julia}
#| error: false
#| echo: false
#| output: false
#| eval: false
2024-05-12 19:50:45 +02:00
@df df plot(:datetime, :Column3)
```
Und nun zum Plot:
2024-05-12 19:50:45 +02:00
2024-05-27 21:56:40 +02:00
```{julia}
2024-05-12 19:50:45 +02:00
@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))
2024-05-12 19:50:45 +02:00
```