1 Introducción a la programación con Sage

1.1 Tipos de datos en Sage

1.1.1 Introducción a Sage

1.1.2 Operaciones

1.1.3 Variables

variable = expresión
print variable
del variable

1.1.4 Tipos de datos

sage:  n(sqrt(2))
1.41421356237310 
sage: n = 12
sage: n.factor()
2^2 * 3

1.1.5 Secuencias de datos

1.1.5.1 Cadenas de caracteres

sage: print ’Hola’ + ’tu’
Holatu
sage: print ’Hola’*2
HolaHola
sage: print len(cadena1), len(cadena2), len(cadena3), len(cadena4)
4  8  12  15
sage: cadena = ’Si miras al abismo...’
sage: print cadena[0], cadena[1], cadena[2], cadena[3]
S i   m
sage: cadena = ’Si miras al abismo...’
sage: print cadena[3:10]
miras a

1.1.5.2 Tuplas

sage: ej_tupla = (1.5,'a',7,'d')
sage: ej_tupla[2]
7
sage: ej_tupla = (1.5,'a',7,'d',9,6)
sage: ej_tupla[1:3]
('a', 7)
sage: (1,3) + (2,5,7)
(1, 3, 2, 5, 7)
sage: (5,2)*3
(5, 2, 5, 2, 5, 2)
sage: a,b,c = (2,5,3)
sage: (c,b,a)
(3, 5, 2)

1.1.5.3 Listas

sage: ej_lista = [1.5,'a',7,'d']
sage: ej_lista[2]
7
sage: ej_lista = [1.5,'a',7,'d',9,6]
sage: ej_lista[1:3]
['a', 7]
sage: [1,3] + [2,5,7]
[1, 3, 2, 5, 7]
sage: [5,2]*3
[5, 2, 5, 2, 5, 2]
sage: ej_lista = [1,'a',7,'d',9,6]
sage: ej_lista[2] = 8
sage: ej_lista
[1, 'a', 8, 'd', 9, 6]
sage: ej_lista = [1,'a',7,'d',9,6]
sage: del ej_lista[2]
sage: ej_lista
[1, 'a', 'd', 9, 6]
sage: ej_lista = [1,'a',7,'d',9,6]
sage: ej_lista.insert(2,'e')
sage: ej_lista
[1, 'a', 'e', 7, 'd', 9, 6]
sage: srange(3,9,2)
[3, 5, 7]
sage: srange(9)
[0, 1, 2, 3, 4, 5, 6, 7, 8]
sage: srange(3,9)
[3, 4, 5, 6, 7, 8]

1.1.5.4 Pertenencia

sage: 'a' in "Hoy"
False
sage: 'o' in "Hoy"
True
sage: 7 in (3,2,5)
False
sage: 7 in (3,7,5)
True
sage: 7 in [3,2,5]
False
sage: 7 in [3,7,5]
True

1.1.5.5 Conversiones entre secuencias

sage: tuple("abc")
('a', 'b', 'c')
sage: tuple([1,2,3])
(1, 2, 3)
sage: list("abc")
['a', 'b', 'c']
sage: list((1,2,3))
[1, 2, 3]
sage: ej_lista = ['a','b','c']
sage: ''.join(ej_lista)
'abc'
sage: '-'.join(ej_lista)
'a-b-c'

1.1.6 Mostrar información por pantalla

sage: a = 3
sage: b = 7
sage: print a, b
3 7
sage: x = "Esto"
sage: print "%s es una cadena de %d elementos" %(x,len(x))
Esto es una cadena de 4 elementos
sage: a=2 ; b=7
sage: print "La media de %d y %d es %f" %(a,b,(a+b)/2)
La media de 2 y 7 es 4.500000
sage: print "La media de %d y %d es %.1f" %(a,b,(a+b)/2)
La media de 2 y 7 es 4.5

1.2 Control del flujo del programa

1.2.1 Alternativas y bucles

1.2.1.1 Alternativas lógicas

if condicion1:
  bloque de instrucciones 1
elif condicion2:
  bloque de instrucciones 2
...
else:
  bloque de instrucciones final
sage: x = 6
sage: if x%2 == 0:
....:       print '%d es par' %x
....: 
6 es par
sage: x = 7
sage: if x%2 == 0:
....:       print '%d es par' %x
....: else:
....:       print '%d es impar' %x
....: 
7 es impar
sage: xs = [5]
sage: if xs:
....:       print "la lista es no vacía"
....: else:
....:       print "la lista es vacía"
....: 
la lista es no vacía
sage: xs = []
sage: if xs:
....:       print "la lista es no vacía"
....: else:
....:       print "la lista es vacía"
....: 
la lista es vacía

1.2.1.2 Bucle for

for elemento in lista:
    instrucciones
