A funkcionális programozás (ls. még a wikipediát) kapcsán szót ejtünk a programozási paradigmákról:
A nyelvek jó része ilyen, ahol a program utasítások sorozata, melyek leírják, hogy a program mit tegyen a bemenettel. (Pl. C, bash)
Itt leírjuk a problémát, és a program találja ki, konstruálja meg a szükséges lépéseket. (Pl. az adatbáziskezelő nyelvek ilyenek: SQL)
Objektumorientált programozás – az objektumok állapotainak kezelése köré szervezi a programot, alapjaival megismerkedtünk korábban. (Pl. JAVA)
A probléma függvényekre, illetve ezek kompozíciójára van bontva. Egy függvény adott bemenetre mindig ugyanazt a kimenetet adja, és a tisztán funkcionális programban nem változtatja a program állapotát. (Pl. Haskell, Standard ML)
A fenti feltétel nem teljesül, ha változóknak értéket adok majd azokat megváltoztatom, például az alábbi függvény más eredményt adhat akkor is, ha a paramétere ugyan az.
def f(y):
return x + y
x = 1
print(f(5))
x = -5
print(f(5))
Ha ezt elkerüljük, akkor a függvényeink matematikai értelemben is függvények lesznek, nem pedig eljárások (gépi utasítások) egymás utánjai. Innen a név, funkcionális programozás.
Guido van Rossum eredetileg ki kívánta dobni a funkcionális programozási nyelvekből ismert alapfüggvényeket a Python 3 core-ból a listaértelmezéssel helyettesíthetőnek tartva a fontosabbakat. A funkcionális programozás híveinek kemény ellenállását látva végül a lambda
, map()
, filter()
maradt, de a reduce()
kikerült az functools
csomagba.
A funkcionális programok helyessége könnyebben igazolható, könnyebben tesztelhetők, modulárisabbak,...
A generátorkifejezés egy iterátort ad vissza, a listaértelmezés egy listát. Előbbit()
-be, utóbbit []
-be tesszük.
Sztringek listáját kapjuk egy fájlból beolvasott sorokból. A .strip()
metódust használva vegyük le mindegyik sor elejéről és végéről a white space karaktereket.
lines = [' line1 ', 'line2\t', '', ' line3 \n ']
stripped_iter = (line.strip() for line in lines)
for i in stripped_iter:
print(i, end="")
[line.strip() for line in lines]
[line.strip() for line in lines if line != ""]
A generátorkifejezés általános alakja:
(expression for expr1 in seq1
if condition1
for expr2 in seq2
if condition2
...
for exprN in seqN
if conditionN )
A listaértelmezésé ugyanez, csak []
-ben. Ez a következő kóddal ekvivalens:
for expr1 in seq1:
if not (condition1):
continue
for expr2 in seq2:
if not (condition2):
continue
...
for exprN in sequenceN:
if not (conditionN):
continue
expression
[(x, y, x * y) for x in (1, 2, 3) for y in [1, 2, 3, 4] if x < y]
for x in (1, 2, 3):
for y in [1, 2, 3, 4]:
if x < y:
print((x, y, x*y), end=", ")
Írjuk ki az 1890 és 1915 közti szökőéveket:
[ev for ev in range(1890, 1915)];
[ev for ev in range(1890, 1915) if ev%4 == 0]
[ev for ev in range(1890, 1915)
if (ev%4 == 0 and ev%100 != 0) or ev%400 == 0]
Ha egy egyszerű kifejezést szeretnénk kiértékelni, akkor ezekhez külön függvényt írni nem feltétlenül szükséges, lokálisan használhatunk egy egyszerűbb módon definiált függvényt, ez a lambda-függvény. Ennek alakja
lambda arguments: expression
def negyzet(x):
return x * x
negyzet(5)
negyzet = lambda x: x * x
negyzet(5)
Akár nevet sem kell adni az objektumnak:
(lambda x: x**2)(5)
A lambda-függvénynek több argumentuma is lehet:
kif = lambda a, b, c: a + b*c
kif(2, 4, 5)
A map
egy iterálható objektum minden elemére alkalmazza a megadott függvényt és visszaad egy iterátort. (A Python korábbi változataiban egy listát adott vissza). Matematizált formulával valahogy így nézne ki:
$$\mathop{\mathrm{map}}:(A\mapsto B)\times A^n\mapsto B^n\ \ (n\in \mathbb{N})$$
l = [1, 5, 8]
print(map(negyzet, l))
list(map(negyzet, l))
d = {1: 5, 2: 6, 3: 7}
list(map(negyzet, d))
list(map(negyzet, d.values()))
list(map(lambda x: x.capitalize(), ['pápa', 'tata']))
Listaértelmezéssel:
[x**2 for x in range(5)]
[x.capitalize() for x in ['pápa', 'tata']]
A map
többváltozósként is használható. Ha az $f$ függvény több változós, akkor több lista megadásá szükséges és
a map
mindegyiken szimultán halad és hattatja az $f$ függvényt.
Formalizálva valahogy így: $$\mathop{\mathrm{map}}: (A \mapsto X)\times A^n \mapsto X^n$$ $$\mathop{\mathrm{map}}: (A\times B \mapsto X) \times A^n\times B^n \mapsto X^n$$ $$\mathop{\mathrm{map}}: (A\times B \times C\mapsto X)\times A^n\times B^n\times C^n \mapsto X^n$$ $$\vdots$$
list(map(pow, [.5, 1, 1.5], [-1, 2, 3]))
list(map(pow, [.5, 1], [-1, 2, 3]))
lambda függvényekben használhatunk feltételes kifejezéseket is. A feltételes kifejezések formája a következő:
kifejezés if feltétel else kifejezés a feltétel hamis esetre
x = -4
x if x>0 else -x
def abszolut(x):
return x if x >= 0 else -x
print(abszolut(5))
print(abszolut(-5))
list(map(lambda x: x if x>0 else 0, range(-5,5)))
Feltételes kifejezésekben az else
használata kötelező.
Ennek egy következménye, hogy nem lesznek lefedetlen esetek a kódban.
Példa lefedetlen esetekre:
def korosztaly(x):
if x < 8:
return "gyerek"
elif x < 15:
return "fiatal"
elif x >=18:
return "felnőtt"
korosztaly(16)
def korosztaly(x):
return "gyerek" if x < 8 else ("fiatal"
if x < 15 else ("felnőtt"
if x >= 18 else "egyik sem"))
korosztaly(16)
A filter
függvény leszűr egy listát egy adott bool-függvény szerint.
A feltétel függvény egy $A\mapsto \{True, False\}$ függvény kell legyen.
$$filter : (A\mapsto \{True, False\})\times A^n\mapsto A^m \text{ ahol } m\leq n$$list(filter(lambda x: x % 2 == 0, range(10)))
[x for x in range(10) if x % 2 == 0]
list(filter(str.isupper, "aAbBcCöŐ"))
zip()
¶list(zip(['a', 'b', 'c'], (1, 2, 3)))
any()
és all()
¶A kvantoroknak megfelelő függvények eldöntik, hogy egy bejárható objektum elemei közt van-e True, illetve hogy mindegyik True értékű-e.
any([1,2,0,1])
all([1,2,0,1])
import math
print([math.cos(i*math.pi) for i in range(3)])
any([math.cos(i*math.pi) == 1.0 for i in range(3)])
all([math.cos(i*math.pi) == 1.0 for i in range(3)])
A reduce
(a functools
csomag függvénye) egy 2-változós függvényt alkalmaz egy iterálható objektum (pl. lista) elemeire balról jobbra haladva, míg végül egyetlen értéket nem kap. Alakja:
reduce(*függvény*, *iterálható*[, *kezdőérték*])
A kétváltozós függvény $A\times B\mapsto A$ alakú kell legyen, az iterálható elemek $B$ beliek, a kezdőérték $A$-beli. A kezdőértéket a többi elem elé teszi. Ez a visszaadott érték üres lista esetén. Ha nincs kezdőérték, és a lista egyelemű, akkor azt az elemet adja vissza.
Egy $(a, b, c)$ tuple-re és egy $x$ kezdőértékre a reduce
a következőt adja:
$$\mathop{\mathrm{reduce}} (f, (a,b,c)) = f(f(a, b), c)$$
$$\mathop{\mathrm{reduce}} (f, (a,b,c), x) = f(f(f(x, a), b), c)$$
from functools import reduce
osszeg = lambda a, b: a+b
reduce(osszeg, [1, 2, 3, 4])
reduce(osszeg, [4])
reduce(osszeg, [], 0)
osztas = lambda a, b: a/b
print(reduce(osztas, [2, 3, 4], 1.0))
1/24.0
Ezzel az összegzés tétele elvileg bármikor elvégezhető és a szélsőérték keresés ennek egy alesete.
nagyobb = lambda x, y: x if x>y else y
reduce(nagyobb, [0, 1, 10, -10, 2], float("-inf"))
kisebb = lambda x, y: x if x<y else y
reduce(kisebb, [0, 1, 10, -10, 2], float("inf"))
reduce(kisebb, [], float("inf"))
def skalar(a, b):
return sum(map(lambda x, y: x*y, a, b))
skalar([0.5, 1, 1.5], [-1, 1, -1])
Általában az $f(g(x_1, y_1), g(x_2, y_2), \ldots g(x_n, y_n))$ akalú kifejezéseket lehet a skalár szorzás általánosításának nevezni.
Könnyű implementálni funkcionálisan.
def inner(a, b, g=lambda x, y: x*y, f=sum):
return f(list(map(g, a, b)))
inner([0.5, 1, 1.5], [-1, 1, -1])
inner("abc", [1, 3, 2], f=list)
Vagy ha a $g$ függvény asszociatív, akkor lehet két változósnak is tekinteni és reduce-al számolni.
def inner(a, b, g=lambda x, y: x*y, f=lambda a, b: a+b, i=0):
return reduce(f, list(map(g, a, b)), i)
inner([0.5, 1, 1.5], [-1, 1, -1])
inner("abc", [1, 3, 2], i='')
inner([1,11,4], [3,2,6], g=min, f=max, i=float("-Inf"))
inner([1,11,4], [3,2,6], g=max, f=min, i=float("Inf"))
Tegyük ugyanezt a 'vagy'-ok 'és'-ével és az 'és'-ek 'vagy'-ával.
Ehhez az or
és and
műveletek függvényváltozata kell. Vagy csinálunk ilyet, vagy az operator
modulból betöltjük:
or_ = lambda a, b: a or b
and_ = lambda a, b: a and b
from operator import or_, and_
inner([0,0,1,1], [0,1,0,1], g=or_, f=and_, i=1)
inner([0,0,1,1], [0,1,0,1], g=and_, f=or_, i=0)
Hányféleképp lehet nyolc királynőt föltenni egy sakktáblára, hogy semelyik kettő ne üsse egymást?
Az ilyen bástyaelhelyezések száma 8! = 40320. A királynők azonban átlósan is üthetik egymást. Az i-edik és j-edik sorban lévő királynő pontosan akkor nem üti egymást, ha abs(i - j) != abs(x[i] - x[j]). A permutációk listázásához töltsük be az itertools
csomag permutations
függvényét, mely a for ciklus minden ciklusában ad egy következő permutációt, amíg a végére nem ér. Így a megoldás:
from itertools import permutations
s = 0
for x in permutations(list(range(8))):
if all([i == j or abs(i - j) != abs(x[i] - x[j])
for i in range(8) for j in range(8)]):
s += 1
print(s)
for x in permutations(list(range(8))):
if all([i == j or abs(i - j) != abs(x[i] - x[j])
for i in range(8) for j in range(8)]):
print(x)