Divisibilidad y congruencia

Documentos relacionados
Ecuaciones Diofánticas

Tema 2: Teorema de estructura de los grupos abelianos finitamente generados.

Semana05[1/14] Relaciones. 28 de marzo de Relaciones

Criterios de divisibilidad y Congruencias

Semana 09 [1/28] Sucesiones. 29 de abril de Sucesiones

un conjunto cuyos elementos denominaremos vectores y denotaremos por es un espacio vectorial si verifica las siguientes propiedades:

ALGEBRA 1- GRUPO CIENCIAS- TURNO TARDE- Enteros

Semana03[1/17] Funciones. 16 de marzo de Funciones

Semana02[1/23] Conjuntos. 9 de marzo de Conjuntos

Algoritmos y programas. Algoritmos y Estructuras de Datos I

Objetivos formativos de Álgebra

Terminaremos el capítulo con una breve referencia a la teoría de cardinales.

PROBLEMAS RESUELTOS DE PREPARACIÓN PARA OPOSICIONES. Problemas 02

Espacios topológicos. 3.1 Espacio topológico

4. Resolución de indeterminaciones: la regla de L Hôpital.

Sobre funciones reales de variable real. Composición de funciones. Función inversa

Unidad 2: Ecuaciones, inecuaciones y sistemas.

Conjuntos, relaciones y funciones Susana Puddu

Base y Dimensión de un Espacio Vectorial

En general, un conjunto A se define seleccionando los elementos de un cierto conjunto U de referencia que cumplen una determinada propiedad.

IN Guía de Problemas Resueltos de Geometría de Programación Lineal v1.0

Matemáticas I: Hoja 1

TEMA 6: DIVISIÓN DE POLINOMIOS RAÍCES MATEMÁTICAS 3º ESO

Parciales Matemática CBC Parciales Resueltos - Exapuni.

Anillo de polinomios con coeficientes en un cuerpo

Conjuntos Los conjuntos se emplean en muchas áreas de las matemáticas, de modo que es importante una comprensión de los conjuntos y de su notación.

1. NÚMEROS PRIMOS Y COMPUESTOS.

EJERCICIOS. 7.3 Valor de un polinomio para x = a. Por lo tanto: para determinar expresiones

Problemas de Espacios Vectoriales

Coordinación de Matemática I (MAT021) 1 er Semestre de 2013 Semana 1: Lunes 11 Viernes 16 de Marzo. Contenidos

Espacios vectoriales reales.

ELEMENTOS DE GEOMETRÍA. Eduardo P. Serrano

Relaciones. Estructuras Discretas. Relaciones. Relaciones en un Conjunto. Propiedades de Relaciones en A Reflexividad

Tema 7: Geometría Analítica. Rectas.

Enunciados de los problemas (1)

El Teorema Fundamental del Álgebra

Bases Matemáticas para la Educación Primaria. Guía de Estudio. Tema 3: Números racionales. Parte I: Fracciones y razones Números racionales

Recordemos que utilizaremos, como es habitual, la siguiente notación para algunos conjuntos de números que son básicos.

Identificación de inecuaciones lineales en los números reales

Materia: Matemática de Octavo Tema: Conjunto Q (Números Racionales)

Tema 7. El sistema de clases

= 310 (1 + 5) : 2 2 = = = 12 ( 3) ( 5) = = 2 = ( 4) + ( 20) + 3 = = 21

Algebra lineal y conjuntos convexos

Introducción. El uso de los símbolos en matemáticas.

Consideramos dos líneas. Hay tres formas de que las dos pueden interactuar:

Continuación Números Naturales:

Capítulo 4. Inecuaciones. M.Sc. Alcides Astorga M., Lic. Julio Rodríguez S. Instituto Tecnológico de Costa Rica Escuela de Matemática

Aritmética entera. AMD Grado en Ingeniería Informática. AMD Grado en Ingeniería Informática (UM) Aritmética entera 1 / 15

Divisibilidad y congruencias

Espacios Vectoriales Asturias: Red de Universidades Virtuales Iberoamericanas 1

Apuntes de Matemática Discreta 8. Relaciones de Equivalencia

Programación. Tema 8: Tablas Hash. Apuntes elaborados por: Eduardo Quevedo, Aaron Asencio y Raquel López Revisado por: Javier Miranda el????

Es importante recordar el concepto de intervalo abierto notado. (a, b)={x R/a x bt} donde a y b no pertenecen al intervalo abierto