sage: suma = 0
sage: for x in [3,2,5]:
....:   suma = suma + x
....: 
sage: suma
10

1.2.1.3 Bucles while

while condicion:
    instrucciones
sage: i = 0
sage: while i < 7:
....:   i = i+1
....: 
sage: i
7

1.2.2 Bloques anidados

sage: k = 100
sage: suma = 0
sage: for j in range(k):
....:   if is_prime(j):
....:      suma = suma + 1/j
....: 
sage: print 'La suma de los inversos de los primos menores que  %d es  %.3f' %(k, suma)
La suma de los inversos de los primos menores que  100 es  1.803
sage: for j in srange(10):
....:   for k in srange(j):
....:     print k, 
....:   print
....: 
0
0 1
0 1 2
0 1 2 3
0 1 2 3 4
0 1 2 3 4 5
0 1 2 3 4 5 6
0 1 2 3 4 5 6 7
0 1 2 3 4 5 6 7 8

1.2.2.1 Tablas de verdad

sage: for p in [True,False]:
....:    for q in [True,False]:
....:       for r in [True,False]:
....:          print 'p = %s, q = %s, r = %s => e = %s' %(p,q,r,e)
....: 
p = True, q = True, r = True => e = e
p = True, q = True, r = False => e = e
p = True, q = False, r = True => e = e
p = True, q = False, r = False => e = e
p = False, q = True, r = True => e = e
p = False, q = True, r = False => e = e
p = False, q = False, r = True => e = e
p = False, q = False, r = False => e = e

1.2.2.2 Buscar un número con ciertas propiedades

sage: k = 196
sage: tercio = ceil(k/3)
sage: dosTercios = floor(2*k/3)
sage: for x in range(tercio, dosTercios):
....:    if gcd(x,k) == 1:
....:       break
....: 
sage: print x
67
sage: k = 196
sage: tercio = ceil(k/3)
sage: dosTercios = floor(2*k/3)
sage: x = randint(tercio,dosTercios)
sage: while gcd(x,k) != 1:
....:    x = randint(tercio,dosTercios)
....: 
sage: print x
69

1.2.3 Definición de funciones

def funcion(argumento1,argumento2,...):
   documentación
   instrucciones
sage: informacion(2)
Información sobre 2
    es primo
    es par
    es potencia de dos
sage: informacion(7)
Información sobre 7
    es primo
sage: informacion(8)
Información sobre 8
    es par
    es potencia de dos
def informacion(n):
    print 'Información sobre %d'%n
    if is_prime(n):
        print '    es primo'
    if n % 2 == 0:
        print '    es par'
    if is_power_of_two(n):
        print '    es potencia de dos'
sage: radio(3,4)
5
sage: r1 = radio(1,1); r2 = radio(5,0)
sage: r2-r1
-sqrt(2) + 5
def radio(x,y):
    return sqrt(x^2 + y^2)

1.2.3.1 Documentación

def radio(x,y):
    '''el radio del círculo con centro en el origen y que pasa por el
    punto (x,y).'''
    return sqrt(x^2 + y^2)

1.2.3.2 ¿Cuándo usar una función?

sage: sumaPrimos(3)
1/2
sage: n(sumaPrimos(3))
0.500000000000000
sage: sumaPrimos(50)
1021729465586766997/614889782588491410
sage: n(sumaPrimos(50))
1.66164651701580
def sumaPrimos(k):
  suma = 0
  for j in srange(k):
    if is_prime(j):
      suma = suma + 1/j
  return suma

# 2ª definición (usando prime_range)
# ==================================

def sumaPrimos2(k):
  suma = 0
  for j in prime_range(k):
      suma = suma + 1/j
  return suma

1.2.3.3 Tratar con argumentos erróneos

sage: range('a')
TypeError: range() integer end argument expected, got str.
sage: 1/0
ZeroDivisionError: Rational division by zero
def f(a):
    return 1/a

def g(a,b):
    return a+f(b)

# Ejemplo de la propagación del error
#    sage: g(1,0)
#    .../codigo/<string> in g(a, b)
#    .../codigo/<string> in f(a)
#    ZeroDivisionError: Rational division by zero
sage: factorial(3)
6
sage: factorial(-3)
Exception: El argumento de factorial debe ser positivo
def factorial(n):
    if n <= 0:
        raise Exception, 'El argumento de factorial debe ser positivo'
    acumulador = 1
    k = n
    while k != 0:
        acumulador *= k
        k -= 1
    return acumulador

1.3 Un poco de programación funcional

1.3.1 Funciones recursivas

sage: factorial(5)
120
sage: factorial(50)
30414093201713378043612608166064768844377641568960512000000000000
def factorial(n):
    if n == 0:
        return 1
    else:
        return n*factorial(n-1)
def factorial(n):
    return n*factorial(n-1)

      Se tiene

