Operacje wejścia i wyjścia

Operacje IO to wszelkie działania mające na celu komunikację programu z zewnętrznym światem - człowiekiem, danymi, siecią, innymi procesami itp. W tym przypadku szczególny nacisk położmy na wczytywanie i zapisywanie danych, co jest, rzecz jasna, jedną z pierwszych i podstawowych czynności w ich analizie.

Strumienie systemowe (stdin, stdout, stderr)

Polecenie readline() domyślnie wczytuje jedną linię tekstu ze strumienia stdin. Zamiast niego może być podany inny strumień (każdy strumień IO zawsze obsługuje conajmniej metodę read i write), np. plik, strumień sieciowy. Zawsze dostępne systemowe strumienie to stdin, stdout (domyślny argument funkcji print) oraz stderr.

x = parse(Float64, readline(stdin))
if x < 0.0
    println(stderr, "Podaj x >= 0")
    exit(1)
end
println("√$x = ", sqrt(x))

Zadanie Napisz program (skrypt) wczytujący 2 punkty na płaszczyźnie podane przez użytkownika (x1, y1, x2, y2), który następnie podaje równanie prostej przechodzącej przez punkty, oraz przedstawia wykres z punktami i linią. W przypadku gdy użytkownik poda błędne punkty (x1 = x2 lub dane, które nie są liczbami) program powinień wypisać odpowiednią informację w strumieniu stderr.

Następnie stwórz plik z czterami liczbami "dane.txt" i przekaż go do programu przez wywołanie

$> cat dane.txt | julia skrypt.jl 1> wynik.txt 2> err.txt

Wyniki powinny zostać zapisane w pliku "wynik.txt" (1> to strumień stdout), natomiast ewentualne błędy, wypisywane do strumienia stderr (2>) zostaną zapisane do pliku "err.txt".

Praca z plikami

Otwarcie pliku poleceniem open zwraca strumień IOStream z którym możemy pracować podobnie jak ze strumieniami standardowymi.

f = open("test.txt", "w")

Tryby otwarcia to:

  • r - (odczyt - domyślny),

  • w - zapis, skasowanie istniejącej zawartości

  • a - zapis, dodawanie do istniejącej zawartości

  • r+ - odczyt i zapis (plik musi istnieć)

  • w+ - odczyt i zapis, skasowanie zawartości

  • a+ - odczyt i zapis, dodawanie do zawartości

Do czytania służą funkcje read, read!, readline, readlines.

Do pisania write, print, println.

Funkcja close zamyka strumień.

Funkcja eof zwraca prawdę jeżeli strumień osiągnął koniec pliku (end-of-file).

write(f, "1.0\n2.0\n3.0\n")
println(f, "4.0")
close(f)

Sprawdzimy teraz zawartość pliku (open domyślnie jest w trybie r)

f = open("test.txt")
readline(f)
readline(f)
readline(f)
readline(f)
readline(f)
eof(f)
close(f)

Składnia do ... end zapewnia bezpieczne zamknięcie pliku automatycznie wywołując close. W poniższym przykładzie zawartość jest wczytana do wektora ciągów znaków.

lines = open("test.txt") do f
    readlines(f)
end

To samo można osiągnąć w poniższej pętli, z różnicą taką, że dane są wczytywane linia po linii

f = open("test.txt")
for line in readlines(f)
    println(line)
end
close(f)

O ile funkcje readline/readlines działają tekstowo, to read zwraca binarną tablicę bajtów. Oczywiście można ją ewentualnie przerobić na ciąg znaków

f = open("test.txt")
data = read(f)
println(data)
close(f)
String(data)

Lub od razu wczytać do odpowiedniego typu

f = open("test.txt")
data = read(f, String)
println(data)
close(f)

Możemy przeczytać też zadaną liczbę (maksymalną, bo może być ich mniej jeżeli osiągniemy koniec pliku) bajtów

f = open("test.txt")
data = read(f, 2)
close(f)
println(data)

Oraz wczytać dane do zadanej przez nas dowolnej zmiennej (wersja read!). Informacja zapisana w pliku w postaci binarej jest wtedy wczytywana w tylu bitach ile zawiera zmienna i reinterpretowana. W przykładzie poniżej oczywiście taka reinterpretacja bitów nie ma wiele sensu, bo zapisane dane były w postaci tekstowej i miały zupełnie inne znaczenie.

f = open("test.txt")
A = zeros(Int64, 2)
println(A)
read!(f, A)
println(A)
close(f)

Ale możemy stworzyć odpowiednie dane binarne i sprawdzić działanie tej metody. Stworzymy macierz losowych liczb Float64 3x2 i wczytamy ją ponownie do takiej samej macierzy wcześniej wypełnionej zerami.

f = open("test.bin", "w")
A = [rand(3);; randn(3)]
println(A, " ", size(A))
write(f, A)
close(f)
f = open("test.bin", "r")
A = zeros(3, 2)
read!(f, A)
println(A)
close(f)