Clase 1: Primalidad. Matemática Discreta - CC3101 Profesor: Pablo Barceló. P. Barceló Matemática Discreta - Cap. 5: Teoría de números 1 / 32

Álgebra Lineal y Estructuras Matemáticas. J. C. Rosales y P. A. García Sánchez. Departamento de Álgebra, Universidad de Granada

PRECALCULO INSTITUTO TECNOLÒGICO DE LAS AMÈRICAS CARRERA DE TECNÓLOGO EN MECATRONICA. Precálculo. Nombre de la asignatura: MAT-001

Lección 2: Funciones vectoriales: límite y. continuidad. Diferenciabilidad de campos

FACTORIZACIÓN GUÍA CIU NRO:

Espacios Vectoriales

Divisibilidad. Rafael F. Isaacs G. * Fecha: 14 de abril de 2005

lasmatemáticas.eu Pedro Castro Ortega materiales de matemáticas

TEMA 1.- POLINOMIOS Y FRACCIONES ALGEBRAICAS

ÁLGEBRA LINEAL I Algunas soluciones a la Práctica 3

Números naturales, principio de inducción

Álgebra de Boole. Retículos.

LA FORMA TRIGONOMETRICA DE LOS NUMEROS COMPLEJOS Y EL TEOREMA DE MOIVRE. Capítulo 7 Sec. 7.5 y 7.6

Espacios Vectoriales. AMD Grado en Ingeniería Informática. AMD Grado en Ingeniería Informática (UM) Espacios Vectoriales 1 / 21

Ejercicio 1: Realiza las siguientes divisiones por el método tradicional y por Ruffini: a)

La aritmética de los relojes

Tema 6: Trigonometría.

MATEMÁTICAS 1º DE ESO

2. El conjunto de los números complejos

Matemáticas 2º E.S.P.A. Pág.1 C.E.P.A. Plus Ultra. Logroño

Sistemas de Ecuaciones Lineales y Matrices

CONCRECIÓN DE LOS CRITERIOS DE EVALUACIÓN Curso: PRIMERO de BACHILLERATO CIENCIAS Asignatura: MATEMÁTICAS I Profesor: ALFONSO BdV

Cuando p(a) = 0 decimos que el valor a, que hemos sustituido, es una raíz del polinomio.

Materia: Matemática de 5to Tema: Método de Cramer. Marco Teórico

Unidad 3: Razones trigonométricas.

Un paquete de problemas de potenciación

TRIÁNGULOS: RELACIONES DE DESIGUALDAD ENTRE SEGMENTOS Y ÁNGULOS

4.2 Números primos grandes. MATE 3041 Profa. Milena R. Salcedo Villanueva

DIVISION: Veamos una división: Tomamos las dos primeras cifra de la izquierda del dividendo (57).

Índice Proposiciones y Conectores Lógicos Tablas de Verdad Lógica de Predicados Inducción

Sesión del día 11 de Marzo del 2011 y tutoría del día 12 de Marzo del 2011

Factorización de polinomios

ESTALMAT-Andalucía Actividades 06/07 Sesión: 19 Fecha: 14/04/07 Título: Aritmética modular Segundo Curso. Aritmética modular

Curso Propedéutico de Cálculo Sesión 2: Límites y Continuidad

SCUACAC026MT22-A16V1. SOLUCIONARIO Ejercitación Generalidades de números

Teorema del valor medio

UNIDAD DIDÁCTICA 6: Trigonometría

Conectados con el pasado, proyectados hacia el futuro Plan Anual de Matemática II Año PAI VII Grado

NÚMEROS COMPLEJOS: C

PAUTA AUXILIAR Nº4. 1. Sean los puntos,,. Pruebe que no son colineales y encuentre la ecuación

Coordenadas de un punto

b) Sea una relación de equivalencia en A y una operación en A. Decimos que y son compatibles si a b a c b c y c a c b para todo a, b, c A

Proyecto. Tema 6 sesión 2: Generación de Rectas, Circunferencias y Curvas. Geometría Analítica. Isidro Huesca Zavaleta

Ámbito Científico-Tecnológico Módulo III Bloque 4 Unidad 6 Eres mi semejante?

Espacios Vectoriales

Estándares de Contenido y Desempeño, Estándares de Ejecución y Niveles de Logro Marcado* MATEMÁTICA

Coordinación de Matemática I (MAT021) 1 er Semestre de 2013 Semana 7: Lunes 22 - Viernes 27 de Abril. Contenidos

