---
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 typically provides three channels (_streams_) for a program:

- Standard input (`stdin`)
- Standard output (`stdout`)
- Standard error (`stderr`)

When executed in a terminal, console, or shell, the program reads keyboard input through `stdin` and outputs to the terminal via `stdout` and `stderr`.


- Writing to `stdout`: `print()`, `println()`, `printstyled()`
- Writing to `stderr`:  `print(stderr,...)`, `println(stderr,...)`, `printstyled(stderr,...)`
- Reading from `stdin`: `readline()`


### Input

The _Python_ language provides an `input()` function:
```{.python}
ans = input("Please enter a positive number!")
```
It prints the prompt, waits for input, and returns 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 operations are buffered by modern operating systems. `flush(stdout)` empties the buffer and forces the write operation to complete immediately. 
- `readline()` returns a string ending with a newline (`\n`). The function `chomp()` removes a trailing line break from the string. 

```{julia}
#| eval: false
a = input("Please enter two numbers!")
```
```{julia}
#| echo: false
a = "34 56"
```


### Processing the Input

> `split(str)` splits a string into "words", returning a string array:

```{julia}
av = split(a)
```


> `parse(T, str)` tries to convert `str` to type `T`:


```{julia}
v = parse.(Int, av)
```

`parse()` throws an error if the string cannot be parsed as type `T`. You can catch the error with
`try/catch`, or use `tryparse(T, str)`, which returns `nothing` in such cases. Test the result with `isnothing()`. 



## Formatted Output with the `Printf` Macro

You often need to output numbers or strings with strict formatting: total length, decimal places, alignment, etc. 

For this purpose, the `Printf` package defines the macros `@sprintf` and `@printf`, which work 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.3f`) for the variables, followed by the variables themselves. 

Placeholders have the form:
```
 %[flags][width][.precision]type
```
where entries in square brackets are optional.

**Type specifications in placeholders** 

| |  |
|:--|:------------| 
|`%s`|      `string`|
|`%i`|      `integer`|
|`%o`|      `integer, octal (base 8)`|
|`%x, %X`|  `integer, hexadecimal (base 16), digits 0-9a-f or 0-9A-F`|
|`%f`|      `floating point`|
|`%e`|      `floating point, scientific notation`|
|`%g`|      `floating point, %f or %e as appropriate`|

: {.striped .hover}


**Flags**

|  | |
|:----|:-----|
|Plus sign|    right-aligned (default) |
|Minus sign|   left-aligned |
|Zero|         adds leading zeros |

: {.striped .hover}


**Width**

```
Minimum number of characters used (more will be taken if necessary)
```


### Examples


```{julia}
using Printf    # Load the package first
```


```{julia}
@printf("|%s|", "Hello")     # string with placeholder for string
```
The vertical bars are not part of the placeholder; they indicate the output field boundaries.


```{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 wrong 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:

```{julia}
@printf("%i %i", 22, j)
```

or as macros, i.e., without parentheses or commas:


```{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 matching the placeholders in number and type


```{julia}
@printf(stderr, "First result: %i %s\nSecond result %i", 
                                   j, "(estimated)", k)
```

The macro `@sprintf` does not print; it returns the formatted string: 


```{julia}
str = @sprintf("x = %10.6f", π );
```


```{julia}
str
```

### Formatting Floating-Point Numbers

The _precision_ value specifies:

- `%f` and `%e` formats: maximum decimal places
- `%g` format: maximum total digits (integer part + 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)    # 7 and 4 digits total, respectively
```

## File Operations

Files are handled by:

- Opening $\Longrightarrow$ creation of a new _stream_ object (in addition to `stdin`, `stdout`, `stderr`)
- Reading from and writing to this _stream_
- Closing $\Longrightarrow$ detachment of the _stream_ object from the file

```
stream = open(path, mode)
```
   
- path: filename or 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("myfile.txt", "w")
```


```{julia}
@printf(file, "%10i\n", k)
```


```{julia}
println(file, " second line")
```


```{julia}
close(file)
```

Let's look at the file:

```{julia}
;cat myfile.txt
```

...and now we open it again for reading:

```{julia}
stream = open("myfile.txt", "r")
```

`readlines(stream)` returns all lines of a text file as a string vector.

`eachline(stream)` returns an iterator over the file lines.


```{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

Julia packages for various file formats include:

- [PrettyTables.jl](https://ronisbr.github.io/PrettyTables.jl/stable/) Output formatted tables
- [DelimitedFiles.jl](https://docs.julialang.org/en/v1/stdlib/DelimitedFiles/) Read and write matrices
- [CSV.jl](https://csv.juliadata.org/stable/) Read and write CSV files
- [XLSX.jl](https://felipenoris.github.io/XLSX.jl/stable/tutorial/) Read and write Excel files

and many more...

### DelimitedFiles.jl

This package offers convenient functions for saving and reading matrices using `writedlm()` and `readdlm()`.

```{julia}
using DelimitedFiles
```


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 simple:
```{julia}
B = readdlm("data2.txt")
```



In Julia, the `do` notation is frequently utilized for file handling (see @sec-do). The `open()` function includes methods where the first argument is a `function(iostream)`. This function is applied to the stream, which is automatically closed afterward. The `do` notation allows to define this function anonymously:

```{julia}
    writedlm(io, A)
end
```

### CSV and DataFrames

- The CSV format provides tables readable by MS Excel and other applications. 
- Example: the weather and climate database _Meteostat_. 
- The [DataFrames.jl](https://dataframes.juliadata.org/stable/) package handles tabular data conveniently. 


```{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 date/time handling, we load two 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)
```

The resulting 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))
```

