Perličky: práce se seznamy

8. 2. 2008
Doba čtení: 5 minut

Sdílet

Volné pokračování seriálu Perličky z let 2001 a 2002 o programovacím jazyce Perl je určeno především programátorům pokročilým v obecné teorii programování, případně jiném jazyce a zároveň začátečníkům až mírně pokročilým v jazyce Perl. V dnešním díle si ukážeme, jak Perl nahlíží na seznamy, pole a hashe.

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$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.

ict ve školství 24

#!/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.

Seriál: Perličky