Transcripción:

de los ejercicios de la clase 8 Divisibilidad y congruencia Taller de Álgebra I Segundo cuatrimestre de 2016 Introducción A continuación les presentamos algunas soluciones para los ejercicios de la clase 8 del Taller, que buscan aprovechar lo que venimos aprendiendo de Haskell para ejercitar los conceptos de divisibilidad de números enteros. Muchos de estos ejercicios se caracterizan por el hecho de que gran parte de su resolución consiste en obtener y demostrar algunos resultados matemáticos. Esto resulta sumamente interesante, porque nos muestra cómo en muchas ocasiones necesitamos del auxilio de la matemática para comprobar que cierto programa que elaboramos efectivamente produce el resultado que esperamos. Divisibilidad Algoritmo de la división Implementar la función division :: Integer -> Integer -> (Integer, Integer) division a d debe funcionar para a 0, d > 0, y no se pueden usar div, mod ni (/). El teorema del algoritmo de la división afirma la existencia de cociente y resto enteros para la división entre dos números enteros. Más formalmente, dados a, d Z, con d 0, asegura que existen q, r Z, tales que a = q d + r, y 0 r < d. La demostración del teorema es constructiva y presenta un algoritmo, que podemos transformar sencillamente en un programa en Haskell para encontrar estos números. A continuación, exponemos la solución para el caso particular en que a 0 y d > 0. Queda como ejercicio extender la misma al caso general en que a es un entero cualquiera y d 0. La idea, como siempre que hacemos recursión, es resolver nuestro problema a partir de la solución de un caso que sea, en algún sentido, más sencillo. De esta forma, buscamos reducir todas las instancias posibles a un subconjunto de casos que podemos resolver de forma ad hoc, sin hacer una llamada recursiva: nuestros casos base. Para este problema, los casos base serán todos aquellos en que 0 a < d. Si esto sucede, podemos tomar q = 0 y r = a, y como a = 0 d + a, tenemos resuelta la división. En caso contrario, debe ser que a d, y por lo tanto, a d 0. Supongamos que sabemos resolver la división entre (a d) y d; es decir, tenemos q y r tales que (a d) = q d + r, y 0 r < d. Entonces, a = q d + r + d = (q + 1) d + r, por lo que q = q + 1, r = r funcionan como cociente y resto de la división. La resolución que acabamos de esbozar puede llevarse a un programa en Haskell de la siguiente manera: division :: Integer - > Integer - > ( Integer, Integer ) division a d a < d = (0, a) a >= d = ( fst ( division (a - d) d) + 1, snd ( division (a - d) d)) 1

Podemos ver que division (a - d) d aparece dos veces en la misma línea de nuestra función. Esto no solo hace que nuestro código sea más difícil de leer, y más propenso a contener errores, sino que provoca que la computadora compute innecesariamente este resultado dos veces. Una solución a estos problemas es usar la palabra clave where. Empleando, además, pattern matching, podemos obtener este código: division :: Integer - > Integer - > ( Integer, Integer ) division a d a < d = (0, a) a >= d = (q + 1, r) where (q, r) = division (a - d) d Divisores y números primos (a) Implementar la función divparcial :: Integer -> Integer -> [Integer] divparcial n m debe funcionar bien siempre que 0 < m n. (b) Utilizando divparcial, programar divisores :: Integer -> [Integer] (c) Utilizando divisores, programar esprimo :: Integer -> Bool Dados dos números enteros k, n, decimos que k divide a n (k n), o que k es un divisor de n, si existe q Z tal que n = q k. Queremos escribir un programa en Haskell que, dado n > 0, encuentre todos sus divisores positivos; en otras palabras, el conjunto divisores(n) = {k Z 1 k n y k n} Cuando queremos resolver este problema de forma recursiva, nos encontramos con un inconveniente: en general, conocer los divisores de (n 1) (o de un número cualquiera menor que n) no nos dice mucho acerca de los divisores de n. Debemos, entonces, buscar una forma de simplificar el problema que no consista en modificar el valor de n. Una técnica que suele funcionar en esta situación es agregar a la función otro parámetro sobre el que sí resulte útil hacer recursión. Esto quiere decir que vamos a resolver un caso más general del problema, para luego quedarnos con el caso particular que nos resulta útil. Una manera de hacer esto es construir la lista de divisores de forma parcial; es decir, dado un valor 0 < m n, obtener el conjunto divparcial(n, m) = {k Z 1 k m y k n} Podemos observar que, si conocemos todos los divisores de n hasta m 1, es fácil obtener todos los divisores hasta m: basta con verificar si m divide o no a n, y en función de esto, agregarlo o no a la lista. Como definimos divparcial para 0 < m, esto no funciona si m = 1; pero este es un caso que podemos resolver sencillamente, dado que el único valor que podría pertenecer a la lista es 1, que es siempre un divisor de n para todo n > 0. Así, considerando este último caso como caso base, podemos escribir {1} si m = 1 divparcial(n, m) = {m} divparcial(n, m) si m > 1 y m n divparcial(n, m) si m > 1 y m n lo cual puede traducirse en el siguiente programa en Haskell: divparcial :: Integer - > Integer - > [ Integer ] divparcial n 1 = [1] divparcial n m mod n m == 0 = m : divparcial n ( m - 1) otherwise = divparcial n ( m - 1) 2

