LENGUAJES DE PROGRAMACIÓN Solución al Trabajo Práctico - Junio de 2015 EJERCICIO 1 Uno de los primeros generadores combinados de números pseudoaleatorios fue el propuesto por Wichmann y Hill en 1982. El generador de Wichmann-Hill emplea los siguientes tres generadores lineales congruenciales para obtener tres números enteros {xi , yi , zi }: xi = (171 · xi−1 ) mód 30269 yi = (172 · yi−1 ) mód 30307 zi = (170 · zi−1 ) mód 30323 de los cuales se obtiene un número pseudoaleatorio ui en el intervalo (0, 1): x yi zi i ui = + + 30269 30307 30323 mód 1 donde i = 1, 2, . . . La semilla de este generador son tres números enteros, {x0 , y0 , z0 }, que deben estar comprendidos entre 1 y 30000. Escriba un programa en C++ que realice las acciones siguientes: 1. Solicitar al usuario que introduzca por consola la semilla del generador. 2. Comprobar que estos tres números están comprendidos entre 1 y 30000. Si no lo están, el programa vuelve a solicitar por consola los números al usuario. LENGUAJES DE PROGRAMACIÓN 3. Empleando el algoritmo de Wichmann-Hill, obtener 100 números pseudoaleatorios e ir almacenándolos en un array de 100 componentes. 4. Mostrar en la consola el contenido del array. Los números deberán mostrarse en formato fijo, con 5 dígitos detrás del punto decimal. 5. Almacenar los números, en el formato anteriormente indicado y formando una única columna (es decir, un número por fila), en un fichero de texto llamado random.txt. 6. Terminar. 2 Dpto. de Informática y Automática, UNED SOLUCIÓN AL TRABAJO PRÁCTICO - JUNIO DE 2015 Solución al Ejercicio 1 // Fichero: random.cpp #include <iostream> #include <cmath> #include <iomanip> #include <fstream> #include <string> const unsigned int N const int c1=171, m1 const int c2=172, m2 const int c3=170, m3 = 100; = 30269; = 30307; = 30323; const std::string nombreFich = "random.txt"; int main() { int Xt, Yt, Zt; // Entrada semilla por consola (enteros entre 1 y 30000) do { std::cout << "Introduzca 3 valores enteros:"<< std::endl; std::cin >> Xt >> Yt >> Zt; } while (Xt<1 ||Xt>30000 ||Yt<1 ||Yt>30000 ||Zt<1 ||Zt>30000); } // Generador de Wichman-Hill double U_RH[N]; for (unsigned int i=0; i<N; i++) { Xt = (c1*Xt) % m1; Yt = (c2*Yt) % m2; Zt = (c3*Zt) % m3; U_RH[i] = fmod( ((double)Xt)/m1 + ((double)Yt)/m2 + ((double)Zt)/m3, 1); } // Muestra resultado en consola for (unsigned int i=0; i<N; i++) { std::cout << "u["<< i << "]: "<< std::fixed << std::setprecision(5) << U_RH[i] << std::endl; } // Apertura fichero para escritura std::ofstream outFich(nombreFich.c_str(), std::ios::out |std::ios::trunc); if (!outFich) { std::cout << "ERROR al abrir fichero"<< std::endl; return 1; } // Graba resultado en fichero for (unsigned int i=0; i<N; i++) { outFich << std::fixed << std::setprecision(5) << U_RH[i] << std::endl; } outFich.close(); return 0; Código 1.1: Solución al Ejercicio 1. Dpto. de Informática y Automática, UNED 3 LENGUAJES DE PROGRAMACIÓN EJERCICIO 2 Escriba un programa en C++ que, a partir de una secuencia de 2 · N números pseudoaleatorios, obtenga N = 45 posiciones aleatorias uniformemente distribuidas sobre una superficie plana rectangular de longitud L = 30 mm y altura A = 2 mm. Para ello, el programa debe realizar las acciones siguientes: 1. El número de posiciones N, así como la longitud (L) y altura (A) de la superficie, son constantes del programa: N = 45, L = 30, A = 2. 2. Abrir para lectura un fichero de texto llamado random.txt, en el cual hay escrita una columna de números pseudoaleatorios. Estos números pueden ser los obtenidos de ejecutar el programa del ejercicio anterior o cualquier otra secuencia de números pseudoaleatorios. 3. Almacenar el contenido completo del fichero en un vector de double llamado randomNums. 4. Si el vector contiene menos de 2 · N componentes, mostrar un mensaje de error y terminar. 5. Operar de la forma siguiente los primeros 2·N números del vector. Por cada pareja de números, {u2i , u2i+1 }, calcular una posición aleatoria {xi , yi } de la forma siguiente: xi = L · u2i yi = A · u2i+1 donde i = 0, . . . , N − 1. 6. Almacenar las posiciones en un fichero de texto llamado posiciones.txt. Deberán almacenarse en dos columnas: en la primera las posiciones xi y en la segunda las correspondientes posiciones yi . Deberán expresarse en formato fijo, con 3 dígitos detrás del punto decimal. 7. Terminar. 4 Dpto. de Informática y Automática, UNED SOLUCIÓN AL TRABAJO PRÁCTICO - JUNIO DE 2015 Solución al Ejercicio 2 // Fichero: posicAleat.cpp #include <iostream> #include <iomanip> #include <fstream> #include <string> #include <vector> const unsigned int N = 45; const double L = 30; const double A = 2; const std::string nombreFichI = "random.txt"; const std::string nombreFichO = "posiciones.txt"; int main() { // Apertura del fichero para lectura std::ifstream inFich(nombreFichI.c_str(), std::ios::in); if (!inFich) { std::cout << "ERROR al abrir fichero"<< std::endl; return 1; } // Lectura de datos del fichero e inclusión en vector std::vector<double> randomNums; while (!inFich.eof()) { double rn; inFich >> rn; randomNums.push_back(rn); } inFich.close(); // Comprobar que el vector contiene al menos 2*N elementos if ( randomNums.size() < 2*N) { std::cout << "Error: el fichero contiene " << randomNums.size() << "elementos y deberia contener al menos " << 2*N << std::endl; return 1; } // Apertura del fichero para escritura std::ofstream outFich(nombreFichO.c_str(), std::ios::out |std::ios::trunc); if (!outFich) { std::cout << "ERROR al abrir fichero"<< std::endl; return 1; } for (unsigned int i=0; i<2*N; i+=2) { outFich << std::fixed << std::setprecision(3) << L*randomNums[i] << "\t"<< A*randomNums[i+1] << std::endl; } outFich.close(); return 0; } Código 1.2: Solución al Ejercicio 2. Dpto. de Informática y Automática, UNED 5 LENGUAJES DE PROGRAMACIÓN EJERCICIO 3 Considérense N = 45 círculos de radio r = 0.5 mm distribuidos aleatoriamente sobre una superficie rectangular de L = 30 mm de longitud y A = 2 mm de altura. Los círculos que se encuentran sobre la superficie se agrupan en conjuntos de la manera siguiente: – Cada círculo debe pertenecer a un único conjunto. – Dos círculos entre los cuales existe solapamiento deben estar en el mismo conjunto. Se considera que existe solapamiento entre dos círculos si la distancia entre sus centros es menor o igual que 2 · r. Escriba un programa en C++ que cumpla las especificaciones siguientes: – El número de círculos (N) y su diámetro (2 · r) deben ser constantes del programa. – El programa debe leer las posiciones de los centros de los N círculos de un fichero de texto llamado posiciones.txt, que contiene dos columnas de números reales expresados en formato fijo, con 3 dígitos detrás del punto decimal. Los números de la primera columna son la coordenada x del centro del círculo y los de la segunda columna la correspondiente coordenada y. Este fichero podría ser, por ejemplo, el obtenido al ejecutar el programa escrito en el ejercicio anterior. – Si el fichero posiciones.txt contiene menos de 2 · N números, el programa debe mostrar un mensaje de error y terminar. – El programa debe agrupar los círculos en conjuntos. – Cada conjunto está compuesto por uno o más círculos. El programa debe escribir en un fichero de texto llamado nElemConj.txt el número de círculos de que consta cada conjunto. Los números se escribirán formando una columna. El orden de escritura de los números es irrelevante. Puede usar en el programa las estructuras de datos que estime convenientes. La figura mostrada a continuación es un ejemplo que pretende ilustrar la clasificación de los círculos en conjuntos. Por simplicidad en este ejemplo se consideran 6 Dpto. de Informática y Automática, UNED SOLUCIÓN AL TRABAJO PRÁCTICO - JUNIO DE 2015 C7 C6 C3 C1 C13 C2 C11 C12 C4 C9 C10 C15 C8 C14 C5 Figura 1.1: Los círculos mostrados en esta figura se clasifican en 5 conjuntos: {C7 }, {C2 , C11 , C9 , C15 }, {C6 , C1 , C13 , C4 , C10 }, {C3 , C12 }, {C8 , C14 , C5 } sólo N = 15 círculos. Los círculos se han etiquetado C1 , . . . , C15 con el propósito de facilitar referirse a ellos al indicar los círculos pertenecientes a cada conjunto. Dados los conjuntos del ejemplo mostrado en la Figura 1.1, la salida del programa debería ser los números siguientes escritos en cualquier orden en una columna: 1, 4, 5, 2, 3. Dpto. de Informática y Automática, UNED 7 LENGUAJES DE PROGRAMACIÓN Solución al Ejercicio 3 // Fichero: circulos.cpp #include <iostream> #include <fstream> #include <string> #include <vector> #include <cmath> const unsigned int N = 45; const double r2 = 2*0.5; const std::string nombreFichI = "posiciones.txt"; const std::string nombreFichO = "nElemConj.txt"; struct Circulo { double xpos; double ypos; int iConjunto; // Campo que indica el conjunto del circulo }; int main() { // Apertura del fichero de posiciones para lectura std::ifstream inFich(nombreFichI.c_str(), std::ios::in); if (!inFich) { std::cout << "ERROR al abrir fichero " << nombreFichI << std::endl; return 1; } // Carga de N circulos en el vector std::vector<Circulo> circulos; while (!inFich.eof() && circulos.size()<N ) { // Coordenadas del centro Circulo c; inFich >> c.xpos >> c.ypos; c.iConjunto = 0; circulos.push_back(c); } inFich.close(); // Comprueba que se hayan leido 2*N valores del fichero if ( circulos.size() < N ) { std::cout << "ERROR: el fichero "<< nombreFichI << " contiene menos de "<< 2*N << " valores"<< std::endl; return 1; } Código 1.3: Solución al Ejercicio 3 (parte inicial). 8 Dpto. de Informática y Automática, UNED SOLUCIÓN AL TRABAJO PRÁCTICO - JUNIO DE 2015 } // Asigna valor al campo iConjunto int nConjunto = 1; for (int i=0; i<circulos.size(); i++) { if (circulos[i].iConjunto == 0) { circulos[i].iConjunto = nConjunto; nConjunto++; } for (int j=i+1; j<circulos.size(); j++) { // Distancia entre los centros de los círculos i y j double d = pow( pow(circulos[i].xpos-circulos[j].xpos,2) + pow(circulos[i].ypos-circulos[j].ypos,2), 0.5); if ( d <= r2 ) { // Los círculos i y j solapan if ( circulos[j].iConjunto != 0 ) { // Si j pertenece a un grupo int iConjuntoAlias = circulos[j].iConjunto; // Todos los circulos del grupo de j pasan a ser del grupo de i for (int k=0; k<circulos.size(); k++) if ( circulos[k].iConjunto == iConjuntoAlias ) circulos[k].iConjunto = circulos[i].iConjunto; } else { // j no pertenece todavía a ningún grupo // El círculo j pasa a ser del grupo del círculo i circulos[j].iConjunto = circulos[i].iConjunto; } } } } // Apertura del fichero para escritura std::ofstream outFich(nombreFichO.c_str(), std::ios::out |std::ios::trunc); if (!outFich) { std::cout << "ERROR al abrir fichero" << nombreFichO << std::endl; return 1; } // Cuenta el número de elementos de cada conjunto y salida for (int iC=1; iC<nConjunto; iC++) { int nElem = 0; for (int i=0; i<circulos.size(); i++ ) if ( circulos[i].iConjunto == iC ) nElem++; if ( nElem > 0 ) outFich << nElem << std::endl; } outFich.close(); return 0; Código 1.4: Solución al Ejercicio 3 (parte final). Dpto. de Informática y Automática, UNED 9 LENGUAJES DE PROGRAMACIÓN EJERCICIO 4 Escriba un programa en C++ que calcule la frecuencia con la que aparece cada número de un determinado conjunto de números naturales. Por ejemplo, dado el conjunto {2, 1, 1, 2, 4, 1, 1, 1}, el programa debe obtener que la frecuencia del número 1 es 5/8 (cinco de los ocho números tienen valor 1), la frecuencia del número 2 es 2/8 (dos de los ocho números tienen valor 2) y la frecuencia del número 4 es 1/8 (uno de los ocho números tiene valor 4). El conjunto de números naturales se encuentra almacenado en un fichero de texto llamado nElemConj.txt. Los números están escritos formando una única columna. El programa debe leer el contenido del fichero, calcular la frecuencia de cada número natural, mostrar el resultado en la consola y terminar. El resultado debe mostrarse en dos columnas: en la primera los números, ordenados crecientemente, y en la segunda su frecuencia, expresada como fracción. En el ejemplo anterior, la salida del programa debería ser: 1 5/8 2 2/8 4 1/8 Solución al Ejercicio 4 // Fichero: histograma.cpp #include <iostream> #include <fstream> #include <string> #include <vector> const std::string nombreFichI = "nElemConj.txt"; struct barraHistograma { int valor; int numVecesAparece; }; Código 1.5: Solución al Ejercicio 4 (parte inicial). 10 Dpto. de Informática y Automática, UNED SOLUCIÓN AL TRABAJO PRÁCTICO - JUNIO DE 2015 int main() { // Apertura del fichero para lectura std::ifstream inFich(nombreFichI.c_str(), std::ios::in); if (!inFich) { std::cout << "ERROR al abrir fichero " << nombreFichI << std::endl; return 1; } // Lectura del fichero y cálculo frecuencia std::vector<barraHistograma> hist; int numTotalValores = 0; while ( !inFich.eof() ) { int num; inFich >> num; if (inFich.eof()) break; numTotalValores++; bool encontrado = false; for ( int i=0; !encontrado && i<hist.size(); i++ ) { if ( hist[i].valor == num ) { hist[i].numVecesAparece++; encontrado = true; } } if ( !encontrado ) { barraHistograma bh = { num, 1 }; hist.push_back(bh); } } inFich.close(); if ( hist.size() > 1 ) { // Ordenación del vector y salida bool desordenado = true; while (desordenado) { desordenado = false; for (int i=0; i<hist.size()-1; i++) { if ( hist[i].valor > hist[i+1].valor ) { desordenado = true; barraHistograma bh = hist[i+1]; hist[i+1] = hist[i]; hist[i] = bh; } } } for (unsigned int i=0; i<hist.size(); i++) std::cout << hist[i].valor << "\t"<< hist[i].numVecesAparece << "/"<< numTotalValores << std::endl; } else { std::cout << "El fichero no contiene numeros" << std::endl; } return 0; } Código 1.6: Solución al Ejercicio 4 (parte final). Dpto. de Informática y Automática, UNED 11