--- engine: julia --- ```{julia} #| error: false #| echo: false #| output: false using InteractiveUtils import QuartoNotebookWorker Base.stdout = QuartoNotebookWorker.with_context(stdout) ``` ```{julia} #| error: false #| echo: false #| output: false flush(stdout) ``` # Input and Output ```{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() ``` ## Console The operating system normally provides 3 channels (_streams_) for a program: - Standard input channel `stdin` - Standard output channel `stdout` and - Standard error output channel `stderr`. When the program is started in a terminal (or console or shell), the program can read keyboard input via `stdin` and output appears in the terminal via `stdout` and `stdout`. - Writing to `stdout`: `print()`,`println()`,`printstyled()` - Writing to `stderr`: `print(strerr,...)`, `println(stderr,...)`, `printstyled(stderr,...)` - Reading from `stdin`: `readline()` ### Input The language _Python_ provides a function `input()`: ```{.python} ans = input("Please enter a positive number!") ``` The function prints the prompt, waits for input, and returns the input as a `string`. In Julia, you can implement this function as follows: ```{julia} function input(prompt = "Input:") println(prompt) flush(stdout) return chomp(readline()) end ``` **Comments** - Write instructions are buffered by modern operating systems. With `flush(stdout)`, the buffer is emptied and the write operation is forced to complete immediately. - `readline()` returns a string ending with a newline `\n`. The function `chomp()` removes a possible line break from the end of a string. ```{julia} #| eval: false a = input("Please enter 2 numbers!") ``` ```{julia} #| echo: false a = "34 56" ``` ### Processing the Input > `split(str)` splits a string into "words" and returns an _(array of strings)_: ```{julia} av = split(a) ``` > `parse(T, str)` tries to convert `str` to type `T`: ```{julia} v = parse.(Int, av) ``` `parse()` generates an error if the string cannot be parsed as a value of type `T`. You can catch the error with `try/catch` or use the function `tryparse(T, str)`, which returns `nothing` in such a case - on which you can then e.g. test with `isnothing()`. ### Reading Individual Keystrokes - `readline()` and similar functions wait for the input to be completed by pressing the `Enter` key. - Techniques for reading individual _keystrokes_ can be found here: - [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) ## Formatted Output with the `Printf` Macro Often you want to output numbers or strings with a strict format specification - total length, decimal places, right/left-aligned, etc. To this end, the `Printf` package defines the macros `@sprintf` and `@printf`, which work very similarly to the corresponding C functions. ```{julia} using Printf x = 123.7876355638734 @printf("Output right-aligned with max. 10 character width and 3 decimal places: x= %10.3f", x) ``` The first argument is a string containing placeholders (here: `%10.3`) for variables to be output; followed by these variables as further arguments. Placeholders have the form ``` %[flags][width][.precision]type ``` where the entries in square brackets are all optional. **Type specifications in placeholders** | | | |:--|:------------| |`%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** | | | |:----|:-----| |Plus sign| right-aligned (default)| |Minus sign| left-aligned| |Zero| with leading zeros| : {.striped .hover} **Width** ``` Number of minimum characters used (more will be taken if necessary) ``` ### Examples: ```{julia} using Printf # Don't forget to load the package! ``` ```{julia} @printf("|%s|", "Hello") # string with placeholder for string ``` The vertical bars are not part of the placeholder. They are intended to indicate the boundaries of the output field. ```{julia} @printf("|%10s|", "Hello") # Minimum length, right-aligned ``` ```{julia} @printf("|%-10s|", "Hello") # left-aligned ``` ```{julia} @printf("|%3s|", "Hello") # Length specification can be exceeded # Better a 'badly formatted' table than incorrect values! ``` ```{julia} j = 123 k = 90019001 l = 3342678 @printf("j= %012i, k= %-12i, l = %12i", j, k, l) # 0-flag for leading zeros ``` `@printf` and `@sprintf` can be called like functions or as macros: ```{julia} @printf("%i %i", 22, j) ``` -- or as macros, i.e., without function parentheses and without comma: ```{julia} @printf "%i %i" 22 j ``` `@printf` can take a stream as its first argument. Otherwise, the argument list consists of - format string with placeholders - variables in the order of the placeholders, matching in number and type to the placeholders ```{julia} @printf(stderr, "First result: %i %s\nSecond result %i", j, "(estimated)" ,k) ``` The macro `@sprintf` does not print anything but returns the filled formatted string: ```{julia} str = @sprintf("x = %10.6f", π ); ``` ```{julia} str ``` ### Formatting Floating Point Numbers: Meaning of the _precision_ value: - `%f` and `%e` format: maximum number of decimal places - `%g` format: maximum number of digits output (integer + decimal places) ```{julia} x = 123456.7890123456 @printf("%20.4f %20.4e", x, x) # 4 decimal places ``` ```{julia} @printf("%20.7f %20.7e", x, x) # 7 decimal places ``` ```{julia} @printf("%20.7g %20.4g", x, x) # total 7 and 4 digits respectively ``` ## File Operations Files are - opened $\Longrightarrow$ a new _stream_-object is created (in addition to `stdin, stdout, stderr`) - then this _stream_ can be read from and written to - closed $\Longrightarrow$ _stream_-object is detached from file ```{.julia} stream = open(path, mode) ``` - path: filename/path - mode: ``` "r" read, opens at file beginning "w" write, opens at file beginning (file is created or overwritten) "a" append, opens to continue writing at file end ``` Let's write a file: ```{julia} file = open("datei.txt", "w") ``` ```{julia} @printf(file, "%10i\n", k) ``` ```{julia} println(file, " second line") ``` ```{julia} close(file) ``` Let's look at the file: ```{julia} ;cat datei.txt ``` ...and now we open it again for reading: ```{julia} stream = open("datei.txt", "r") ``` `readlines(stream)` returns all lines of a text file as a vector of strings. `eachline(stream)` returns an iterator over the lines of the file. ```{julia} n = 0 for line in eachline(stream) # Read line by line n += 1 println(n, line) # Print with line number end close(stream) ``` ## Packages for File Formats For input and output in various file formats, there are Julia packages, e.g., - [PrettyTables.jl](https://ronisbr.github.io/PrettyTables.jl/stable/) Output of formatted tables - [DelimitedFiles.jl](https://docs.julialang.org/en/v1/stdlib/DelimitedFiles/) Input and output of matrices, etc. - [CSV.jl](https://csv.juliadata.org/stable/) Input and output of "comma-separated values" files, etc. - [XLSX.jl](https://felipenoris.github.io/XLSX.jl/stable/tutorial/) Input and output of Excel files and many more... ### DelimitedFiles.jl This package enables convenient saving/reading of matrices. It provides the functions `writedlm()` and `readdlm()`. ```{julia} using DelimitedFiles ``` We generate a 200×3 matrix of random numbers ```{julia} A = rand(200,3) ``` and save it ```{julia} f = open("data2.txt", "w") writedlm(f, A) close(f) ``` The written file starts like this: ```{julia} ;head data2.txt ``` Reading it back is even simpler: ```{julia} B = readdlm("data2.txt") ``` One more point: In Julia, the `do` notation is often used for file handling, see @sec-do. This uses the fact that `open()` also has methods where the 1st argument is a `function(iostream)`. This function is then applied to the _stream_ and the stream is automatically closed at the end. The `do` notation allows you to define this function anonymously after the `do`: ```{julia} open("data2.txt", "w") do io writedlm(io, A) end ``` ### CSV and DataFrames - The CSV format is often used to provide tables in a form that can be read not only by MS Excel. - An example is the weather and climate database _Meteostat_. - The [DataFrames.jl](https://dataframes.juliadata.org/stable/) package provides functions for convenient handling of tabular data. ```{julia} using CSV, DataFrames, Downloads # Weather data from Westerland, see 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); ``` The data looks like this: ```{julia} # https://dev.meteostat.net/bulk/hourly.html#endpoints # # Column 1 Date # 2 Time (hour) # 3 Temperature # 5 Humidity # 6 Precipitation # 8 Wind direction # 9 Wind speed df = DataFrame(file) ``` ```{julia} #| error: false #| echo: false #| output: false #| eval: false describe(df) ``` For convenient plotting and handling of date and time formats in the weather table, we load two helper packages: ```{julia} using StatsPlots, Dates ``` We create a new column that combines date (from column 1) and time (from column 2): ```{julia} # new column combining col. 1 and 2 (date & time) df[!, :datetime] = DateTime.(df.Column1) .+ Hour.(df.Column2); ``` ```{julia} #| error: false #| echo: false #| output: false #| eval: false @df df plot(:datetime, :Column3) ``` And now to the plot: ```{julia} @df df plot(:datetime, [:Column9, :Column6, :Column3], xlims = (DateTime(2023,9,1), DateTime(2024,5,30)), layout=(3,1), title=["Wind" "Rain" "Temp"], legend=:none, size=(800,800)) ```