Ahora, podemos utilizar esta función para hallar todos los divisores positivos de un número n, ya que todos ellos son menores o iguales que n; basta con tomar m = n. Así, obtenemos: divisores :: Integer - > [ Integer ] divisores n = divparcial n n Por último, decimos que un entero p > 1 es primo si ningún natural k con 1 < k < p divide a p. Como 1 y p siempre son divisores de p, esto equivale a decir que p tiene exactamente dos divisores positivos. Por lo tanto, podemos determinar fácilmente si un número p > 1 es primo a partir de la lista de sus divisores: esprimo :: Integer - > Bool esprimo p = length ( divisores p) == 2 Algoritmo de Euclides (a) Programar la función mcd :: Integer -> Integer -> Integer que utilice el algoritmo de Euclides calcule el máximo común divisor entre dos números. mcd a b debe funcionar siempre que a > 0, b 0. (b) Programar la función euclides :: Integer -> Integer -> (Integer, Integer) que utilice el algoritmo de Euclides extendido para obtener dos valores (s, t) tales que (a : b) = sa + tb. El algoritmo de Euclides es un procedimiento antiquísimo (data de alrededor del 300 a.c.), extremadamente simple y eficiente, para encontrar el máximo común divisor entre dos números a y b, que notaremos (a : b). Se basa en que dados a, b Z, si tomamos k Z un entero cualquiera, se cumple (a : b) = (a + k b : b) Si q y r son el cociente y el resto de la división de a por b, tenemos que a = q b + r, de donde r = a q b, y entonces (a : b) = (a q b : b) = (r : b) = (b : r) La idea es aplicar este procedimiento repetidas veces, y es posible ver que siempre se llegará a un caso en el que el valor de r será 0. Así, podemos resolver nuestro problema con una formulación recursiva, tomando como casos base aquellos en que b = 0. A estos casos los podemos resolver directamente, ya que se cumple que (a : 0) = a. Es interesante notar que, si inicialmente b > a, el primer paso del algoritmo invierte estos valores, dejando b > a, propiedad que se mantiene durante el resto del procedimiento. Por ejemplo, los pasos para calcular (30 : 48) son: a b Procedimiento 30 48 Dividimos 30 por 48, q = 0, r = 30 48 30 Dividimos 48 por 30, q = 1, r = 18 30 18 q = 1, r = 12 18 12 q = 1, r = 6 12 6 q = 2, r = 0 6 0 Obtenemos 6 como resultado La escritura de este algoritmo en Haskell es bastante directa. Podemos aprovechar la función division que programamos anteriormente, con lo cual el resultado es: mcd :: Integer - > Integer - > Integer mcd a 0 = a mcd a b = mcd b r where ( q, r) = division a b 3

