lunes, 1 de septiembre de 2014

EXPRESIONES REGULARES EN PYTHON(II)

METACARACTERES Y PATRONES
Son símbolos que indican un comportamiento especial como repetición, rango, exclusión o separación.
. ^ $ * + ? { } [ ] \ | ( )

.
Indica cualquier carácter (letras, números, signos de puntuación, espacios) excepto una nueva línea (\n):
>>> re.findall('.', 'Benevolent dictator')
['B', 'e', 'n', 'e', 'v', 'o', 'l', 'e', 'n', 't', ' ', 'd', 'i', 'c', 't', 'a', 't', 'o', 'r']

>>> re.findall('.', 'Benevolent\ndictator') #Ignora la nueva línea (\n)
['B', 'e', 'n', 'e', 'v', 'o', 'l', 'e', 'n', 't', 'd', 'i', 'c', 't', 'a', 't', 'o', 'r']


>>> re.findall('.', 'Benevolent\tdictator') #Pero no ignora la tabulación (\t)
['B', 'e', 'n', 'e', 'v', 'o', 'l', 'e', 'n', 't', '\t', 'd', 'i', 'c', 't', 'a', 't', 'o', 'r']


*
Tiene en cuenta el carácter anterior cero o más veces. Por ejemplo, el patrón "in*o" identifica estas combinaciones: io (ninguna presencia de la "n"), ino (una aparición de la "n"), inno (dos apariciones de la "n").
>>> re.findall('in*o', 'inocencia, innovaciones')
['ino', 'inno', 'io']


+
Indica el carácter anterior una o más veces:
>>> re.findall('in+o', 'inocencia, innovaciones')
['ino', 'inno']


[ ]
Indica que puede haber una coincidencia con cualquier carácter entre los corchetes. También se puede incluir el rango entre los corchetes:
[a-z] : cualquier letra minúscula
[0-9] : cualquier dígito
[a-dA-D] : las letras de la a la d minúsculas o mayúsculas

>>> re.findall('cu[sz]co', 'se puede escribir cusco o cuzco')
['cuzco', 'cusco']

Se puede señalar un rango usando guión. Por ejemplo, [0-9] busca cualquier dígito entre 0 y 9.

>>> re.findall('[0-9]', '3 de enero de 2014')
['3'] # el único con una sola cifra

[^ ]
Busca cualquier caracter excepto el indicado en corchetes

>>> re.findall('est[^a]s', 'estas, estos, estes')
['estos', 'estes']

{ n }
Repite el elemento previo n veces. Se puede definir un rango de coincidencias

{2,4} : de 2 a 4 caracteres siendo el comportamiento "voraz", es decir, primero trata de hacer coincidir cuatro caracteres, luego tres, luego dos.
{2,} : indica dos o más caracteres.

>>> re.findall('[a-z]{3}', 'Django es un framework de desarrollo web')
['jan', 'fra', 'mew', 'ork', 'des', 'arr', 'oll', 'web']

>>> re.findall('[a-z]{3,4}', 'Django es un framework de desarrollo web')
['jang', 'fram', 'ewor', 'desa', 'rrol', 'web']

>>> re.findall('[a-z]{3,}', 'Django es un framework de desarrollo web')
['jango', 'framework', 'desarrollo', 'web']


^ $
Buscan al comienzo y al final de la cadena de texto respectivamente.
>>> re.findall('^Django', 'Django es un framework de desarrollo web')
['Django']

>>> re.findall('^web', 'Django es un framework de desarrollo web')
[]  # 'web' no está al comienzo de la cadena

>>> re.findall('web$', 'Django es un framework de desarrollo web')
['web']  # 'web' está al final de la cadena


?
Busca una o cero coincidencias del caracter anterior.
i?python : busca python o ipython.
>>> re.findall('i?python', 'ipython es iron python')
['ipython', 'python']

\
Permite ignorar caracteres especiales como [], ?, +, etc. Por ejemplo, si queremos analizar operaciones matemáticas debemos ignorar el significado de + y * en el módulo de expresiones regulares:
>>> re.split(' \+ ', '4 + 8') #Queremos extraer sólo los números
['4', '8']