Do poruszania się po strumieniu służy polecenie skip któremu podajemy strumień i liczbę bajtów o jaką chcemy się poruszyć (można poruszać się do przodu i tyłu). Ponieważ wpisaliśmy dane 64-bitowe, każda liczba zajmuje 8 bajtów. Idąc 8 bajtów do przodu przeskoczymy jedną liczbę, idąc 16 bajtów do tyłu cofniemy się o dwie. Aby sprawdzić czy liczby są poprawnie wczytywane trzeba pamiętać, że dane są zapisywane kolumnami (a nie rzędami).

f = open("test.bin", "r")
println(read(f, Float64))
println(read(f, Float64), "\n")
close(f)

f = open("test.bin", "r")
println(read(f, Float64))
skip(f, 8)
println(read(f, Float64))
skip(f, -16)
println(read(f, Float64))

close(f)

Pliki tekstowe

Wiele danych jest zapisywanych w postaci plików tekstowych, które pomimo iż zajmują zdecydowanie więcej miejsca (i są niepraktyczne dla dużych danych), to mają przewagę nad plikami binarnymi w postaci czytelności dla człowieka bez dodatkowych informacji o strukturze binarej i konieczności dekodowania. Standardowym formatem danych teksotwych jest CSV (coma separated values), którym zajmiemy się później, bo czytaniem w tym formacie zajmują się specjalne biblioteki. Niestety czasem dane nie stosują się do specyfikacji formatu, zawierają różne błędy, specjalne kodowania znaków itp. W takich przypadkach trzeba je wczytywać ręcznie, linijka po linijce.

W przypadku pracy z plikami tekstowymi przydają się funkcje operujące na ciągach:

  • usuwanie zadanych znaków z przodu i z tyłu: strip, domyślnie działa na "białe znaki" tj. takie które są identyfikowane funkcją isspace

  • Podział ciągu na podciągi: split w miejscu podanych znaków lub domyślnie białych znaków

  • Zastąpienie wszystkich wystąpień jakiegoś podciągu replace

Załóżmy, że mamy plik z danymi liczbowymi, w których zastosowano przecinek do części dziesiętnych, do oddzielenia wartości średnik, całość objęto nawiasami, na początku znajduje się tabulator, a na końcu znak końca linii.

s = "\t(0,01;1,2;3,5)\n"
println(s)

Polecenie strip usunie "białe znaki" czyli w naszym przypadku tabulator i koniec linii

strip(s)

Aby pozbyć się nawiasów musimy podać jakie znaki chcemy usunąć z końców

strip(s, ['\t', '\n', ')', '('])

Spróbujmy podzielić teraz ciąg na podciągi

split(s)

Ponieważ split domyślnie robi to w miejscu spacji, musimy podać średnik jako miejsce podziału ręcznie

split(s, ";")

Jak widać najpierw będziemy musieli pozbyć się elementów z końca i początku, a potem podzielić wektor

split(strip(s, ['\t', '\n', ')', '(']), ";")

Zamianę przecinków na kropki zrobimy za pomocą funkcji replace i możemy to zrobić na samym początku

replace(s, "," => ".")
ss = split(strip(replace(s, "," => "."), ['\t', '\n', ')', '(']), ";")

Ostatnim elementem jest zamiana napisów na liczby

map(x -> parse(Float64, x), ss)

Lub, łącząc wszystko razem

map(x -> parse(Float64, x), split(strip(replace(s, "," => "."), ['\t', '\n', ')', '(']), ";"))

Takie operacje można oczywiście wykonywać w pętli dla każdej linii tekstu

data = Array{Float64, 1}()
for line in readlines(f)
    line = split(line)
    # ...
    x = parse(Float64, line[1])
    push!(data, x)
    #...
end

Zadanie Ze strony NBP pobrać archiwum kursów walut za rok 2021 w formacie CSV NBP

Plik jest w kodowaniu iso-8859-1, w systemach linux można sprawdzić kodowanie, a następnie dokonać konwersji do kodowania utf-8 w poniższy sposób

$ file -bi archiwum_tab_a_2021.csv
$ iconv -f iso-8859-1 -o utf-8 archiwum_tab_a_2021.csv archiwum.csv

Zadanie polega na tym, aby wczytać plik w ten sposób, aby otrzymać słownik o strukturze:

  • "data" => wektor dat lub liczby dni od pierwszego dnia roku

  • "waluta" => wektor kursów danej waluty

np.

  • "data" => [Date("2021-01-04"), Date("2021-01-05"), Date("2021-01-07"), ...],

  • "USD" => [3.6998, 3.7031, 3.6656, ...],

  • "EUR" => [4.5485, 4.5446, 4.4973, ...],

  • ...

Po wczytaniu danych proszę narysować wykres stosunków kilku wybranych walut do dolara (np. EUR/USD, GBP/USD, CHF/USD)

CC BY-SA 4.0 Krzysztof Miernik. Last modified: December 04, 2023. Website built with Franklin.jl and the Julia programming language.