Una variante interesante de este algoritmo es el llamado algoritmo de Euclides extendido, que nos permite, mediante una pequeña adaptación, obtener dos números enteros s y t tales que s a + t b = (a : b). El caso base es similar al algoritmo original: si b = 0, tenemos que (a : b) = (a : 0) = a = 1 a + 0 b. Es decir, basta con tomar s = 1 y t = 0. Para el caso recursivo, sean q y r el cociente y el resto de la división entera entre a y b, y asumamos que tenemos dos números s y t tales que (b : r) = s b + t r. Esto implica que (a : b) = (b : r) = s b + t r y, como a = q b + r, es decir, r = q b a, podemos deducir que (a : b) = s b + t (a q b) = t a + (s q) b lo cual implica que el problema queda resuelto si tomamos s = t y t = (s q). Una posible implementación de esto en Haskell, usando pattern matching y la palabra clave where para hacer el código más legible, es la siguiente: euclides :: Integer - > Integer - > ( Integer, Integer ) euclides a 0 = (1, 0) euclides a b = ( t, s - t * q) where ( s, t) = euclides b r ( q, r) = division a b Clases de congruencia Los ejercicios que siguen giran en torno al concepto de clases de congruencia. Una clase de congruencia será para nosotros un subconjunto de los números enteros, que puede ser: 1. El conjunto vacío ( ). 2. Un conjunto de la forma P = {p Z p a (mód b)}, donde a y b son números enteros. En el caso de las clases de congruencia no vacías, podemos intepretarlas como los conjuntos formados por todos los enteros que pueden obtenerse a partir de a dando una cantidad entera de saltos de tamaño b, ya sea hacia adelante o hacia atrás. Por este motivo, diremos que a es la base y b es el salto de la clase de congruencia P. Estos dos valores son los que utilizaremos en Haskell para representar a una clase de congruencia no vacía. Notar que, si bien se trata de conjuntos que tienen infinitos elementos, podemos representarlos a todos ellos mediante un par de valores bien elegidos. En Haskell, representaremos las clases de congruencia mediante el siguiente tipo de datos. data ClaseCongr = Vacio CongruentesA Integer Integer deriving Show Como siempre, una valor del tipo de datos solo almacena el constructor con el que se lo creó y los parámetros que se le pasaron al mismo. Es nuestra responsabilidad como programadores, a la hora de realizar funciones que utilicen el tipo, interpretar y darle un significado a estos valores. Para resolver los ejercicios utilizaremos estas dos funciones, que son sumamente sencillas y cuya implementación queda como ejercicio: multiplo :: Integer -> Integer -> Bool, que determina si el primero de sus argumentos es múltiplo del segundo (debe funcionar correctamente cuando su segundo argumento es 0). congruentes :: Integer -> Integer -> Integer -> Bool, que verifica si sus dos primeros argumentos son congruentes módulo el tercero, asumiendo que el tercer argumento es distinto de cero. Ejercicio 1 Programar la función pertenece :: Integer -> ClaseCongr -> Bool, que determine si un entero forma parte de una clase de congruencia. 4

Por ejemplo: pertenece 13 Vacio False pertenece 13 (CongruentesA 5 4) True Evaluar la pertenencia de un número k Z a una clase de congruencia es sencillo, y podemos hacerlo considerando estos dos casos: 1. Si la clase es el conjunto vacío, es claro que k no pertenece a ella. 2. Si la clase es del tipo {p Z p a (mód b)}, debemos verificar si k cumple esta condición, para lo cual utilizaremos la función congruentes. Traduciendo esto a un programa en Haskell: pertenece :: Integer - > ClaseCongr - > Bool pertenece k Vacio = False pertenece k ( CongruentesA a b) = congruentes k a b Ejercicio 2 Programar la función incluido :: ClaseCongr -> ClaseCongr -> Bool, que dadas dos clases de congruencia P 1 y P 2, determine si P 1 P 2. Por ejemplo: incluido (CongruentesA 4 6) (CongruentesA 10 3) True Hay dos casos de este problema que son sumamente sencillos de resolver: La clase de congruencia vacía está incluida trivialmente en cualquier clase de congruencia. Ninguna clase de congruencia no vacía está incluida en la clase de congruencia vacía. Consideremos, entonces, dos clases de congruencia no vacías: P 1 = {p Z p a 1 (mód b 1 )} P 2 = {p Z p a 2 (mód b 2 )} Dado un elemento de P 1, es condición necesaria para que P 1 P 2 que dicho elemento pertenezca a P 2. Como a 1 P 1, entonces, deberá pasar que a 1 P 2, es decir, a 1 a 2 (mód b 2 ). Si esto no sucede, es claro que P 1 P 2. Supongamos que a 1 P 2 ; lo que quisiéramos ver es en qué casos sucede que para todo elemento p P 1 se cumple p P 2. Veamos que esto pasa si y solo si b 1 (el salto existente entre elementos de P 1 ) es múltiplo de b 2 (el salto entre elementos de P 2 ): ( ) Como cualquier elemento de P 1 es un elemento de P 2, en particular a 1 y a 1 + b 1 pertenecen a P 2. Esto quiere decir que a 1 a 2 (mód b 2 ) y a 1 + b 1 a 2 (mód b 2 ), y por lo tanto, a 1 a 1 + b 1 (mód b 2 ). Dicho de otro modo, b 2 (a 1 + b 1 ) a 1 = b 1, o sea, b 1 es múltiplo de b 2. ( ) Consideremos un elemento p P 1 ; entonces, p a 1 (mód b 1 ). Como b 2 b 1, esto implica que p a 1 (mód b 2 ), y como a 1 a 2 (mód b 2 ), tenemos que p a 2 (mód b 2 ), y por lo tanto, p P 2. Resumiendo lo anterior, resulta que P 1 P 2 si y solo si a 1 a 2 (mód b 2 ), y además b 1 es múltiplo de b 2. A partir de esto, podemos programar la función incluido en Haskell de esta forma: 5

