Seznamy, řezy a pole
Perlovský seznam je vektor obsahující nula nebo více skalárních veličin. Seznam lze zapsat pomocí klasické závorkové konstrukce, často se však používá operátor qw() nebo qw// (což ušetří zmáčknutí SHIFTu). Operátor qw zachází se svým vnitřkem, jako by byl v jednoduchých uvozovkách a vytvoří seznam rozsekáním podle prázdných znaků. Dalším častým operátorem jsou dvě tečky .., které umí „počítat po jednom“.
$a = 5;
(1, $a, "f") # (1, 5, "f")
(1, ($a, "f")) # totéž
qw/raz dva tři/ # ('raz', 'dva', 'tři')
2 .. 5 # (2, 3, 4, 5)
"A" .. "C" # ("A", "B", "C")
() # prázdný seznam, velice užitečný
K prvkům seznamu lze přistupovat pomocí indexů. Indexem může být skalár nebo opět seznam. Skalární indexy začínají na hodnotě nastavené $[, což je obvykle 0. Indexy menší než toto číslo značí indexování seznamu od jeho konce. V případě indexování seznamu seznamem vytvoříme takzvaný řez, což je seznam obsahující prvky původního seznamu na zadaných indexech. Indexy řezu začínají opět od hodnoty $[.
(1, $a, "f")[0] # 1
(1, $a, "f")[-1] # "f"
qw/ahoj děti/[1] # 'děti'
(0 .. 9)[3 .. 5] # ('3', '4', '5')
((0 .. 9)[3 .. 5])[-2] # 4
qw/nastoupil vystoupil/[1, 0] # ('vystoupil', 'nastoupil')
Perlovské pole (zapsáno @a) pomocí symbolu @ pouze říká, abychom na symbol a nahlíželi jako na identifikátor seznamu, podobně jako bychom to česky vyjádřili pomocí přípony v plurálu („áčka“). Je důležité si uvědomit, že tímto získáváme seznam se všemi funkcemi všude tam, kde se objevuje zápis @a. Je to poněkud odlišný způsob nahlížení na pole, než jaký se používá v jiných moderních programovacích jazycích.
@a = qw/skákal pes přes oves/; @b = qw/přes zelenou louku/; @c = (2, -1 .. 1); (@a, @b)[@c] # přes louku skákal pes
Seznamy a hashe
Perlovský hash se má se seznamy celkem rád. Lze si opět představit, že zápis %a znamená „áčka, na která se nahlíží jako na páry (klíč, hodnota)“. Hashe se inicializují seznamem, operátor => převážně slouží k lepší čitelnosti kódu. Hashe lze indexovat vyhledáním klíče pomocí složených závorek. Česky bychom řekli „hodnota klíče v seznamu“( $a{'klíč'}). Jako index lze také použít seznam a tak vytvářet řezy. V tom případě však musíme prefixovat symbolem @, neboť řez je seznam. Dále lze hashe umístit do seznamu nebo přiřadit do pole a takto získat zpět seznam párů (klíč, hodnota). Pořadí párů však bude náhodné.
$ENV{'PATH'} # '/bin:…'
@ENV{qw/HOME USER/} # všimněte si symbolu @, více indexy vytváříme seznam
%h = @a; # ('skákal' => 'pes', 'přes' => 'oves')
$h{'přes'} # 'oves'
%k = (%h, @b); # index 'přes' se přepíše na 'přes' => 'zelenou'
# a přidá se 'louku' => undef
@u = 'A' .. 'Z';
%v = @u; # 'A' => 'B', 'C' => 'D', …
@w = %v; # pořadí prvků je jiné než v @u, asociace párů však zůstane
Struktury a pojmenované parametry
Hashe se používají nejen čistě pro účely hashování, ale často také tam, kde by stála konstrukce struct v C.
%zaznam = ( 'jméno' => 'František Koudelka', 'oddíl' => 'STS Chvojkovice-Brod' );
(Jak vytvořit pole takových záznamů si ukážeme v příštím díle.)
Příbuznost hashů a seznamů lze využít také jako náhražku pojmenovaných parametrů funkcí.
sub spojeni {
my %parametry = @_;
print "Spojuji se na '$parametry{'kam'}' jako '$parametry{'kdo'}'\n";
}
# pak lze použít
spojeni(kdo => 'pes', kam => 'okno');
spojeni(kam => 'díra', kdo => 'kočka');
Spolu s tím si lze dopřát i luxus parametrů s výchozími hodnotami. Lze to udělat klasicky pomocí operátoru or, nebo využít fakt, že nová dvojice (klíč, hodnota) v seznamu přepíše starou.
sub spojeni {
my %parametry = @_;
$parametry{'kdo'} or $parametry{'kdo'} = $ENV{'USER'};
print "Spojuji se na '$parametry{'kam'}' jako '$parametry{'kdo'}'\n";
}
# nebo
sub spojeni {
my %parametry = (kdo => $ENV{'USER'}, @_);
print "Spojuji se na '$parametry{'kam'}' jako '$parametry{'kdo'}'\n";
}
# pak lze zavolat
spojeni(kam => 'domu');
Seznamové operátory
K často používaným seznamovým operátorům patří přiřazení a print. V případě přiřazení lze do seznamu na levé straně napsat skalární proměnné, jejichž obsah se tímto přiřadí. Operátor print použije k oddělení prvků seznamu hodnotu $,.
($home, $user) = $ENV{qw/HOME USER/};
print "Babka má ", secti_jabka(), " jablek.";
# obsah všech seznamů v dnešních příkladech lze rychle vypsat takto
$, = " "; print @pole; # nebo (seznam)
# nebo beze změny $,
{ local $, = " "; print @pole; }
K řazení prvků se používá operátor sort. Řadit lze dle návratové hodnoty libovolné funkce (která je záporná, kladná, nebo nula), nicméně zajímavější je varianta s blokem kódu. V bloku jsou definované identifikátory $a a $b jako reference na dvě porovnávané hodnoty.
sort { $a cmp $b } qw/pes prase ping/; # seřadí jako řetězce
sort (1..100); # také jako řetězce
sort { $a - $b } (1..100); # jako čísla
sort { $a <=> $b } (1..100); # také jako čísla
sort { abs($a) <=> abs($b) } (-100..100); # jako čísla, bez znaménka
Chceme-li řadit podle nějaké složitější funkce, je vhodné si její hodnoty dopředu spočítat a řadit podle těchto hodnot.
$hodnoty{$_} = funkce($_) for @pole;
@serazeno = sort { $hodnoty{$a} <=> $hodnoty{$b} } @pole;
%hodnoty = undef;
(Toto lze provést poněkud elegantněji pomocí referencí a operátoru map. O referencích ale příště.)
Zmiňovaný operátor map umožňuje na seznamu provést libovolnou transformaci. Ve vykonávaném bloku přestavuje $_ odkaz na aktuálně zpracovávaný prvek. Na rozdíl od cyklu for však můžeme prvky seznamu mazat nebo přidávat, jestliže posledním výrazem v bloku nevyhodnotíme skalár, ale prázdný seznam, respektive seznam s více hodnotami.
@mocniny = map { 2**$_ } (0 .. 8);
# je zhruba ekvivalentní
for (0 .. 8) {
$mocniny[$_] = 2**$_;
}
@mala_pismenka = map { lc($_) } @slova;
# výše uvedený cyklus
# $hodnoty{$_} = funkce($_) for @pole
# lze zapsat také takto
%hodnoty = map { $_ => funkce($_) } @pole;
# mazat prvky lze pomocí prázdného seznamu
@vetsi_nez_10 = map { $_ > 10 ? $_ : () } (1 .. 100);
Operátor grep je obdoba posledního příkladu na map. Pokud vyhodnocovací blok vrátí pravdivou hodnotu, prvek je v seznamu ponechán, jinak je vymazán. Pokud je grep vyhodnocen jako skalár, vrátí počet prvků, které testem prošly.
@skryte_soubory = grep { $_ =~ /^\./ } @soubory;
$autorizovan = grep { $jmeno eq $_ } @autorizovani_uzivatele;
Závěr
V dnešním díle jsme si ukázali některé mocnější techniky pro práci se seznamy, poli a hashi. Naše poznatky můžeme shrnout do následujícího prográmku.
#!/usr/bin/perl -w
use strict;
my ($max, $r) = (shift, 1);
die "Použití: $0 <kladné číslo>\n" unless defined $max and $max > 0;
$, = " ";
my @faktorial = ( 1, map {
$r *= $_;
$r;
} (1 .. $max) );
print @faktorial, "\n";
my @trojuhelnik = map {
my $n = $_;
map { $faktorial[$n] / $faktorial[$_] / $faktorial[$n - $_] } (1 .. $n);
} (1 .. $max);
print @trojuhelnik, "\n";
Generace pole faktoriálů spočívá ve vynásobení předchozí vypočtené hodnoty aktuálním prvkem ze sekvence 1 až $max. Jako výjimka je první prvek (index 0) inicializován napevno jedničkou. Pascalův trojuhelník pak vyrobíme po řádcích (vnější map), přičemž N-tý řádek obsahuje právě N prvků, které se vypočítají jako kombinační čísla (vnitřní map).
V příštím díle se budeme zabývat převážně referencemi, způsobem, jak vybudovat složitější datové struktury a jejich použitím spolu s již probranými operátory.