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ówZastą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)