|
Identifica un patrón u otro siendo su comportamiento "no voraz (not greedy)", es decir, al encontrar la coincidencia de la izquierda se detiene. Por ejemplo, queremos sustituir un anglicismo:
>>> re.sub('basket|basketball', 'baloncesto', 'Jugadores de basketball de la liga nacional de basket') #Encuentra basketball y lo sustituye por baloncestoball
'Jugadores de baloncestoball de la liga nacional de baloncesto'

Para corregir este comportamiento simplemente colocamos el más largo primero:
>>> re.sub('basketball|basket', 'baloncesto', 'Jugadores de basketball de la liga nacional de basket')
'Jugadores de baloncesto de la liga nacional de baloncesto'


()
Los paréntesis agrupan expresiones regulares e indican el comienzo y el final de un grupo. Findall agrupa las coincidencias en tuplas:

>>> re.findall(r"(\w+): (\w+)", "nombre: Gabriel, apellido: Marquez, numero: 3")
[('nombre', 'Gabriel'), ('apellido', 'Marquez'), ('numero', '3')]



(?:)

 Agrupa expresiones regulares pero no captura coincidencias. Esto es más evidente al usar split:
>>> re.split(r"(?:\w+):", "nombre: Gabriel, apellido: Marquez, numero: 3")
['', ' Gabriel, ', ' Marquez, ', ' 3']


Comparemos al usar sólo paréntesis:
>>> re.split(r"(\w+):", "nombre: Gabriel, apellido: Marquez, numero: 3")
['', 'nombre', ' Gabriel, ', 'apellido', ' Marquez, ', 'numero', ' 3']


(?P:<>)

Agrupa coincidencias que pueden ser referenciadas por un nombre:
>>> patron = r'(?P<mes>[\d])/(?P<dia>[\d])/(?P<año>[0-9][0-9][0-9][0-9])'
>>> m = re.search(patron, 'nacio en 7/2/1978')

>>> m.group('año')
'1978'
>>> m.group('mes')
'7'
>>> m.group('dia')
'2'

Un ejemplo de la página del tutorial de django:
urlpatterns = [
    # ex: /polls/
    url(r'^$', views.index, name='index'),
    # ex: /polls/5/
    url(r'^(?P[0-9]+)/$', views.detail, name='detail'),
    # ex: /polls/5/results/
    url(r'^(?P[0-9]+)/results/$', views.results, name='results'),
    # ex: /polls/5/vote/
    url(r'^(?P[0-9]+)/vote/$', views.vote, name='vote'),
]

miércoles, 23 de julio de 2014

EXPRESIONES REGULARES EN PYTHON(I)

Las expresiones regulares son como pequeños lenguajes de programación y cuando se trata de procesar cadenas de texto resultan ser herramientas convenientes. Lo primero que hacemos en Python para trabajar con expresiones regulares es invocar al módulo:

>>> import re

Algunas funciones de este módulo son: compile, match, search, findall, split, sub, finditer.

COMPILE
Prepara el patrón de caracteres que se va a buscar. La sintaxis de compile es:

>>> re.compile(pattern, flags=0)

pattern es el patrón de caracteres que se va a buscar y flags es el modificador que cambia el comportamiento del patrón. Esto es lo que hace cada modificador que aplicamos en compile.

FLAGS:
re.IGNORECASE (re.I) no distingue entre mayúsculas y minúsculas.
re.LOCALE (re.L) permite que algunos caracteres especiales tengan en cuenta el idioma que se usa en el computador local.
re.MULTILINE (re.M) permite que "^" busque el comienzo de cada linea (no solo el comienzo del texto) y que "$" busque el final de cada linea (no solo el final del texto).
re.DOTALL (re.S) hace que "." busque cualquier caracter incluyendo una nueva linea (es decir '\n').
re.UNICODE (re.U) permite usar caracteres Unicode.
re.VERBOSE (re.X) se utiliza para poder dividir una expresión regular compleja en pequeños fragmentos que pueden tener comentarios para una mejor comprensión.
Si no se desea compilar pero sí usar modificadores, se puede usar el símbolo (?_) junto con uno o varios modificadores, por ejemplo, (?i), (?m), (?iL).
A parte de la opción de usar modificadores compile ofrece una ligera mayor velocidad de búsqueda de los caracteres correspondientes. Un ejemplo de uso de compile:

>>> pattern = re.compile('python', re.I)
>>> pattern.findall('la programación PyThOn')
['PyThOn']

Aqui con re.I se ignoran las mayúsculas y minúsculas lo que se puede lograr también sin compilar usando (?i):

>>> re.findall('(?i)python','la programacion PyThOn')
['PyThOn']

De otra forma no hay correspondencia:

>>> re.findall('python','la programacion Python')
[]

MATCH
La función match determina si hay una coincidencia únicamente al comienzo del texto procesado:

>>> patron = "Python"
>>> texto1 = "Monty Python"
>>> texto2 = "Python interpreter"
>>> match1 = re.match(patron, texto1)
>>> type(match1)  # no hay coincidencia
<type 'NoneType'>
>>> print match1
None
>>> match2 = re.match(patron, texto2)
<_sre.SRE_Match object at 0x02A2DF00>
>>> match.group() # hay una coincidencia
'Python'

Incluso si se usa el modificador MULTILINE solamente hay búsqueda al comienzo del texto.

>>> texto3 = "Monty \n Python"
>>> match3 = re.match(patron, texto3, re.M)#no hay coincidencia


SEARCH
Busca la primera aparición del patrón de búsqueda en la cadena de texto. Search devuelve un objeto match o None si no hay coincidencia. El método group() devuelve el texto coincidente.

>>> match = re.search('django', 'plataforma django, django reinhardt')
>>> match.group()
'django'

Search sólo devuelve la primera coincidencia. Para comprobarlo podemos usar match.span() que indica dónde comienza y termina el texto encontrado.

>>> match.span()
(11, 17)


FINDALL
Encuentra todas las coincidencias en forma de lista:

>>> re.findall('\d+', '23 de junio, 14 de mayo')
['23', '14']

FINDITER
Devuelve un iterador de objetos match. El resultado es igual a findall pero generando los elementos de la lista uno por uno.

>>> iter = re.finditer('\d+', '23 de junio, 14 de mayo')
>>> iter
<callable-iterator object at 0x02ACE610>
>>> iter.next()
<_sre.SRE_Match object at 0x02AD0528>
>>> iter.next().group()
'14'

SUB
Sustituye el patrón por La sintaxis de sub es:
>>> re.sub(patternreplstringcount=0flags=0)

>>> re.sub('string', 'cadena', 'remplaza la string de caracteres')
'remplaza la cadena de caracteres'

Count indica las veces que se debe sustituir el patrón de caracteres, por defecto se remplazan todas las apariciones del texto.

>>> re.sub('string', 'cadena', 'remplaza la string de caracteres string', count=1)
'remplaza la cadena de caracteres string'

Subn hace lo mismo pero devuelve una tupla con el número de remplazos.

>>> re.subn('string', 'cadena', 'remplaza la string de caracteres string')
('remplaza la cadena de caracteres cadena', 2)

SPLIT
Separa la cadena de texto en donde aparece el patrón.
>>> re.split(patternstringmaxsplit=0flags=0)

>>> re.split('web','Django es un framework web de codigo abierto escrito en Python')
['Django es un framework ', ' de codigo abierto escrito en Python']

sábado, 12 de julio de 2014

Insertar documentos en Mongodb usando Python y pymongo

Mongodb es una base de datos no relacional (NoSQL) que permite la escalabilidad (Big Data) y que ha sido adoptada por empresas reconocidas como Cisco, Codecademy, eBay, Forbes y SAP. 

Pymongo es el driver con el que se puede conectar a Python con Mongodb. Una ventaja de Mongodb es que maneja documentos json equivalentes a diccionarios en Python. Una ejemplo de cómo insertar documentos en la base de datos mongo a través de Python puede ilustrarse usando la página "http://media.mongodb.org/zips.json" que presenta una colección de códigos postales de Estados Unidos del tipo:


{

"_id": "10280",

"city": "NEW YORK",

"state": "NY",

"pop": 5574,

"loc": [

-74.016323,

40.710537

]

}

Los pasos para insertar el documento son los siguientes:



  1. >>> import urllib2
  2. >>> import pymongo
  3. >>> coneccion = pymongo.MongoClient("localhost", 27017)  #Establecemos la                                                                  #conección con mongo
  4. >>> db = coneccion.zipcodes          #La base de datos se llama zipcodes
  5. >>> datos = db.zip                   #La coleccion de documentos se llama zip
  6. >>> url = "http://media.mongodb.org/zips.json"  #De esta página extraemos los                                                                 #datos json
  7. >>> doc = urllib2.urlopen(url)                  #Abrimos la página
  8. >>> for line in doc:
  9.         datos.insert(eval(line))  #Recorremos cada línea, la convertimos en                                                           #diccionario y
  10.                                   #la insertamos en la colección zip


Ahora si entramos a la linea de comados de mongo podemos comprobar que existe la base de datos y la colección. Y podemos usar los comandos de búsqueda de mongodb:

  1. C:\Users>mongo
  2. MongoDB shell version: 2.4.8
  3. connecting to: test
  4. > show dbs
  5. datoszip        0.203125GB
  6. reddit  0.203125GB
  7. test    0.203125GB
  8. > use datoszip
  9. switched to db datoszip
  10. > show collections
  11. system.indexes
  12. zip
  13. > db.zip.findOne()
  14. {
  15.         "_id" : "01001",
  16.         "city" : "AGAWAM",
  17.         "state" : "MA",
  18.         "pop" : 15338,
  19.         "loc" : [
  20.                 -72.622739,
  21.                 42.070206
  22.         ]
  23. }
  24. > db.zip.find({city:'SEATTLE'}).limit(2).pretty()
  25. {
  26.         "_id" : "98101",
  27.         "city" : "SEATTLE",
  28.         "state" : "WA",
  29.         "pop" : 5801,
  30.         "loc" : [
  31.                 -122.330456,
  32.                 47.611435
  33.         ]
  34. }
  35. {
  36.         "_id" : "98102",
  37.         "city" : "SEATTLE",
  38.         "state" : "WA",
  39.         "pop" : 19000,
  40.         "loc" : [
  41.                 -122.320993,
  42.                 47.63025
  43.         ]
  44. }

lunes, 23 de junio de 2014

Generadores, funciones lambda y el problema 2 del proyecto Euler

En el problema 2 del proyecto Euler hay que hallar la suma de los números pares en la secuencia de Fibonacci:

Even Fibonacci numbers

Problem 2

Published on 19 October 2001 at 06:00 pm [Server Time]
Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:
1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
By considering the terms in the Fibonacci sequence whose values do not exceed four million, find the sum of the even-valued terms.


Con las funciones lambda y filter podemos extraer los números pares de una secuencia:


  1. secuencia_par = filter(lambda x: x%2 == 0, [1,2,3,4,5,6])
  2. print secuencia_par
  3. Out[1]: [2, 4, 6]


Los generadores son como funciones pero en vez de la sentencia return tienen sentencias yield que generan una sola salida y detienen la ejecución del código hasta que vuelven a ser llamadas. Por ejemplo, el siguiente código genera valores por encima del parámetro ingresado que pueden ser llamados con el método next():


  1. def contador(x):
  2.     while True:
  3.         yield x
  4.         x += 1
  5.        
  6. c = contador(3)
  7. c.next()
  8. Out[2]: 3
  9. c.next()
  10. Out[3]: 4
  11. c.next()
  12. Out[4]: 5
  13. c.next()
  14. Out[5]: 6

La secuencia de fibonacci se puede obtener con un generador lo que es ventajoso en términos de manejo de memoria:

  1. def fibonacci(num):
  2.         x,y = 0,1
  3.         while x < num:
  4.                 yield x
  5.                 x, y = y, x + y