incluido :: ClaseCongr - > ClaseCongr - > Bool incluido Vacio _ = True incluido _ Vacio = False incluido ( CongruentesA a1 b1) ( CongruentesA a2 b2) = congruentes a1 a2 b2 && multiplo b1 b2 Ejercicio 3 Implementar la función show de la clase ClaseCongr, para que las clases de congruencia se muestren de esta forma: Prelude > CongruentesA 3 8 {a en Z a = 3 ( mod 8)} Prelude > {} Vacio Para mostrar nuestras clases de congruencia por pantalla, la función show que tenemos que definir deberá tomar un elemento de tipo ClaseCongr y devolver un String. Dado que el tipo ClaseCongr tiene dos constructores, tendremos que hacer pattern matching y definir cómo se comporta la función show para cada uno de ellos. Esto quiere decir que el esqueleto del código que vamos a usar para hacer que ClaseCongr sea instancia de Show será algo de esta pinta: instance Show ClaseCongr where show Vacio =... show ( CongruentesA a b) =... eliminando, además, la declaración deriving Show de la definición del tipo ClaseCongr. El caso de Vacio es sencillo, porque basta con devolver el String "{}". Si, en cambio, la clase de congruencia es del tipo CongruentesA a b, tenemos que construir el String que vamos a devolver a partir de ciertas partes fijas ("{a en Z a = ", " (mod " y ")}"), entre las cuales hay que intercalar los números a y b. Como no podemos concatenar un String y un Integer, primero hace falta convertir estos números a String. Afortunadamente, el tipo Integer es instancia de la clase Show, por lo que contamos con una función show :: Integer -> String que hace este trabajo por nosotros (es la función que utiliza Haskell cada vez que quiere mostrar un número entero por pantalla). Teniendo en cuenta todo lo anterior, podemos completar la función show de ClaseCongr como sigue: instance Show ClaseCongr where show Vacio = "{}" show ( CongruentesA a b) = "{a en Z a = " ++ show a ++ " ( mod " ++ show b ++ ")}" Ejercicio 4 Programar iguales :: ClaseCongr -> ClaseCongr -> Bool, que determina si dos clases de congruencia tienen los mismos elementos. Por ejemplo, iguales (CongruentesA 22 5) (CongruentesA 2 5) True. Hacer que ClaseCongr sea instancia de Eq, utilizando la igualdad programada anteriormente. 6