sage: factorial(5)
RuntimeError: maximum recursion depth exceeded

1.3.2 Funciones de orden superior

sage: funciones = [is_prime, is_prime_power, is_power_of_two, is_square]
sage: k = 5
sage: for f in funciones:
....:     print f(k)
....: 
True
True
False
False
sage: hasta(lambda x: x*2, lambda x: x > 1000, 1)
1024
def hasta(f,p,x):
    t = x
    while not p(t):
        t = f(t)
    return t
sage: raiz(49,0.1)
7.00140647524394
# 1ª definición (con funciones auxiliares):
def raiz(a,e):
    def aceptable(t): 
        return abs(t**2-a) < e
    def mejora(t):  
        return (t+a/t)/2
    return n(hasta(mejora,aceptable,a))

# 2ª definición (con funciones anónimas):
def raiz2(a,e):
    return n(hasta(lambda t: (t+a/t)/2,
                   lambda t: abs(t**2-a) < e,
                   a))

"Si f(x) es una función continua en el intervalo [a, b], y si, además, en los extremos del intervalo la función f(x) toma valores de signo opuesto (f(a) * f(b) < 0), entonces existe al menos un valor c en (a, b) para el que f(c) = 0".

La idea es tomar el punto medio del intervalo c = (a+b)/2 y considerar los siguientes casos:

Definir la función ceroBiseccion tal que ceroBiseccion(f,a,b,e) es una aproximación del punto del intervalo [a,b] en el que se anula la función f, con un error menor que e, aplicando el método de la bisección (se supone que f(a)*f(b)<0). Por ejemplo,
sage: ceroBiseccion(cos,0,2,0.0001)
1.57080078125000
sage: ceroBiseccion(lambda x: x^2-49,0,10,0.1)
7.00195312500000
load Hasta.sage

# 1ª solución (por recursión):
def ceroBiseccion(f,a,b,e):
    def aceptable(x): 
        return abs(f(x)) < e 
    def aux(c,d): 
        m = (c+d)/2
        if aceptable(m):    
            return m
        elif f(c) * f(m)  < 0:  
            return aux(c,m)
        else: 
            return aux(m,d)
    return n(aux(a,b))


# 2ª solución (con until):
def ceroBiseccion2(f,a,b,e): 
    def aceptable(t):
        (x,y) = t
        return abs(f(x)) < e
    def mejora(t): 
        (x,y) = t
        m = (x+y)/2
        if f(x) * f(m) < 0:  
            return (x,m)
        else:
            return (m,y)
    return n(hasta(mejora,aceptable,(a,b))[0])
sage: def suma (x,y):
....:    return x+y
....: 
sage: reducir(suma,[2,5,3])
10
sage: reduce(lambda x,y: x+y, [2,5,3])
10
sage: reducir(operator.add,[2,5,3])
10
sage: reducir(operator.mul,[2,5,3])
30
sage: reducir(operator.concat,[[3,5],[2],[4,6,7]])
[3, 5, 2, 4, 6, 7]
def reducir(f,xs):
    r = xs[0]
    for x in xs[1:]:
        r = f(r,x)
    return r

1.4 Secuencias y flujos de datos

1.4.1 Transformar los elementos de una lista

