Es un paradigma de programación donde los programas se ejecutan evaluando expresiones, en contraste con la programación imperativa donde los programas se componen de sentencias que alteran el estado del sistema.
Para ello es necesario que las funciones sean de primera-clase, es decir que sean tratadas como cualquier otro valor, puedan ser pasadas como argumentos a otras funciones y puedan ser regresadas como valores a otras funciones.
El hecho de las funciones no tengan efectos secundarios es muy importante para el procesamiento de datos, ya que permite paralelizar las operaciones.
A continuación presentamos algunos ejemplos de funciones de orden superior. En python, una función lambda
se utiliza para definir un objeto de tipo función. Estas funciones tampoco tienen efectos secundarios.
In [9]:
def operate(f, arg1, arg2):
print f(arg1, arg2)
add = lambda a, b: a + b
sub = lambda a, b: a - b
mul = lambda a, b: a * b
div = lambda a, b: a / b
operate(add, 1, 2)
operate(sub, 1, 2)
operate(mul, 1, 2)
operate(div, 1, 2)
Utilizando PySpark, podremos ver de mejor manera el potencial de los principios de programación funcional.
In [1]:
from pyspark import SparkContext
sc = SparkContext("local[2]", "Functional", pyFiles=[])
Primero definimos el contexto de Spark y creamos una colección paralela. Esto nos permitirá utilizar modismos funcionales desde python.
In [46]:
import math
print 'La función sqrt:', math.sqrt
col = sc.parallelize([1,2,3,4,5])
sqrts = col.map(math.sqrt)
print 'sqrt(x):', sqrts.collect()
La función map
recibe una función como argumento y aplica la operación definida a cada uno de los elementos de la colección regresando una nueva colección con los valores calculados.
En este ejemplo podemos ver aplicados al menos dos de los principios de programación funcional.
math.sqrt
se utiliza como un argumento a otra función.map
no se está cambiando el estado global del programa, el estado de la variable col
no se afecta, sino que regresa una nueva colección con el resultado de aplicar la función math.sqrt
In [47]:
import re
col = sc.parallelize(["hola", "esto", "es", "una", "demostracion"])
lenghts = col.map(len)
count = lenghts.fold(0, add)
print 'len(x):', lenghts.collect()
print 'total:', count
cat = col.fold("", add)
print 'cat:', cat
consonants = col.map(lambda st: re.sub(r"[aeiou]", "", st))
print 'remove [aeiou]:', consonants.collect()
En este ejemplo podemos ver como realizar operaciones más elaboradas, por ejemplo la función fold
nos permite agregar los resultados utilizando una funcion asociativa y un valor neutral.
Es importante notar el orden en el que se aplica la función fold.
Ahora contemos el número de veces que aparece cada letra en la colección:
In [48]:
words = col.map(list)
print 'split words:', words.collect()
chars = col.flatMap(list)
counts = chars.map(lambda item: (item, 1)).reduceByKey(add)
print 'chars:', chars.collect()
print 'counts:', counts.collect()
print chars.countByKey()
En este ejemplo utilizamos dos funciones nuevas, la función flatMap
aplica la función a cada uno de los elementos y después des-empaca las iterables que resultan. reduceByKey
agrega los resultados utilizando una función asociativa sobre la llave.
In [3]:
ds1 = sc.parallelize([("mexico", 2), ("australia", 8), ("alemania", 9),
("canada", 1), ("francia", 8)])
ds2 = sc.parallelize([("mexico", "green"),
("australia", "blue"),
("alemania", "yellow"),
("canada", "red"),
("canada", "blue"), ("francia", "white")])
dataset = ds1.join(ds2)
print dataset.collect()
La función join
nos permite unir los elementos de un dataset a partir de una llave.
In [6]:
users = sc.textFile('data/users.tsv')
count = users.count()
print 'usuarios:', count
In [11]:
data = users.map(lambda st: st.split('\t')).cache()
print data.take(5)
In [10]:
total_followers = data.map(lambda item: int(item[3])).fold(0, add)
total_friends = data.map(lambda item: int(item[4])).fold(0, add)
total_status = data.map(lambda item: int(item[5])).fold(0, add)
print 'average followers: %.2f' % (total_followers / float(count))
print 'average friends: %.2f' % (total_friends / float(count))
print 'average status: %.2f' % (total_status / float(count))
In [28]:
screen_name_lenght = data.map(lambda item: len(item[1])).fold(0, add)
print 'average screen name: %.2f' % (screen_name_lenght / float(count))
In [17]:
early_users = data.filter(lambda item: int(item[0]) < 1000)
early_name_lenght = early_users.map(lambda item: len(item[1])).fold(0, add)
print 'average screen name: %.2f' % (early_name_lenght / float(early_users.count()))
In [20]:
locations = data.map(lambda item: item[8]).map(lambda item: (item, 1)).reduceByKey(add)
top_locations = locations.map(lambda item: (item[1], item[0])).sortByKey(ascending=False)
print top_locations.take(5)
In [18]:
early_locations = early_users.map(lambda item: item[12]).map(lambda item: (item, 1)).reduceByKey(add)
top_early_locations = early_locations.map(lambda item: (item[1], item[0])).sortByKey(ascending=False)
print top_early_locations.take(5)
Finalmente como un ejercicio de clase vamos a obtener cuales son los elementos comunes en las descripciones de los usuarios.
In [37]:
descriptions = data.map(lambda item: item[11])
tokens = descriptions.map(lambda desc: desc.lower())\
.flatMap(lambda desc: desc.split())
token_count = tokens.map(lambda token: (token, 1))\
.reduceByKey(add)
freq = token_count.map(lambda _: _[1])
total_tokens = freq.fold(0, add)
print total_tokens
In [38]:
%pylab inline
In [47]:
fr = sorted(freq.collect(),reverse=True)
plt = plot(fr)
yscale('log')
xscale('log')
Podemos observar que la distribución del lenguaje en las descripciones sigue una ley de Zipf, es decir hay un gran número de palabras que se utilizan muy poco, al mismo tiempo que hay un número pequeño de palabras que se utilizan de manera muy frecuente.
In [ ]: