Loendi hõlmamine

Loendi hõlmamine on mõnes programmeerimiskeeles kasutatav süntaktiline konstruktsioon loendi põhjal uue loendi moodustamiseks.

Loendi hõlmamise süntaks põhineb matemaatilises hulgateoorias kasutataval notatsioonil, mis määrab ära hulga, millest liikmeid uude hulka saab valida, ja tingimuse(d), mida uue hulga liikmed peavad rahuldama. Loendi hõlmamise puhul on suurim erinevus võrreldes hulkadega see, et loendi liikmed ei pea olema unikaalsed.

Ajalugu
Loendi hõlmamist saab kasutada funktsionaalsest programmeerimisest tuntud map- ja filter-funktsioonide asemel. Samas on ka loendi hõlmamise süntaks ise tuntuks saanud just funktsionaalsete programmeerimiskeelte vahendusel. Esimest korda kasutati sellist konstruktsiooni programmeerimiskeeles SETL, mis põhines hulgateoorial ning loodi aastal 1969. SETL omakorda mõjutas programmeerimiskeelt ABC, mida programmeerimiskeele Python looja Guido van Rossum on nimetanud Pythoni otseseks eelkäijaks. Selles artiklis toodud näited põhinevadki peamiselt Pythoni süntaksil.

Terminit comprehension, mille eestikeelseks vasteks on pakutud "hõlmamine", kasutati esimest korda programmeerimiskeele NPL loojate Rod Burstall ja John Darlington poolt aastal 1977.

Näited
Loendi hõlmamine on töövõte, mis võimaldab programmeerijal esitada sama mõte kokkusurutumal, kuid samas loendi hõlmamise konstruktsiooniga harjunud inimesele kergemini loetavamal kujul. Vaatleme näidet programmeerimiskeelest Python, kuid ilma loendi hõlmamise süntaksit kasutamata:

>>> uus_loend = []
>>> for n in range(11):
>>>     uus_loend.append(n * 2)
>>> uus_loend
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Esimesel real luuakse tühi loend nimega uus_loend. Teisel real on tsükli päis, mis määrab ära vahemiku, mille piires tsüklimuutuja n muutub (0-10, 11 pole kaasa haaratud). Tsükli kehas (taandega kolmas rida) lisatakse igal tsükli sammul loendile 'uus_loend' uus liige, mis on võrdne tsüklimuutuja 'n' ja arvu 2 korrutisega. Viimasel real trükitakse tulemus käsureale välja. Tulemuseks on loend, mille liikmed on paarisarvud vahemikus 0-20.

Loendi hõlmamise süntaks võimaldab loogiliselt samaväärse tulemuse saavutada väiksema koodiridade arvuga:

>>> uus_loend = [n * 2 for n in range(11)]
>>> uus_loend
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Nagu näha on tsükkel koos tsüklimuutuja korrutamisega arvuga 2 ning uue loendi moodustamine teostatud kõik ühel koodireal.

Loendi hõlmamise süntaks võimaldab programmeerijal lisada ka tingimusi, millele uue loendi liige peab vastama.

>>> uus_loend = [n for n in range(11) if n % 2 == 1]
>>> uus_loend
[1, 3, 5, 7, 9]

Sarnaselt eelmise näitega valitakse ka seekord liikmeid täisarvude hulgast vahemikus 0–10, kuid seekord neid ei korrutata arvuga kaks ehk liikmed jäävad samaks (n for n, mitte n * 2 for n). Erinevalt eelmisest korrast peavad aga liikmed seekord täitma tingimuse, et olla valitud loendi uus_loend koosseisu. See tingimus (if n % 2 == 1) tähendab, et loendi liikme täisarvulisel jagamisel arvuga 2 peab jäägiks olema 1 ehk teisisõnu peab tegemist olema paaritute arvudega.

Loendi hõlmamise süntaks võimaldab esitada ka komplekssemaid konstruktsioone, mis sisaldavad mitut tsüklit. Näiteks maatriksi ehk kahemõõtmelise m x n mõõtmetega loendi transponeerimine on programmeerimiskeeles Python teostatav loendi hõlmamise abil järgnevalt.

>>> maatriks = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> transponeeritud_maatriks = [[maatriks[rida][veerg] for rida in range(len(maatriks))] for veerg in range(len(maatriks[0]))]
>>> transponeeritud_maatriks
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

Loendi hõlmamine on lisaks Pythonile kasutusel mitmetes teistes keeltes, ehkki sageli oluliselt teistsuguse süntaksiga. Näiteks C# keeles on kasutusel järgmised viisid: 

var uus = Enumerable.Range(0, 10)
         .Where(x => x*x > 3)
         .Select(x => x*2);

Sama tulemuse annab ka SQLi süntaksiga sarnaneva süntaksiga järgmine C# kood:

var uus = from x in Enumerable.Range(0,10)
          where x*x > 3
          select x*2;

Pythonis näeb samadele tingimustele vastav loendi hõlmamine välja järgmiselt:

[2*x for x in range(10) if x**2 > 3]

Tulemuseks on vahemikust 0–9 valitud täisarvud, mida on korrutatud arvuga kaks ning mis vastavad tingimusele, et nende ruut on suurem kui arv kolm (mis eemaldab valikust arvud 0 ja 1). Seega kuuluvad uude loendisse täisarvud vahemikus 4–18.

Loendi hõlmamise konstruktsioon ei ole saadaval kõikides programmeerimiskeeltes. Näiteks C# sarnane Java vastavat konstruktsiooni ei sisalda.

Loendi hõlmamisega sarnane süntaks on näiteks Pythonis olemas ka teiste andmestruktuuride (hulgad, sõnastikud) jaoks.

Hulga genereerimine hulga hõlmamise süntaksi abil. Antud juhul valitakse sõnest 'ABCDABCD' välja tähed A ja D ning esitatakse nad ühekordselt, sest hulgas ei saa olla korduvaid elemente.

>>> {hulga_element for hulga_element in 'ABCDABCD' if hulga_element not in 'CB'}
{'A', 'D'}

Sõnastiku (võtme-väärtuste paaride) genereerimine sõnastiku hõlmamise süntaksi abil. Järgmises näites tagastatakse paarid, kus võtmeteks on positiivsed täisarvud alates nullist ning väärtusteks suurtähed A ja D.

>>> {võti: väärtus for võti, väärtus in enumerate('ABCD') if väärtus not in 'CB'}
{0: 'A', 3: 'D'}

Viited

Välislingid
Loendi hõlmamise näiteid erinevatest programmeerimiskeeltest
Loendi hõlmamise süntaksi juhend – docs.python.org
2000. aastal tehtud ettepanek võtta loendi hõlmamine Pythonis kasutusele

Programmeerimine