Para programar iguales, podemos aprovechar la función incluido, que ya programamos anteriormente. Teniendo en cuenta que dos conjuntos P 1 y P 2 son iguales si y solo si P 1 P 2 y P 2 P 1, podemos formular el siguiente programa: iguales :: ClaseCongr - > ClaseCongr - > Bool iguales p1 p2 = incluido p1 p2 && incluido p2 p1 Utilizar esta función para que ClaseCongr sea instancia de Eq es una cuestión meramente sintáctica. instance Eq ClaseCongr where p1 == p2 = iguales p1 p2 Ejercicio 5 Si P 1 y P 2 son clases de congruencia, definimos su suma como P 1 + P 2 = {(a + b) a P 1, b P 2 } Verificar que la suma de dos clases de congruencia es también una clase de congruencia, y programar una función suma :: ClaseCongr -> ClaseCongr -> ClaseCongr que calcule esta suma. Por ejemplo, suma (CongruentesA 3 6) (CongruentesA 2 4) CongruentesA 5 2. Idea matemática Consideremos dos clases de congruencia P 1 y P 2. Si alguna de ellas es vacía, el resultado de la suma tal y como está definida en el enunciado es el conjunto vacío. Analicemos, entonces, los casos en que ambas clases de congruencia son no vacías; es decir: P 1 = {p Z p a 1 (mód b 1 )} P 2 = {p Z p a 2 (mód b 2 )} e intentemos caracterizar la suma de ambas, es decir, el conjunto P + = {(p 1 + p 2 ) p 1 P 1 y p 2 P 2 } Para poder resolver el problema, queremos poder expresar este último conjunto, también, como una clase de congruencia. En otras palabras, queremos determinar si P + es el conjunto vacío, y en caso de que no lo sea, determinar una base y un salto apropiados. Dado que a 1 P 1 y a 2 P 2, es fácil ver que (a 1 + a 2 ) P +. Esto quiere decir que P + no es vacía, y que a = a 1 + a 2 nos sirve como base. En cuanto al salto, vamos a demostrar que es (b 1 : b 2 ), es decir, el m.c.d. entre b 1 y b 2 ; o lo que es lo mismo, que si definimos la clase de congruencia P = {z Z z a 1 + a 2 (mód (b 1 : b 2 ))} entonces P + = P. Vamos a probarlo viendo que se cumple la doble inclusión entre los conjuntos. P + P : Sea p un elemento cualquiera de P +. Luego, p = p 1 + p 2 con p 1 P 1 y p 2 P 2. Como p 1 a 1 (mód b 1 ) y (b 1 : b 2 ) b 1, entonces p 1 a 1 (mód (b 1 : b 2 )). Análogamente, p 2 a 2 (mód (b 1 : b 2 )). Por lo tanto, p = p 1 + p 2 a 1 + a 2 (mód (b 1 : b 2 )), es decir, p P. P P + : Sea p un elemento cualquiera de P. Como p a 1 + a 2 (mód (b 1 : b 2 )), entonces p = a 1 + a 2 + k (b 1 : b 2 ), para algún k Z. Sabemos que (b 1 : b 2 ) puede escribirse como combinación lineal entera de b 1 y b 2. Es decir, existen s, t Z tales que (b 1 : b 2 ) = s b 1 + t b 2. Reemplazando en lo anterior, tenemos que p = a 1 + a 2 + k (s b 1 + t b 2 ) = (a 1 + k s b 1 ) + (a 2 + k t b 2 ). Si tomamos p 1 = a 1 + k s b 1 y p 2 = a 2 + k s b 2, vemos que p 1 P 1, p 2 P 2 y p = p 1 + p 2. Por lo tanto, p P +. Esto demuestra que la suma entre ambas clases de congruencia, es decir, el conjunto definido como P +, es efectivamente la clase de congruencia con base (a 1 + a 2 ) y salto (b 1 : b 2 ). 7