Combinando la función sum() con filter y lambda podemos resolver el problema 2 del proyecto Euler:

  1. lista = fibonacci(4000000) #un generador
  2. pares = lambda x: x%2 == 0 #selecciona los números pares
  3. print sum(filter(pares, lista))

El resultado es 4613732.

lunes, 21 de abril de 2014

Check iO: un juego para programar con Python

Check iO es una sitio creado por programadores ucranianos con ejercicios para practicar las habilidades adquiridas en Python. 


http://www.checkio.org/
Uno de los problemas planteados en el sitio es el siguiente:

"Se comienza a alimentar a las palomas. Un minuto más tarde aparecen dos más y luego de otro minuto aparecen tres, luego cuatro y así sucesivamente. Una porción de comida le dura un minuto a cada paloma y en caso de que no haya suficiente comida para todas, comen las que llegaron primero. Las palomas no dejan de comer. Si hay N porciones de comida, ¿Cuántas palomas se pueden alimentar con al menos una porción de comida?"




Para resolverlo creé una clase llamada Pigeon con las propiedades eat y fed:

  1. class Pigeon(object):
  2.     def __init__(self): 
  3.         self.yum = 0 #porciones consumidas
  4.     def eat(self, portions):
  5.         if portions > 0:
  6.             self.yum += 1
  7.             portions -= 1
  8.         return portions #porciones que quedan
  9.     def fed(self):
  10.         return self.yum > 0 #devuelve False o True


El procedimiento para hallar la solución es el siguiente:


  1. def alimentar_aves(porciones):
  2.         minuto = 0
  3.         aves = []
  4.         while porciones > 0:
  5.                 minuto += 1
  6.                 for i in range(minuto):
  7.                         aves.append(Pigeon())
  8.                 for ave in aves:
  9.                         porciones = ave.eat(porciones)
  10.                         if porciones == 0:
  11.                                 break
  12.         cont = 0
  13.         for paloma in aves:
  14.                 if paloma.fed():
  15.                         cont += 1
  16.         return cont

Y como el ejemplo de la figura de arriba, el resultado para cinco porciones es:


  1. alimentar_aves(5)
  2. >>> 3


lunes, 24 de febrero de 2014

PYTHON, UNICODE Y EXPRESIONES REGULARES

Una expresión regular es una forma de describir un conjunto de cadenas de texto. En Python se debe llamar al modulo re para buscar o remplazar patrones de texto. La función que busca todas las coincidencias es re.findall(patrón,texto,marcador). He aquí algunos ejemplos:




import re
print re.findall('reg','expresion regular')
print re.findall('201\d','2008, 2009, 2010, 2011')
print re.findall('\w+','expresion regular')

Salida:

['reg']
['2010', '2011']
['expresion', 'regular']
El patrón '\w+' corresponde a cualquier cantidad de letras sin incluir espacios vacíos. '201\d' corresponde a un número de cuatro cifras que comienza con 201. Más información acerca de expresiones regulares se puede encontrar en los siguientes vínculos:



En inglés es suficiente la codificación ASCII, pero en español para representar la eñe y las tildes es necesario la codificación Unicode. En un archivo Python hay que declarar Unicode al comienzo de la siguiente forma:

# -*- coding: UTF-8 -*-
En el siguiente ejemplo todavía no se reconoce la ó como una letra válida:
# -*- coding: UTF-8 -*-
import re
print re.findall('\w+','expresión regular')


Salida:

['expresi', 'n', 'regular']

De modo que especificamos el marcador (flag) re.UNICODE para que se tengan en cuenta los caracteres Unicode:

# -*- coding: UTF-8 -*-
import re
print re.findall('\w+','expresión regular',re.UNICODE)


Salida:

['expresi\xc3\xb3n', 'regular']
La ó aparece con su código Unicode, pero aparece normalmente con la función print:

lista = re.findall('\w+','expresión regular',re.UNICODE)
print lista
for item in lista:
    print item


Salida:

['expresi\xc3\xb3n', 'regular']
expresión
regular