sage: [x^2 for x in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
sage: [is_prime(x) for x in range(10)]
[False, False, True, True, False, True, False, True, False, False]
sage: [len(x) for x in ['En', 'un', 'lugar', 'de', 'la', 'Mancha']]
[2, 2, 5, 2, 2, 6]
sage: [x.upper() for x in ['En', 'un', 'lugar', 'de', 'la', 'Mancha']]
['EN', 'UN', 'LUGAR', 'DE', 'LA', 'MANCHA']

1.4.2 Filtrar una lista

sage: [x for x in srange(10) if is_prime(x)]
[2, 3, 5, 7]
sage: [x for x in ['En', 'un', 'lugar', 'de', 'la', 'Mancha'] if len(x)==2]
['En', 'un', 'de', 'la']

1.4.3 Todo a la vez

sage: [[x,y] for x in [2,3,5,6] if is_even(x) for y in [4,7,6,9] if is_odd(y)]
[[2, 7], [2, 9], [6, 7], [6, 9]]

1.5 Funciones que trabajan sobre listas

sage: len([x for x in divisors(21000) if is_square(x)])
4
sage: any([is_prime(x) for x in srange(20,101)])
True
sage: [x for x in srange(20,101) if is_prime(x)]
[23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
sage: [n^2 for n in srange(1,1000) if is_prime(n+1)][2]
16

1.6 Evaluación perezosa

sage: xlista = xsrange(100)
sage: print xlista.next()
0
sage: print xlista.next()
1
sage: acumulador = 0
sage: for j in xsrange(101):
....:     acumulador += j
....: 
sage: acumulador
5050
sage: cuadrados = (x^2 for x in xsrange(10))
sage: sum(cuadrados)
285
sage: primo(2,10,100)
17
def primo(n,x,y):
    ls = srange(x,y)
    primos = [t for t in ls if is_prime(t)]
    return primos[n]
sage: tiempo(primo,2,10,100)
0.015407085418701172
def tiempo(f,n,x,y):
    inicio = time.time()
    f(n,x,y)
    final = time.time()
    return final - inicio
sage: primoP(2,10,100)
17
# 1ª definición
def primoP(n,x,y):
    ls = xsrange(x,y)
    primos = (t for t in ls if is_prime(t))
    for k in srange(n):
        primos.next()
    return primos.next()

# 2ª definición
def primoP2(n,x,y):
    k = n
    for j in xsrange(x,y):
        if is_prime(j):
            k -= 1
            if k == -1:
                break
    return j
sage: tiempo(primo,2,10000,100000)
8.730193138122559

sage: tiempo(primoP,2,10000,100000)
0.00787806510925293

sage: tiempo(primoP2,2,10000,100000)
0.006257057189941406

1.7 Combinación de generadores y flujos

def tiene_especiales(x,y):
    return any([is_prime(2*k+1) for k in srange(x,y)])
def tiempo(f,x,y):
    inicio = time.time()
    f(x,y)
    final = time.time()
    return final - inicio
def tiene_especialesP(x,y):
    return any(is_prime(2*k+1) for k in xsrange(x,y))
sage: tiempo(tiene_especiales,10000,100000)
9.206285953521729

sage: tiempo(tiene_especialesP,10000,100000)
0.0020439624786376953

1.8 Ejercicios

sage: raices(1,3,2)
[-1, -2]
sage: raices(1,-2,1)
[1]
sage: raices(1,2,3)
Exception: No tiene raices reales
def raices(a,b,c):
    d = b^2-4*a*c
    e = sqrt(d) 
    if d > 0:
        return [(-b+e)/(2*a), (-b-e)/(2*a)]
    elif d == 0: 
        return [(-b+e)/(2*a)]
    else:
        raise Exception, "No tiene raices reales"
sage: restos([1,3,7,13,21,31,43,57,73,91],5)
1 3 2 3 1 1 3 2 3 1
def restos(xs,n):
    for x in xs:
        print x % n,
sage: multiplos([1,3,7,13,21,31,43,57,73,91],3)
3 21 57
def multiplos(xs,n):
    for x in xs:
        if x % n == 0:
            print x,
sage: divisores([1,3,7,13,21,31,43,57,73,91])
Divisores de 1: 1
Divisores de 3: 1 3
Divisores de 7: 1 7
Divisores de 13: 1 13
Divisores de 21: 1 3 7 21
Divisores de 31: 1 31
Divisores de 43: 1 43
Divisores de 57: 1 3 19 57
Divisores de 73: 1 73
Divisores de 91: 1 7 13 91
def divisores(xs):
    for x in xs:
        print "Divisores de %d:"%x,
        for k in srange(1,x+1):
            if x % k == 0:
                print k,
        print
sage: sumaInversos(1)
La suma es 1.500000 y el número de sumandos es 2
sage: sumaInversos(2)
La suma es 2.083333 y el número de sumandos es 4
sage: sumaInversos(3)
La suma es 3.019877 y el número de sumandos es 11
sage: sumaInversos(10)
La suma es 10.000043 y el número de sumandos es 12367
def sumaInversos(n):
    k = 0
    suma = 0
    while suma <= n:
        k = 1 + k
        suma = suma + 1/k
    print "La suma es %f y el número de sumandos es %d"%(suma,k)

Definir la función raiz tal que raiz(a,e) es la raíz cuadrada de n, con un error menor que e calculada mediante al algoritmo de Herón. Por ejemplo.

sage: raiz(49,0.1)
7.00140647524394
def raiz(a,e):
    t = a
    while (abs(t**2-a) >= e):
        t = (t+a/t)/2
    return n(t)
sage: collatz(7)
[7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]
def collatz(n):
    s = [n]
    while (n != 1):
        if is_even(n):
            n = n//2
        else:
            n = 3*n+1
        s = s + [n]
    return s
longitud_collatz(7)  ==  17
# 1ª definición (usando collatz)
def longitud_collatz(n):
    return len(collatz(n))

# 2ª definición (sin usar collatz)
def longitud_collatz_2(n):
    x = 1
    while (n != 1):
        if is_even(n):
            n = n//2
        else:
            n = 3*n+1
        x = x + 1
    return x
collatz_mayor(16)  ==  7
collatz_mayor(50)  ==  27
def collatz_mayor(n):
    x = 1
    while (longitud_collatz_2(x) <= n):
        x = x+1
    return x

1.9 Ejercicios