en Haskell Teniendo en cuenta los razonamientos anteriores, la resolución del ejercicio en Haskell es directa, asumiendo que ya tenemos programada la función mcd. suma :: ClaseCongr - > ClaseCongr - > ClaseCongr suma Vacio _ = Vacio suma _ Vacio = Vacio suma ( CongruentesA a1 b1) ( CongruentesA a2 b2) = CongruentesA ( a1 + a2) ( mcd b1 b2) Ejercicio 6 Verificar que la intersección entre dos clases de congruencia (P 1 P 2 ) también es una clase de congruencia, e implementar interseccion :: ClaseCongr -> ClaseCongr -> ClaseCongr, que calcule esta intersección. Idea matemática Al igual que en el caso anterior, si consideramos dos clases de congruencia P 1 y P 2 tales que alguna de ellas es vacía, la intersección resulta trivialmente vacía. Por lo tanto, consideremos dos clases de congruencia no vacías: P 1 = {p Z p a 1 (mód b 1 )} y la intersección de ambas, es decir, el conjunto P 2 = {p Z p a 2 (mód b 2 )} P = P 1 P 2 Tratemos de identificar primero los casos en que esta interesección es vacía. Para que un elemento p esté simultáneamente en P 1 y en P 2, deben existir k 1, k 2 Z tales que p = a 1 + k 1 b 1 = a 2 + k 2 b 2. Es decir: P 1 P 2 ( k 1, k 2 Z) a 1 + k 1 b 1 = a 2 + k 2 b 2 ( k 1, k 2 Z) a 1 = a 2 + k 1 b 1 + k 2 b 2 ( k Z) a 1 = a 2 + k (b 1 : b 2 ) a 1 a 2 (mód (b 1 : b 2 )), donde la anteúltima equivalencia se desprende del resultado del Ejercicio 1, teniendo en cuenta que el conjunto {a 2 + k 1 b 1 + k 2 b 2 k 1, k 2 Z} se puede pensar como la clase de congruencia P + que proviene de la suma de las clases de congruencia P 1 = {z Z z 0 (mód b 1 )} y P 2 = {z Z z a 2 (mód b 2 )}. De esta forma, sabemos que los casos en que P = serán exactamente aquellos en que P 1 y P 2 sean tales que a 1 a 2 (mód (b 1 : b 2 )). Veamos ahora qué pasa cuando la intersección no es vacía. Nos gustaría ver que en estos casos efectivamente tenemos una clase de congruencia, y encontrar una base y un salto apropiados. Supongamos que P no es vacía, y tomemos un elemento p 0 P. Qué pasa si ahora miramos cualquier elemento p P, y calculamos su diferencia con p 0? 1 Tengamos en cuenta que tanto p 0 como p pertenecen a P 1. Luego, p 0 a 1 p (mód b 1 ) p 0 p 0 (mód b 1 ); es decir, la diferencia entre p 0 y p tiene que ser un múltiplo de b 1. Aplicando el mismo razonamiento, podemos deducir que esta diferencia debe ser también un múltiplo de b 2. Por lo tanto, p 0 p será múltiplo de m = b1 b2 (b, el m.c.m. entre b 1:b 2) 1 y b 2. O sea, dado un elemento p 0 P, cualquier otro elemento p P tendrá que cumplir p p 0 (mód m). Lo anterior, claro, no quiere decir que cualquier número p que cumpla p p 0 (mód m) es necesariamente parte de P. Sin embargo, en este caso, eso resulta ser cierto. Cómo lo probamos? Si p satisface lo anterior, entonces lo podemos escribir como p = p 0 + k m. Por un lado, p 0 a 1 (mód b 1 ), y como b 1 m, k m 0 (mód b 1 ), con lo cual p a 1 (mód b 1 ), y luego p P 1. Análogamente, se puede ver que p P 2. 1 Todavía no sabemos si P tiene más de un elemento, así que no pedimos que p 0 y p sean distintos. 8

Ahora sí sabemos que, dado un elemento p 0 P, podemos afirmar que P = {p Z p p 0 (mód m)}. En otras palabras, la intersección entre P 1 y P 2 es la clase de los enteros congruentes a p módulo m. Aún resta determinar un valor para p, que puede ser cualquier elemento válido de P. Ahora bien, si P es una clase de congruencia de salto m, seguro existirá un entero c, 0 c < m, tal que c P. Como podemos verificar de forma sencilla la pertenencia de un elemento a P (viendo si pertenece a P 1 y P 2 simultáneamente), una solución posible es escribir un programa que revise estos m valores posibles para c, dando así con uno adecuado para tomar como base de la clase de congruencia. en Haskell Asumimos, al igual que en el ejercicio anterior, que ya contamos con la implementación de la función mcd. interseccion :: ClaseCongr - > ClaseCongr - > ClaseCongr interseccion Vacio _ = Vacio interseccion _ Vacio = Vacio interseccion ( CongruentesA a1 b1) ( CongruentesA a2 b2) mod a1 d /= mod a2 d = Vacio otherwise = CongruentesA base m where d = mcd b1 b2 m = div ( b1 * b2) d base = encontrarcongruente a1 b1 a2 b2 0 encontrarcongruente :: Integer - > Integer - > Integer - > Integer - > Integer - > Integer encontrarcongruente a1 b1 a2 b2 n mod a1 b1 == mod n b1 && mod a2 b2 == mod n b2 = n otherwise = encontrarcongruente a1 b1 a2 b2 ( n +1) Ejercicio 7 Implementar tienesolucion :: Integer -> ClaseCongr -> Bool que diga si una ecuación de congruencia tiene solución. Explícitamente, tienesolucion t (CongruentesA a m) True si y solo si la ecuación t x a (mód m) tiene solución. Vamos a asumir que la clase de congruencia que recibimos por parámetro es no vacía, dado que, de lo contrario, el ejercicio no tiene sentido. Es fundamental tener en cuenta que la ecuación de congruencia es equivalente a la ecuación diofántica t x a (mód m) t x m y = a que, como habrán demostrado en las clases teóricas de la materia, tiene solución si y solo si (t : m) a. Esto quiere decir que podemos resolver el ejercicio con el siguiente programa: tienesolucion :: Integer - > ClaseCongr - > Bool tienesolucion t ( CongruentesA a m) = multiplo a ( mcd t m) 9