Professional Documents
Culture Documents
PRZYKADOWY ROZDZIA
SPIS TRECI
KATALOG KSIEK
KATALOG ONLINE
ZAMW DRUKOWANY KATALOG
Przetwarzanie danych
dla programistw
Autor: Greg Wilson
Tumaczenie: Marek Ptlicki
ISBN: 83-246-0407-3
Tytu oryginau: Data Crunching
Format: B5, stron: 264
TWJ KOSZYK
DODAJ DO KOSZYKA
CENNIK I INFORMACJE
ZAMW INFORMACJE
O NOWOCIACH
ZAMW CENNIK
CZYTELNIA
FRAGMENTY KSIEK ONLINE
Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl
danych
i nadal naley do najbardziej popularnych. Jednym z powodw popularnoci takiego formatu jest fakt, e dane w nim zapisane mona
przetwarza i przeglda za pomoc dowolnego edytora tekstw. Gbszy
powd tej popularnoci ley w tym, e pisanie programw do automatycznej
manipulacji tekstem jest bardzo atwe. W tym rozdziale przyjrzymy si programom tego typu, ich moliwociom i podstawowej strukturze. Zatrzymamy si te chwil przy wierszu polece systemw Unix, rodowisku zorientowanemu tekstowo, ktre nadal trzyma si bardzo dobrze, pomimo
niemal czterdziestoletniej tradycji.
18
Chyba e danych jest zbyt wiele, by mogy zmieci si w pamici. Do tego tematu
wrcimy w dalszej czci rozdziau.
Rozdzia 2. Tekst
19
input.close();
// Przetwarzanie
Collections.reverse(list);
// Zapis
PrintWriter output =
new PrintWriter(new BufferedWriter(new FileWriter(args[1])));
for (Iterator i=list.iterator(); i.hasNext(); ) {
output.println((String)i.next());
}
output.close();
}
catch (IOException e) {
System.err.println(e);
}
}
Nietrudno zauway, e ten kod jest duszy od jego odpowiednika w Pythonie, lecz obydwa realizuj dokadnie t sam funkcj w niemale identyczny sposb.
Oczywicie bezporednie przeoenie kodu z jednego jzyka na drugi nie
zawsze daje najbardziej naturalne lub optymalne rozwizanie. Prawdziwi
programici Javy z pewnoci preferuj umieszczenie odczytu, przetwarzania i zapisu danych w osobnych metodach klasy, zechc te pomin wywoanie metody reverse(), a raczej zechc odczytywa elementy pojedynczo, jednak w odwrconej kolejnoci:
PrintWriter output =
new PrintWriter(new BufferedWriter(new FileWriter(args[1])));
for (ListIterator i=list.listIterator(list.size()); i.hasPrevious(); )
{
output.println((String)i .previous());
}
output.close();
20
1
2
3
4
5
Ammonia
DAVE WOODCOCK
N
1
H
1
H
1
H
1
1
97 10 31
0.257
0.257
0.771
0.771
-0.363
0.000
0.727 0.000
-0.727
0.890
-0.727 -0.890
Rozdzia 2. Tekst
21
Ja pyta...
Czy tak wolno?
Ignorowanie nierozpoznanych danych kojarzy si do niebezpiecznie. Co si stanie, jeli atomy zostan zdefiniowane w wierszach
nierozpoczynajcych si sowem kluczowym ATOM? Co si stanie,
jeli ignorowane rekordy zmodyfikuj znaczenie rekordw ATOM?
Czy nie naley przestudiowa specyfikacji z zakrelaczem w rku,
a program napisa po upewnieniu si co do susznoci decyzji?
W tym przypadku odpowied brzmi nie. Czytanie specyfikacji
formatu PDB zajoby wicej czasu, ni rczne przeksztacenie
formatw. Oczywicie dogbna znajomo specyfikacji moe
przyczyni si do uniknicia bdw, lecz koszt takich bdw
jest bliski zeru. Tego typu decyzja oczywicie byaby nieodpowiedzialna, jeli mielibymy do czynienia z formatem danych kartoteki pacjentw szpitala lub dokumentacji silnikw odrzutowych
samolotw pasaerskich, lecz w tym przypadku jest to podejcie
jak najbardziej praktyczne. Zadanie realizujemy w jak najprostszy
sposb i poprawiamy pniej, jeli wystpi bdy. W tym przypadku jednak kluczowe jest, aby dokadnie zna format wyjciowy,
aby natychmiast zauway wszelkie bdy.
http://www.rcsb.org/pdb/docs/format/pdbguide2.2/guide2.2_frame.html
22
Rozdzia 2. Tekst
23
Bd: wyraenie fields[2] wybiera z listy pojedynczy cig znakw, natomiast fields[4:7] wybiera podlist, a Python nie wie, co w rzeczywistoci
chcemy zrobi, dodajc do siebie cig znakw i list. W kadym jzyku
programowania wystpuj tego typu przypadki specjalne, gdy rne
uyteczne koncepcje nie pasuj do siebie wzajemnie. Jednym z powodw,
24
dla ktrych jzyki Python, Ruby czy Java zdobywaj stopniowo przewag
nad jzykami Perl czy C++ jest to, e w tych pierwszych, dziki jasno
zdefiniowanym reguom, ilo tego typu niejednoznacznoci jest znacznie
zmniejszona. Sytuacj mona naprawi, na przykad opakowujc cig
znakw w list jednoelementow ([]):
def readPdb(inputName):
input = open(inputName, 'r')
result = []
for line in input:
if line[:4] == 'ATOM':
fields = line.split()
atom = [fields[2]] + fields[4:7]
result.append(atom)
input.close()
return result
'0.257',
'0.257',
'0.771',
'0.771',
'-0.363', '0.000']
'0.727', '0.000']
'-0.727', '0.890']
'-0.727', '-0.890']
Ten przykad nie mia na celu pokaza skutku tego banalnego (aczkolwiek
do czsto popenianego) bdu; chodzio mi o to, aby pokaza konieczno
testowania napisanego kodu przed przejciem do kolejnego etapu prac.
Podzia pracy na etapy wczytywania danych, przetwarzania i zapisu w przetwarzaniu danych jest przydatny midzy innymi wanie do tego typu przyrostowego testowania.
Nastpny etap polega na okreleniu nazwy pliku wynikowego. W tym
przypadku regua jest prosta: naley zastpi rozszerzenie .pdb rozszerzeniem .vu3:
def translateName(inputName):
return inputName[ :-4] + '.vu3'
Ju sysz okrzyki purystw Pythona: waciwy sposb wydobycia rozszerzenia z nazwy pliku polega na zastosowaniu funkcji os.path.splitext().
Jednake ten przykadowy kod pisz dla wasnego uytku, jestem pewny,
e uyj go tylko kilka razy w yciu i gdy odkryj, e wystpi problem
z danymi, mog go usun i uruchomi skrypt jeszcze raz, a przede wszystkim nie chc odstrasza mniej dowiadczonych Czytelnikw, oszaamiajc
Rozdzia 2. Tekst
25
ich nadmiern liczb bibliotek, ktrych nigdy wczeniej nie spotkali. Dlatego
zdecydowaem, e w tym konkretnym przypadku zadanie nieco uproszcz.
Moe si wyda, e nareszcie nadszed czas, aby zapisa plik VU3. Jednake jeszcze nie wiadomo, w jaki sposb przeksztaci symbole atomowe
formatu PDB na kod ksztatu, koloru i rednicy, typowe dla formatu VU3.
Najprostszy sposb polega po prostu na zakodowaniu w programie tablicy
przegldowej. Jeli program napotka symbol atomu, ktrego nie znajdzie
w tej tablicy, jego dziaanie zakoczy si bdem, moemy jednak szybko
uzupeni tablic o brakujce wpisy i ponownie uruchomi program. Taka
decyzja jest do przyjcia. Mamy wic:
Lookup = {
'H' : (1, 6, 0.2),
'N' : (1, 17, 0.5)
}
def writeVu3(outputName, atoms):
output = open(outputName, 'w')
for (symbol, X, Y, Z) in atoms:
if symbol not in Lookup:
print >> sys.stderr, 'Nieznany symbol atomu "%s"' % symbol
sys.exit(1)
shape, color, radius = Lookup[symbol]
print >> output, shape, color, radius, X, Y, Z
output.close()
26
Potrzebujemy czterech podej, aby uzupeni wszystkie brakujce pierwiastki (siarka, chlor, elazo i brom). Przy okazji okazuje si, e symbole
atomw mog by zapisywane wielkimi literami, np. CL, jak i za pomoc
kombinacji wielkich i maych liter, np. Cl. Tego typu niespjnoci s na
porzdku dziennym w zadaniach przetwarzania danych, poniewa dla
czowieka nie stanowi problemu, s rozumiane intuicyjnie. Jednak program
musi by odpowiednio zakodowany, aby by w stanie radzi sobie z nimi
w sposb waciwy.
Mamy dwa wyjcia: albo dopisa do tablicy przegldowej odpowiednie
wpisy reprezentujce kad z moliwych pisowni symbolu pierwiastka, albo
znormalizowa symbole przed odczytem z tablicy przegldowej. Pierwszy
sposb doprowadzi nas do tablicy o nastpujcej postaci:
Lookup = {
'BR' (1, 2, 0.9),
'Br' (1, 2, 0.9),
'C' (1, 3, 0.5),
'CL' (1, 8, 0.6),
'Cl' (1, 8, 0.6),
'FE' (1, 13, 1.1),
'Fe' (1, 13, 1.1),
'H' (1, 6, 0.2),
'N' (1, 17, 0.5),
'O' (1, 19, 0.5),
'S' (1, 17, 0.7)
}
Rozdzia 2. Tekst
27
DRY
Jedn z oglnych zasad programowania okrela si akronimem
DRY: ang. Dont Repeat Yourself (nie powtarzaj si) [HT00]. Jeli
informacja zostanie powielona w dwch miejscach lub ich wikszej
liczbie, prdzej czy pniej zapomni si uzupeni jedn z tych
kopii, co doprowadzi do bdu trudnego do wykrycia, poniewa
programista bdzie przekonany, e przecie ju to poprawi. Fakt,
e programy do przetwarzania danych bardzo czsto z zaoenia
su rozwizaniu jednorazowego problemu, nie powinien by
wymwk dla niedbaego programowania. Dowiadczenie uczy
bowiem, e jednorazowy kod czsto przydaje si wielokrotnie,
a ze nawyki nabyte przy okazji rozwiza prowizorycznych pozostaj na stae i ujawniaj si rwnie przy tworzeniu bardziej odpowiedzialnego kodu.
28
Jak pamitamy, kady element listy atoms jest list zoon z podcigw
wczytanej wczeniej definicji atomu w czsteczce (zapisanej w pojedynczym
wierszu pliku wejciowego). Jeli wiersz mia poprawny format, nazwa atomu
znalaza si w elemencie o indeksie zero. Metoda capitalize() obiektu tekstowego zwraca jego kopi z pierwsz liter zamienian na wielk i pozostaymi zamienionymi na mae, na przykad cig aBC zostanie zamieniony na Abc.
Musz co wyzna. Gdy pisaem przedstawiony tutaj kod, nie przyszo mi
nawet do gowy, aby duplikowa wiersze w tabeli przegldowej. Gdy si
przez wiele lat spotyka podobne problemy, normalizacja danych staje si
Rozdzia 2. Tekst
29
nawykiem, podobnie jak logiczny podzia operacji przetwarzania na osobne etapy. Nawyki tego typu zapewne nie w kadej sytuacji prowadz do
optymalnego kodu, lecz dziki nim nie musz kadego przypadku rozpatrywa w zupenym oderwaniu od innych.
30
Na razie nie bdziemy zajmowa si specjalnym znaczeniem wartoci rozpoczynajcych si od znaku $ i skupimy si na odczytywaniu pliku i przeksztacaniu do postaci:
<configure>
<section title= "Bootstrap">
<entry key="Location">$ SYSUSERCONFIG/sversion.ini</entry>
<entry key="BaseInstallation">$ORIGIN/..</entry>
<entry key="buildid">645m44(Build:8784)</entry>
<entry key="InstallMode">STANDALONE&ALL_USERS</entry>
<entry key="ProductPatch"></entry>
</section>
<section title="ErrorReport">
<entry key="ErrorReportPort">80</entry>
<entry key="ErrorReportServer">services.caribou.org</entry>
</section>
</configure>
Odczyt pliku w formacie .ini jest trudniejszy od odczytu pliku PDB, poniewa tam mielimy zagwarantowane, e kady rekord mieci si w jednym
wierszu. W tym przypadku tak nie jest. Najprostsze podejcie do tego
problemu polega na odfiltrowaniu podczas odczytu wszystkich elementw
pliku, ktre nie s poddawane przeksztaceniu, przeksztaceniu tego, co
zostanie, i zapisaniu wyniku w pliku XML. Oczywicie naley rwnie
przeksztaci znaki &, < i > odpowiednio w &, < i >, co jest wymagane przez format XML.
Zaczniemy od zwyczajowego szablonu program przetwarzajcego:
for inputName in sys.argv[1:]:
lines = readIni(inputName)
settings = process(lines)
outputName = translateName(inputName)
writeXml(outputName, settings)
Rozdzia 2. Tekst
31
Jeli taki zapis jest poprawny, nasz algorytm zwrci bdn warto, poniewa odrzuca wszystkie znaki od znaku # do koca wiersza. Co gorsza, ten
bd wystpi w sposb niezauwaony zamiast wywoa wyjtek, kod
odrzuci w peni poprawne dane.
32
Opisany problem mona rozwiza, wyszukujc w wierszach znaki cudzysoww i inne znaczniki specjalne. Zadanie znacznie uprociyby wyraenia
regularne, ktre omwi w rozdziale 3., a na razie przejd do nastpnego
zadania naszego kodu: wygenerowania pliku XML zawierajcego oczyszczone definicje z pliku .ini.
Krtkie przypomnienie: gwnym blokiem w naszym pliku XML bdzie
<configure> i </configure>. Za kadym razem, gdy w pliku napotkamy sekcj
[nazwa], w pliku wynikowym musi znale si zapis <section title="nazwa">.
Oczywicie przed otwarciem nowej sekcji naley zamkn poprzedni:
</section>. Ponadto kady atrybut postaci nazwa=warto musi by odzwierciedlony w wyniku jako <entry key="nazwa">warto</entry>.
Nie ma problemu, kod realizujcy te funkcje jest nastpujcy:
def process(lines):
result = ['<configure>']
for line in lines:
# Pocztek nowej sekcji
if line[0] == '[':
# Zamknicie poprzedniej sekcji
result.append('</section>')
# Pocztek nowej sekcji
title = line[1:-1]
result.append(' <section title="%s">' % title)
# Wpis w biecej sekcji
else:
key, value = line.split('=', 1)
value = escape(value)
result.append(' <entry key="%s">%s</entry>' % (key, value))
# Gotowe
result.append('</configure>')
return result
Czy ten program jest ju poprawny? Nie. Popeniem dwa bdy i proponuj przyjrze si mu jeszcze raz. A jeszcze lepiej: proponuj wykona go
z nastpujcym, testowym plikiem .init:
Rozdzia 2. Tekst
33
# Ustawienia instalacyjne
[Bootstrap]
ProductKey=Caribou CAD 1.1
Location=$SYSUSERCONFIG/sversion.ini
# Obsuga bdw
[ErrorReport]
ErrorReportPort=80
Zamykajcy znacznik </section> powinien by zapisywany wycznie w przypadku, gdy program przetworzy przynajmniej jedn sekcj. Do tego celu
wykorzystamy kolejny znacznik boolowski. Inne rozwizanie mogoby polega na zastpieniu licznikiem wystpie pierwszego z pomocniczych znacznikw boolowskich. Jeli licznik jest rwny zeru i znajdujemy si w ptli,
oznacza to, e nie naley wypisywa zamykajcych znacznikw </section>.
Jeli licznik jest rny od zera i znajdujemy si poza ptl, naley wypisa
ten znacznik. Oto ostateczna posta naszej funkcji:
34
Wystarczyo kilka prostych testw, aby przekona si, e zaimplementowane funkcje realizuj poprawnie swoje zadania, zatem uznaem program
za ukoczony.
Przykadowe bdy w funkcji process() s bardzo reprezentatywne dla tej
klasy problemw, jako e bdy w przetwarzaniu danych pojawiaj si najczciej na pocztku i kocu procesu. Dlatego podczas sprawdzania poprawnoci kodu przetwarzajcego dane, warto zastosowa nastpujce dane
testowe:
t puste dane wejciowe (o ile przetwarzany format na to zezwala);
t pojedynczy rekord;
Rozdzia 2. Tekst
t
t
35
36
Rozdzia 2. Tekst
37
(ang. map, na przykad w Javie), haszy (ang. hash, np. Perl) albo tablic
asocjacyjnych lub skojarzeniowych. Niezalenie od nazwy, sownik dziaa
jak tabela dwukolumnowa, gdzie w lewej kolumnie wpisywane s klucze,
a w prawej skojarzone z nimi wartoci. Doskonaym przykadem sownika
moe by tabela cen, w ktrej kodom produktw (kluczom) przyporzdkowane s ich ceny (wartoci):
ANW-400 179.95
ANW-407 179.95
ANW-460 209.95
Rzeczywisty czas wyszukiwania jest uzaleniony od tego, jak bardzo klucze rni si
od siebie, co z kolei ma wpyw na potencjalne wystpienie kolizji. Szczegy mona
znale w [Sed97].
38
Rozdzia 2. Tekst
39
40
Rozdzia 2. Tekst
41
Ta funkcja ponownie musi wzi pod uwag przypadki specjalne na pocztku i kocu przetwarzania. Mimo zmian, jej dziaanie nadal jest bardzo
intuicyjne. Zwracany wynik jest list par. Pierwszym elementem kadej pary
jest tytu sekcji, drugim natomiast sownik zawierajcy ostateczn warto
kadego atrybutu w tej sekcji.
Zmienilimy format danych zwracanych z funkcji process(), musimy zatem
zmieni te wykorzystujc je funkcj writeXml():
def writeXml(outputName, settings):
output = open(outputName, 'w')
print >> output, '<configure>'
for (section, content) in settings:
print >> output, '<%s>' % section
for key in content:
value = content[key]
print >> output, ' <%s>%s</%s>' % (key, value, key)
print >> output, '</%s>' % section
print >> output, '</configure>'
output.close()
42
Jeszcze chwila Przyjrzyjmy si sekcji Section3. W pliku .ini klucz Red wystpi przed kluczem Green. W pliku .xml pojawi si pniej. Czy to bd?
Nie, to dziaanie zamierzone. Jak wspominaem wczeniej, klucze w sownikach nie maj uporzdkowanej kolejnoci. Ta cecha sownikw spowodowaa, e Green pojawi si przed Red, nawet pomimo tego, e w danych
wejciowych wystpowa wczeniej.
Czy to jest problem? To zaley wycznie od tego, czy znaczenie ma kolejno kluczy w sekcji, co z kolei zaley od programu CAD wykorzystujcego t konfiguracj. Jeli nie ma to znaczenia dla programu, nie warto si
tym przejmowa. Jeli jednak ma to znaczenie, mona zmodyfikowa kod,
na przykad zachowujc kolejno kluczy atrybutw w dodatkowych sownikach, dla kadej sekcji z osobna.
Rozdzia 2. Tekst
43
%general.ini%
[View]
WindowSize=1024,768
BackgroundColor=green7
WindowSize=1280,1024
Niestety, nowy format XML nie uwzgldnia tej moliwoci: kady plik
musia by samodzieln caoci.
W pierwszej kolejnoci naley oszacowa skal problemu. W firmie byo
zatrudnionych okoo osiemdziesiciu inynierw, z czego tylko pitnastu
wykorzystywao konfiguracje wczajce pliki zewntrzne. aden z przypadkw nie by wielokrotnie zagniedony (to znaczy drugi plik nie zawiera
wczenia kolejnego pliku). Pliki konfiguracyjne musz by skonwertowane
tylko raz, zdecydowaem wic, e po prostu rcznie pocz te pitnacie
plikw .ini w caoci i uruchomi procedur przeksztacajc.
To zadanie zajo okoo dwudziestu minut, z ktrych wikszo spdziem
na poszukiwaniu administratora sieci w celu udostpnienia mi tych plikw
do odczytu. Gdyby plikw do przetwarzania byo okoo setki lub gdyby
okazao si, e wykorzystuj wielokrotne, rekurencyjne wczanie, z pewnoci zdecydowabym si zmodyfikowa swj program ini2xml.py.
Jak zawsze na pocztek zdecydowabym, w ktrym miejscu skryptu musz
wprowadzi poprawki. Jednym ze sposobw moe by napisanie kolejnej
funkcji filtrujcej, zastpujcej zawartoci pliku wszystkie wystpienia instrukcji wczajcej plik zewntrzny. Ta metoda zadziaa jedynie w przypadku, gdy nie mamy do czynienia z rekurencyjnym wczaniem, jeli jednak
byoby inaczej, naley zastosowa odpowiedni ptl, na przykad:
lines = readIni(filename)
while containsIncludes(lines):
index = findFirstInclude(lines)
expansion = readIni(index)
lines = insert(lines, index, expansion)
44
To nie wyglda najgorzej, lecz wszyscy Czytelnicy, ktrzy mieli okazj przetwarza pliki tekstowe wczajce inne pliki, z pewnoci umiechn si
z powtpiewaniem. Naley bowiem pamita o moliwoci wystpienia
bdnej sytuacji samowczenia pliku, na przykad (plik nosi nazw selfinclusion.ini):
[Foo]
a = b
%selfinclusion.ini%
Rozdzia 2. Tekst
Rysunek 2.1.
45
readIni().
46
Wywoanie funkcji
nastpujce:
readIni()
Czas na przetestowanie nowej wersji. Czy skrypt dziaa poprawnie w przypadku plikw .ini nie wczajcych innych plikw? Czy dziaa poprawnie
w przypadku wczenia pliku w samym sobie? Praktycznie natychmiast
zwraca komunikat o bdzie i koczy dziaanie7. Co si stanie w przypadku,
plik A wcza plik B, ktry wcza plik C, ktry wcza plik A? Przygotowanie odpowiedniego zestawu testowego zajmuje dosownie chwil,
warto wic sprawdzi taki przypadek. Wszystko w porzdku, rwnie w tym
przypadku program zachowuje si zgodnie z zaoeniami.
Wnioski
To wiczenie z przetwarzania danych tekstowych pozwala zdoby sporo
uniwersalnych dowiadcze zwizanych z przetwarzaniem danych. Po pierwsze: nie mona zbyt wczenie uzna, e zadanie jest skoczone. Pierwsze
rozwizanie problemu zwizanego z przetwarzaniem danych z reguy nie
uwzgldni kilku wanych szczegw, kluczowe jest zatem, aby kod pisa
w sposb czytelny, nawet w przypadku gdy kod z zaoenia ma by jednorazowego uytku. Warto te przechowa taki kod w systemie kontroli wersji,
aby mc odszuka go za jaki czas, gdy mimo wszystko okae si potrzebny.
7
Rozdzia 2. Tekst
47
48
t
t
t
I to ju wszystko, to s reguy, ktre musz by przestrzegane przez program przeznaczony do pracy w powoce. Dziki nim bez problemu bdzie
mg wsppracowa z dziesitkami innych programw napisanych zgodnie
z t sam filozofi. Przestrzeganie tych regu umoliwia bowiem przekierowania (ang. redirection) wyniku dziaania programu oraz czenie kilku
programw w potoki (ang. pipe). Przekierowanie suy przekazaniu danych
z pliku na wejcie programu, zamiast na przykad wpisywania ich z klawiatury. Druga funkcja przekierowania suy zapisywaniu danych z wyjcia
programu. Pierwsza z tych funkcji realizuje si za pomoc operatora <,
na przykad:
myprog < somefile.txt
# przekazanie zawartoci pliku file.txt na
wejcie programu myprog
Rozdzia 2. Tekst
49
wc
ls *.py | wc
10
147
50
Rozdzia 2. Tekst
Tabela 2.1.
Przydatne polecenia
cat
cd
chmod
cut
cp
date
diff
du
echo
wypisanie argumentw
env
find
grep
head
lpr
ls
man
dokumentacja polece
mkdir
tworzenie katalogw
mv
od
ps
pwd
rm
usuwanie plikw
rmdir
usuwanie katalogw
sort
sortowanie wierszy
tail
tar
archiwizowanie plikw
uniq
wc
zip
51
52
Wiersz polece stanowi bardzo proste narzdzie do wykonywania prostych zada i jest do efektywny, jeli wemie si pod uwag czas i nakad
pracy; naley jednak pamita o jego ograniczeniach. Najwaniejszym z nich
(z punktu widzenia przetwarzania danych) jest brak obsugi danych strukturalnych10. W klasycznych systemach Unix wszelkie dane przetwarzane
z poziomu powoki stanowi listy cigw znakw. Jeli kto potrzebuje
bardziej zaawansowanej struktury danych, na przykad drzewiastej, zapewne
atwiej bdzie sign po Pythona, Rubyego czy podobny jzyk programowania. Alternatywnym podejciem moe by zainwestowanie w komercyjne
narzdzia (na przykad TextPipe Pro firmy Crystal Software: http://www.
crystalsoftware.com.au), ktre pozwalaj wykorzysta wiele nietekstowych
struktur danych rwnie z poziomu jzykw powoki.
Jak zbudowa poprawnie dziaajce narzdzie
Jak wspominaem wczeniej, poprawnie dziaajce narzdzie wiersza polece
powinno czyta dane ze standardowego wejcia, a wyniki swoich dziaa
zapisywa na standardowym wyjciu, dziki czemu programici bd mieli
moliwo czenia tego narzdzia z innymi za pomoc potokw. Gdy mamy
pewno, e narzdzie do przetwarzania danych nie bdzie wykorzystane
wicej, ni raz, nie warto upiera si przy zachowaniu tego wymogu. Jeli
jednak istnieje prawdopodobiestwo, e ten program bdzie wykorzystywany
wielokrotnie lub w przypadku gdy tworzony program ma by elementem
wikszego systemu do przetwarzania danych, warto go stworzy w zgodzie
z pewn konwencj (ktra nie zawsze jest speniona w peni przez narzdzia uniksowe):
t jednoliterowe parametry wiersza polece powinny rozpoczyna si
od jednego mylnika, na przykad -p;
t wieloliterowe parametry wiersza polece powinny rozpoczyna si
od dwch mylnikw, na przykad --print;
10
By moe wolna od tego ograniczenia bdzie powoka nowej generacji firmy Microsoft,
o nazwie kodowej Monad.
Rozdzia 2. Tekst
t
53
jeli nie zostanie podana nazwa pliku, program powinien czyta dane
wejciowe ze standardowego wejcia i zapisywa wyniki swoich dziaa
na standardowym wyjciu;
jeli zostanie podana jedna nazwa pliku, program powinien
odczyta z niego swoje dane wejciowe, a wyniki dziaa zapisa
na standardowym wyjciu;
jeli zostan podane dwie nazwy plikw, program powinien
odczyta swoje dane wejciowe z pierwszego z nich, a wyniki dziaa
zapisa w drugim;
alternatywna zasada jest taka, e program odczytuje swoje dane
wejciowe ze wszystkich plikw okrelonych w wywoaniu po kolei,
a wyniki dziaa zapisuje na standardowym wyjciu;
komunikaty o bdach powinny by wypisywane na standardowym
strumieniu bdw (co spowoduje, e zostan wypisane na ekranie
nawet w przypadku, gdy wynik bdzie przekierowany do pliku);
w przypadku poprawnego wykonania program powinien zwrci
status rwny zeru, poniewa powoka interpretuje kod wyjcia rny
od zera jako sygnalizacj bdu wykonania.
54
Moe si pojawi konieczno przetwarzania wikszej liczby plikw wejciowych, zmienna input jest ustawiona pocztkowo na pust list. Jeli
po przetworzeniu parametrw ta zmienna jest nadal pust list, dane bd
odczytane ze standardowego wejcia. Zmienne output i sampling s ustawione na None, wic istnieje moliwo zweryfikowania, czy kto nie usiowa
ustawi ich wartoci wielokrotnie (gdyby na samym pocztku ustawi ich
wartoci domylne, trudno byoby stwierdzi, czy parametr zosta ustawiony
przez uytkownika). Program zawiera sporo kontroli bdw. Warto zadba
o tego typu szczegy, jeli program ma by uywany wielokrotnie, warto
uatwi uytkownikom jego uytkowanie.
Funkcje przetwarzajce parametry wywoania maj bardzo prost konstrukcj:
# Wypisanie komunikatu o bdzie i zakoczenie pracy z kodem bdu
def fail(msg):
print >> sys.stderr, msg
sys.exit(1)
Rozdzia 2. Tekst
55
Ja pyta...
Czy zawsze musz pisa tak wielk ilo kodu?
Nie. Wikszo jzykw programowania zawiera biblioteki i moduy
przetwarzajce listy parametrw wywoania, najczciej biblioteki
te nosz nazw getopt lub podobn. W swoim kodzie zaprezentowaem samodzielny sposb przetwarzania listy parametrw
wywoania wycznie dla celw demonstracji i na potrzeby programowania w jzyku, w ktrym nie mamy dostpnej biblioteki
realizujcej t funkcj.
Warto zwrci uwag na funkcj process(), w ktrej znajduje si sprawdzenie, czy nazw pliku nie jest mylnik. W takim przypadku dane nie s odczytywane z pliku, tylko ze standardowego wejcia. Dziki temu uytkownik
56
W ten sposb zostan kolejno przetworzone wszystkie wiersze plikw wejciowych, modu wie nawet, e znak oznacza standardowe wejcie.
W Perlu jest jeszcze prociej:
while (<>) {
doSomething($_);
}
Rozdzia 2. Tekst
57
Jeli mamy szczcie, dane moemy podzieli na czci w ramach dwuetapowego procesu. W pierwszym dane s filtrowane w taki sposb, e to
co pozostao mieci si ju w pamici, w drugim etapie wykonujemy nasze
przetwarzanie z tymi odfiltrowanymi danymi. Zamy na przykad, e
firma realizuje miliard transakcji rocznie i chcemy znale milion najwikszych z nich. Zamy, e w pamici zmieci si najwyej sto milionw
rekordw. Problem moemy rozwiza w nastpujcy sposb:
for (kady blok zoony ze 100 milionw rekordw)
wczytaj do pamici
posortuj
zapisz w pliku tymczasowym milion najwyszych wartoci
wczytaj plik tymczasowy (o rozmiarze 10 milionw rekordw)
posortuj
wypisz milion najwyszych wartoci
2.8. Podsumowanie
Ten rozdzia zawiera wprowadzenie kilku podstawowych koncepcji i technik. Niektre z nich s specyficzne dla przetwarzania strumieni tekstu, inne
maj oglne zastosowanie w technikach przetwarzania danych. Podsumujmy
najwaniejsze z nich:
t Naley rozpocz od uoglnienia i dodawa do rozwizania
kolejne szczegy dopiero wwczas, gdy napotkamy przypadki,
ktrych uoglnienie nie obsuguje. Naley unika rozwiza
przekombinowanych, implementujcych rozwizania nieistniejcych
problemw. W strategii Extreme Programming tego typu rozwizania
58
Struktury danych
Przetwarzanie danych opiera si w wikszoci przypadkw na
listach i katalogach, lecz czasem rozwizanie problemu wymaga
zastosowania technik opisanych w bardziej zaawansowanych algorytmach informatycznych. Na przykad problem wyszukania miliona
najwyszych wartoci z listy miliarda mona rozwiza za pomoc kolejki priorytetowej, to znaczy listy, w ktrej q[k] ma zawsze
wiksz warto od q[2*k+1] do q[2*k+2]. Za kadym razem, gdy
do tej listy jest dodawany element, kolejka porzdkuje si automatycznie, zachowujc t zasad. Jeli ograniczymy jej rozmiar do
miliona rekordw, mona przetworzy miliard rekordw po kolei
(pojedynczo), a w efekcie otrzymamy milion najwikszych wartoci.
Standardowa biblioteka Pythona zawiera obsug kolejki priorytetowej, suy do tego modu heapq. Podobne moduy s dostpne
dla wikszoci jzykw programowania. Jeli Czytelnik wykorzystuje
jzyk, do ktrego nie ma odpowiedniej biblioteki, mona samemu zaimplementowa odpowiedni struktur zgodnie z opisem
w [Sed97] lub wielu innych podrcznikach omawiajcych algorytmy i struktury danych.
t
t