Autor Tema: Java ejemplo hilos (Thread) con interfaz gráfica Swing calcular tiempos proceso  (Leído 980 veces)

fabricioom

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 5
    • Ver Perfil
Hola mi gente, pido ayuda con un problema que he avanzado algo pero ya no sé cómo
implementarlo me pidieron esto:

Crear la data de 2 millones de registros con datos:

VENTA (codi,ciudad,fecha,valor)

fecha entre año 2016 a 2020, valor entre 1 a 5000

- Hilo1: Suma de valores>3000 de los años 2017, 2018, 2019
- Hilo2: De los años 2018, 2019, 2020 cual es el valor menor y mayor

Cada hilo muestre los tiempos de proceso y resultados planteados.

Para esto me dieron 2 proyectos para modificarlos y a la vez implementar lo que me han pedido y estos son los 2 proyectos:


PROYECTO 1

Código: [Seleccionar]
Proyecto 1
Consta de: PruebaHilo1java, clase1.java y un formulario
//Prueba-hilo1
//Guardar 10000 registros: codigo,valor,fecha


package prueba.hilo1;

public class PruebaHilo1 {

public static void main(String[] args) {
 formu1 formu = new formu1();formu.setVisible(true);
 }       
 
}

La clase 1
package prueba.hilo1;

import javax.swing.*;
import java.io.*;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.Random;

public class clase1 {
   
File file1;
FileWriter archi1;
PrintWriter archi2;
FileReader archi3;
BufferedReader archi4;

String carpe="c:/prueba";
String archivo1="archivo1.txt";
String archivo2="archivo2.txt";
String archivofin="";

String cadena1;
String codi;
String nom;
float valo;

//limpia entrada
public void limpia_entrada() {
formu1.z1.setText("");formu1.a1.setText("");
}

//Generar1 archivo1.txt 1 MILLON DE REGISTROS CLIENTE (codigo,valor,fecha)
public void generar1() throws IOException {
file1=new File(carpe+"/"+archivo1);
archi1 = new FileWriter(file1,true);archi2 = new PrintWriter(archi1);

//Genera 1 millon registros 10000 clientes y cada cliente 100 registro

int a=0;
for (int i = 0; i < 10000; i++) {a++;
for (int j = 0; j < 100; j++) {
   
//Generar codigo U1, U2, U3, U4... (100 de cada uno)
codi="U"+a;

//Genera numero entre 1 y 1000
valo=(float)(Math. random()*1000+1);   

//Genera fecha entre 2000-01-01 y 2010-12-31
long fechini=Timestamp.valueOf("2000-01-01 00:00:00").getTime();
long fechfin=Timestamp.valueOf("2010-12-31 00:00:00").getTime();
long difech=fechfin-fechini+1;
long fecha1=(long)(Math. random()* difech+fechini);   
Date fecha = new Date(fecha1);

//Guarda en archivo plano: codigo;valor;fecha
archi2.write(codi+";"+valo+";"+fecha+"\n");} } 
JOptionPane.showMessageDialog(null,"Archivo generado");archi1.close();

}

//Generar1 archivo2.txt 1 MILLON DE REGISTROS CLIENTE (codigo,nombres,ciudad)
public void generar2() throws IOException {
file1=new File(carpe+"/"+archivo2);
archi1 = new FileWriter(file1,true);archi2 = new PrintWriter(archi1);

//Genera 1 millon registros 10000*100
int a=0;
for (int i = 0; i < 10000; i++) {

a++;
Random b = new Random();
//Genera ciudad
String[] ciu = {"Manta","Portoviejo","Chone","Montecristi","Jipijapa"};
int e1 = b.nextInt(ciu.length);

for (int j = 0; j < 100; j++) {

//Generar codigo U1, U2, U3, U4... (100 de cada uno)
//Generar nombre AZ1, AZ2, AZ3...

codi="U"+a;nom="AZ"+a;
//Guarda en archivo plano: codigo;nombres;ciudad
archi2.write(codi+";"+nom+";"+ciu[e1]+"\n");} } 
JOptionPane.showMessageDialog(null,"Archivo generado");archi1.close();

}


//Consulta individual1 Archivo.txt por el primer campo = CODIGO
public void consu_individual1() throws IOException  {
archivofin=archivo1;consu_individual();}

//Consulta individual2 Archivo.txt por el primer campo = CODIGO
public void consu_individual2() throws IOException  {
archivofin=archivo2;consu_individual();}

public void consu_individual() throws IOException  {
formu1.z1.setText("");
file1=new File(carpe+"/"+archivofin);
archi3 = new FileReader(file1);archi4 = new BufferedReader(archi3);
cadena1=archi4.readLine();

//De cadena codigo;valor;fecha extraer datos de CODIGO
String codi=formu1.a1.getText();
while(cadena1!=null){String dato="";
for (int i = 0; i < cadena1.length()-1; i++) {
String car=String.valueOf(cadena1.charAt(i));       
if (";".equals(car)) {break;} else {dato=dato+car;}}
if (codi.equals(dato)) {formu1.z1.append(cadena1+"\n");}
cadena1=archi4.readLine();}}
//   U1;34;12-12-2000

//Consulta general1 de Archivo1.txt
public void consu_general1() throws IOException  {
archivofin=archivo1;consu_general();}

//Consulta general1 de Archivo2.txt
public void consu_general2() throws IOException  {
archivofin=archivo2;consu_general();}

public void consu_general() throws IOException  {
formu1.z1.setText("");
file1=new File(carpe+"/"+archivofin);
archi3 = new FileReader(file1);archi4 = new BufferedReader(archi3);
cadena1=archi4.readLine();
while(cadena1!=null){
if (cadena1!=null) {formu1.z1.append(cadena1+"\n");}
cadena1=archi4.readLine();}}

//Crear carpeta/archivo Archivo1.txt
public void crear_carpe_archi1() throws IOException  {
archivofin=archivo1;carpe_archi();}

//Crear carpeta/archivo Archivo2.txt
public void crear_carpe_archi2() throws IOException  {
archivofin=archivo2;carpe_archi();} 

public void carpe_archi() throws IOException  {
file1 = new File(carpe);
if (!file1.exists()) {
   if (file1.mkdirs()) {JOptionPane.showMessageDialog(null,"Carpeta creada");}}
   else {JOptionPane.showMessageDialog(null,"Carpeta ya existe");}
file1 = new File(carpe+"/"+archivofin);
if (!file1.exists()) {
if (file1.createNewFile()) {JOptionPane.showMessageDialog(null,"Archivo.txt creado");}}
  else {JOptionPane.showMessageDialog(null,"Archivo ya existe");} }

}



Y el formulario.



PROYECTO 2

El proyecto 2 lo adjunto en un archivo de texto plano (bloc de notas) porque son 11 clases (Consta de : PruebaHilo2.java, archivo.java, clase1.java, dos_hilos1.java, dos_hilos2.java, sinhilo.java, tres_hilo1.java, tres_hilo2.java, tres_hilo3.java, un_hilo.java y un formulario.)

Y lo que he hecho hasta ahora es solo crear 2 hilos como me lo pide pero sin la función de cada hilo asi:

Código: [Seleccionar]
package prueba.hilo3;

import prueba.hilo3.formu1;
import java.io.*;

public class archivo {
   
File file1;
FileWriter archi1;
PrintWriter archi2;
FileReader archi3;
BufferedReader archi4;

String carpe="c:/prueba";
String archivo1="archivo1.txt";
String archivo2="archivo2.txt";
String archivofin="";
String cadena1;

void declara_archivo() throws FileNotFoundException, IOException {
formu1.z1.setText("");formu1.z1.append("Procesando...\n");
file1=new File(carpe+"/"+archivofin);
archi3 = new FileReader(file1);archi4 = new BufferedReader(archi3);
cadena1=archi4.readLine();
   
}
}


package prueba.hilo3;

import java.io.FileNotFoundException;
import java.io.IOException;
import prueba.hilo3.formu1;
/**
 *
 * @author 59399
 */
public class clase1 {
    public void limpia_entrada() {
formu1.z1.setText("");}
   
//HILO1:CALCULA SUMA DE VALORES >3000 DE LOS AÑOS 2017, 2018, 2019
void unhilo() {
  un_hilo hilo1=new un_hilo();hilo1.start();     
}
 
//CALCULA HILO2: DE LOS AÑOS 2018, 2019, 2020 CUAL ES EL VALOR MENOR Y MAYOR
void doshilo() {
 dos_hilo1 hilo1=new dos_hilo1();
 hilo1.start();}
   
}

//DOS HILO1: Cuenta valor entre 1 y 500 de 1 millón registros CLIENTE(codigo,valor,fecha)

package prueba.hilo3;

import prueba.hilo3.formu1;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;


public class dos_hilo1  extends Thread { //heredado Thread{

@Override  //sobreescribe metodo de clase padre   
public void run(){ //Ejecuta proceso
long tiempoini= System.currentTimeMillis();
float c1=0,c2=0,c3=0,c4=0,c5=0,c6=0;

archivo vari=new archivo();
    try {
         vari.archivofin=vari.archivo2;vari.declara_archivo();
    } catch (IOException ex) {
        Logger.getLogger(dos_hilo1.class.getName()).log(Level.SEVERE, null, ex);
    }

//Busca dato 3 dato de CLIENTE (codigo;nombre;ciudad)
//y cuenta ciudad de MANTA
while(vari.cadena1!=null){String dato="";int a=0;
for (int i = 0; i < vari.cadena1.length(); i++) {
String car=String.valueOf(vari.cadena1.charAt(i));       
if (";".equals(car)) {a++;}
else if (a==2) {if (!";".equals(car)) dato=dato+car;}}
c1++;
if ("Manta".equals(dato)) {c2++;}
if ("Portoviejo".equals(dato)) {c3++;}
if ("Montecristi".equals(dato)) {c4++;}
if ("Jipijapa".equals(dato)) {c5++;}
if ("Chone".equals(dato)) {c6++;}

    try {
        vari.cadena1=vari.archi4.readLine();
    } catch (IOException ex) {
        Logger.getLogger(dos_hilo1.class.getName()).log(Level.SEVERE, null, ex);
    }

}

float ma=0,me=0;
if (c2>c3&&c2>c4&&c2>c5&&c2>c6) {ma=c2;}
if (c3>c2&&c3>c4&&c3>c5&&c3>c6) {ma=c3;}
if (c4>c2&&c4>c3&&c4>c5&&c4>c6) {ma=c4;}
if (c5>c2&&c5>c3&&c5>c4&&c5>c6) {ma=c5;}
else {ma=c6;}
if (c2<c3&&c2<c4&&c2<c5&&c2<c6) {me=c2;}
if (c3<c2&&c3<c4&&c3<c5&&c3<c6) {me=c3;}
if (c4<c2&&c4<c3&&c4<c5&&c4<c6) {me=c4;}
if (c5<c2&&c5<c3&&c5<c4&&c5<c6) {me=c5;}
else {me=c6;}

formu1.z1.append("Total registros: "+String.format("%5.0f",c1)+"\n");
formu1.z1.append("Promedio: "+String.format("%5.2f",c1/5)+"\n");
formu1.z1.append("Mayor cantidad de ciudad: "+String.format("%5.0f",ma)+"\n");
formu1.z1.append("Menor cantidad de ciudad: "+String.format("%5.0f",me)+"\n");

long tiempofinal = System.currentTimeMillis();
formu1.z1.append("Tiempo total: "+(tiempofinal-tiempoini)+" MiliSeg"+"\n");
}
     
   
}

//UN HILO: total de valor de 1 millón registros CLIENTE(codigo,valor,fecha)
package prueba.hilo3;

import prueba.hilo3.formu1;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import prueba.hilo3.archivo;

public class un_hilo extends Thread  { //heredado Thread
 
@Override  //sobreescribe metodo de clase padre   
public void run(){ //Ejecuta proceso
long tiempoini= System.currentTimeMillis();
float sum=0;float cuenta=0;

archivo vari=new archivo();
try {
        vari.archivofin=vari.archivo1;vari.declara_archivo();
    } catch (IOException ex) {
        Logger.getLogger(un_hilo.class.getName()).log(Level.SEVERE, null, ex);
    }

//Busca dato2 VALOR de CLIENTE (codigo;valor;fecha) y realiza sumatoria de valor
while(vari.cadena1!=null){String dato="";int a=0;
for (int i = 0; i < vari.cadena1.length()-1; i++) {
String car=String.valueOf(vari.cadena1.charAt(i));       
if (";".equals(car)) {a++;}
if (a==1) {{if (!";".equals(car)) dato=dato+car;}}
if (a==2) {break;} }
cuenta++;sum=sum+Float.valueOf(dato);

    try {
        vari.cadena1=vari.archi4.readLine();
    } catch (IOException ex) {
        Logger.getLogger(un_hilo.class.getName()).log(Level.SEVERE, null, ex);
    }
}

formu1.z1.append("Total registros: "+String.format("%.0f",cuenta)+"\n");
formu1.z1.append("Total valor: "+String.format("%20.2f",sum)+"\n");
long tiempofinal = System.currentTimeMillis();
formu1.z1.append("Tiempo total: "+(tiempofinal-tiempoini)+" MiliSeg"+"\n");}
}


Y el respectivo formulario. No sé cómo combinar de los 2 proyectos lo que me pide, habrá que implementarlo. Hasta ahí llegué, espero que me respalden muchachos, unas de las mejores páginas para aprender en este mundo de la  programacion.

« Última modificación: 17 de Agosto 2022, 21:26 por Ogramar »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 860
    • Ver Perfil
Re: Hilos En Java
« Respuesta #1 en: 11 de Julio 2021, 02:08 »
Ufff... veo un montón de clases, cuando lo que se pide no es tanto. No sé si modificar y adaptar ese código es el camino...

Echarle un vistazo para inspirarse tal vez..., pero adaptarlo, puede ser más complicado que comenzar uno nuevo.

Te dejo una propuesta para solucionarlo, a ver si te es útil.

Comenzamos con la clase Venta.

Se crea una fecha completa para el atributo, pero luego para guardar los datos en el archivo de texto, solo se guarda el año (ignoro mes y día) porque es el dato importante para los posteriores cálculos.

Uso un método que retorna un String con los atributos separados por comas, que serán las líneas que se guardarán en el archivo de texto.

Código: [Seleccionar]
import java.time.LocalDate;
import java.util.Random;

public class Venta {

private String codigo;
private String ciudad;
private LocalDate fecha;
private int valor;

public Venta(int numCodigo) {
codigo = String.format("U%07d", numCodigo); //Formato U0000001
Random azar = new Random();
ciudad = ciudadRandom(azar);
int year = azar.nextInt(5) + 2016; //Año entre 2016 y 2020
int mes = azar.nextInt(12) + 1;
int dia;
if (mes == 2)
dia = azar.nextInt(28) + 1;
else
dia = azar.nextInt(30) + 1;
fecha = LocalDate.of(year, mes, dia);
valor = azar.nextInt(5000) + 1;
}

private String ciudadRandom(Random rnd) {
final String[] ciudades = {"Manta","Portoviejo","Chone","Montecristi","Jipijapa"};
return ciudades[rnd.nextInt(ciudades.length)];
}

public String toTXT() {
//De la fecha solo se guarda el año porque es el dato que importa
return String.format("%s,%s,%d,%d", codigo, ciudad, fecha.getYear(), valor);
}

}

Bien, vamos a ver los hilos.
Hay que crear dos clases que hereden de Thread, es decir, dos tipos de hilo. Cada hilo hará distintos cálculos con los datos del archivo de texto que luego crearemos.

Estos hilos, además de hacer sus cálculos, modificarán el formulario (la interfaz gráfica) mostrando ellos mismos los resultados obtenidos.
No he sabido ver cómo es el formulario del ejercicio de ejemplo. Yo he optado por usar dos áreas de texto, una para cada hilo, y además tendrán una barra de progreso que se incrementarán durante el tiempo que dure el proceso del hilo.

Así que estos hilos se encargarán de leer las líneas del archivo, buscar los datos que necesitan, computar, actualizar la barra de progreso y finalmente mostrar los datos computados en el área de texto.

Para que estos hilos sepan con cuál barra progreso y área de texto han de interactuar, usaré unos setter para hacerles llegar las referencias de los correspondientes elementos del formulario con los que han de trabajar.

Esta sería la clase Hilo1

Código: [Seleccionar]
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;

/**
 * Suma de valores>3000 de los años 2017, 2018, 2019
 */
public class Hilo1 extends Thread{

private JProgressBar progreso;
private JTextArea texto;

public void setProgreso(JProgressBar progreso) {
this.progreso = progreso;
}

public void setTexto(JTextArea texto) {
this.texto = texto;
}

@Override
public void run() {
progreso.setValue(0);
texto.setText("      Suma de valores > 3000 de los años 2017, 2018, 2019\n"
+ "      ----------------------------------------------------------------------------\n\n");
int sumaValores = 0;
File archivo = new File("DatosVentas.txt");

try {
long inicio = System.currentTimeMillis();
BufferedReader lector = new BufferedReader(new FileReader(archivo));
String linea = lector.readLine();
while (linea != null) {
progreso.setValue(progreso.getValue() + 1);
String[] datos = linea.split(",");
int year = Integer.parseInt(datos[2]);
if (year >= 2017 && year <= 2019) {
int valor = Integer.parseInt(datos[3]);
if (valor > 3000)
sumaValores += valor;
}
linea = lector.readLine();
}
lector.close();
long fin = System.currentTimeMillis();
//Resultados
texto.append("Suma: " + sumaValores);
texto.append("\nTiempo: " + (fin - inicio) + " ms");
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null, "No hay archivo de datos.", "Calculos de Hilo1",
JOptionPane.ERROR_MESSAGE);
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error accediendo a:\n" + archivo.getAbsolutePath(),
"Calculos de Hilo1", JOptionPane.ERROR_MESSAGE);
}
}

}

Y esta Hilo2. Prácticamente son idénticas, solo cambian los datos que computan.
Código: [Seleccionar]
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;

/**
 * De los años 2018, 2019, 2020 cual es el valor menor y mayor
 */
public class Hilo2 extends Thread{

private JProgressBar progreso;
private JTextArea texto;

public void setProgreso(JProgressBar progreso) {
this.progreso = progreso;
}

public void setTexto(JTextArea texto) {
this.texto = texto;
}

@Override
public void run() {
progreso.setValue(0);
texto.setText("      De los años 2018, 2019, 2020 calcular valor menor y mayor\n"
+ "      ----------------------------------------------------------------------------------\n\n");
int menor = 5001;
int mayor = 0;
File archivo = new File("DatosVentas.txt");

try {
long inicio = System.currentTimeMillis();
BufferedReader lector = new BufferedReader(new FileReader(archivo));
String linea = lector.readLine();
while (linea != null) {
progreso.setValue(progreso.getValue() + 1);
String[] datos = linea.split(",");
int year = Integer.parseInt(datos[2]);
if (year >= 2018 && year <= 2020) {
int valor = Integer.parseInt(datos[3]);
if (valor > mayor)
mayor = valor;
if (valor < menor)
menor = valor;
}
linea = lector.readLine();
}
lector.close();
long fin = System.currentTimeMillis();
//Resultados
texto.append("Mayor: " + mayor);
texto.append("\nMenor: " + menor);
texto.append("\nTiempo: " + (fin - inicio) + " ms");
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null, "No hay archivo de datos.", "Calculos de Hilo2",
JOptionPane.ERROR_MESSAGE);
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error accediendo a:\n" + archivo.getAbsolutePath(),
"Calculos de Hilo2", JOptionPane.ERROR_MESSAGE);
}
}

}

Bien, ya tendríamos los hilos.
Vamos a ver la interfaz del programa.

Este es el diseño que he creado:




Hay un botón para generar un archivo con los datos de ventas y otro botón para dar comienzo a los cálculos.

Al pulsar este último botón, se iniciarán los dos hilos y los resultados se verán en los dos paneles centrales.


Bien, el panel superior con el botón para crear el archivo, es un panel que he escrito como una clase separada porque tiene bastante líneas de código y además es completamente autónomo, no necesita interactuar con el resto de elementos de la interfaz, así que puede escribirse como una clase individual.

Esta clase usa un BufferedWriter dentro de un bucle para crear objetos de la clase Venta y escribir una línea de texto con los datos en un archivo.
El programa pide crear 2 millones de registros (el archivo superará los 50MB de peso..  :o), esto puede requerir unos cuantos segundos según la potencia de computación de cada PC, así que antes de crear el archivo se muestra un mensaje advirtiendo de esto, para que el usuario sea consciente y sepa que ha de esperar.
Una vez creado el archivo, también se avisa al usuario para que sepa que el proceso ya se ha completado.

Esta es la clase PanelCrear

Código: [Seleccionar]
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

public class PanelCrear extends JPanel{

private JButton btCrear;

public PanelCrear() {
btCrear = new JButton("Crear nuevo fichero de datos");
btCrear.addActionListener(new AccionCrear());
add(btCrear);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(25, 25, 25, 25),
BorderFactory.createLoweredSoftBevelBorder()));
}

private class AccionCrear implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "A continuación se creará un archivo con 2 millones de líneas."
+ "\nEsto puede tardar unos segundos según las capacidades de tu PC.\nTen paciencia...",
"Crear Archivo", JOptionPane.WARNING_MESSAGE);
File archivo = new File("DatosVentas.txt");
try {
BufferedWriter escritor = new BufferedWriter(new FileWriter(archivo, false));
//Creamos 2 millones de registros
for (int i = 1; i <= 2000000; i++) {
Venta nueva = new Venta(i); //Generamos nueva VENTA
escritor.write(nueva.toTXT()); //Creamos una línea de texto con los datos de la VENTA
escritor.newLine();
}
escritor.close();
JOptionPane.showMessageDialog(null, "Nuevos datos creados en:\n" + archivo.getAbsolutePath(),
"Crear Archivo", JOptionPane.INFORMATION_MESSAGE);
} catch (IOException e1) {
JOptionPane.showMessageDialog(null, "No se pudo crear archivo:\n" + archivo.getAbsolutePath(),
"Crear Archivo", JOptionPane.ERROR_MESSAGE);
}

}
}

}

Vamos a ver ahora los paneles que muestran el area de texto y la barra de progreso.
Se trata de una única clase, que mediante constructor recibe un entero entre 1 y 2 para indicarle si ha de trabajar con Hilo1 o con Hilo2.

Así no hay que escribir dos clases distintas, basta con escribir solo una.

En realidad es una clase con poco código.

Inicializa el area de texto y la barra de progreso.

Luego tiene un método llamado calcular() que según el tipo de hilo que se le haya indicado, configura e inicia un Hilo1 o un Hilo2

Así, con una sola clase, podemos disponer de dos paneles para cada tipo de hilo.

Esta es la clase PanelHilo

Código: [Seleccionar]
import java.awt.BorderLayout;

import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;
import javax.swing.border.EtchedBorder;

public class PanelHilo extends JPanel{

private JProgressBar barraProgreso;
private JTextArea areaTexto;
int tipoHilo; //Se le indica al panel si ha de usar un Hilo1 o un Hilo2

public PanelHilo(int hilo) {
barraProgreso = new JProgressBar(0,2000000);
barraProgreso.setStringPainted(true);
areaTexto = new JTextArea(10, 33);
areaTexto.setEditable(false);
tipoHilo = hilo;

setLayout(new BorderLayout());
JPanel pnBarra = new JPanel();
pnBarra.add(barraProgreso);
pnBarra.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(15,15,15,15),
BorderFactory.createRaisedSoftBevelBorder()));

JPanel pnArea = new JPanel();
pnArea.add(areaTexto);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Hilo " + tipoHilo),
BorderFactory.createEtchedBorder(EtchedBorder.RAISED)));

add(pnBarra, BorderLayout.NORTH);
add(pnArea, BorderLayout.CENTER);
}

public void calcular() {
//Instanciamos un Hilo1 o un Hilo2 según el atributo tipoHilo
if (tipoHilo == 1) {
Hilo1 hiloCalculos = new Hilo1();
hiloCalculos.setProgreso(barraProgreso);
hiloCalculos.setTexto(areaTexto);
hiloCalculos.start();
}
else {
Hilo2 hiloCalculos = new Hilo2();
hiloCalculos.setProgreso(barraProgreso);
hiloCalculos.setTexto(areaTexto);
hiloCalculos.start();
}
}

}

Ya solo queda la clase principal. Es un JFrame que utiliza las clases JPanel que hemos visto antes y además añade el botón para iniciar los cálculos.

Al pulsar este botón se comprueba si existe o no un fichero de datos.

Si no existe, se avisa y no se hacen cómputos (porque no se puede).

Si existe, se le pide a los dos PanelHilo mediante el método calcular() que inicien sus correspondientes hilos.

Y enseguida podremos ver los resultados en pantalla.

Esta es la clase Formulario

Código: [Seleccionar]
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Formulario extends JFrame{

private PanelHilo pnHilo1;
private PanelHilo pnHilo2;
private JButton btIniciar;

public Formulario() {
pnHilo1 = new PanelHilo(1); //Especificamos que "tipo" de hilo ha de usar
pnHilo2 = new PanelHilo(2);

JPanel pnHilos = new JPanel();
pnHilos.add(pnHilo1);
pnHilos.add(pnHilo2);

btIniciar = new JButton("Iniciar Calculos");
btIniciar.addActionListener(new AccionIniciar());
JPanel pnIniciar = new JPanel();
pnIniciar.add(btIniciar);

setLayout(new BorderLayout());
add(new PanelCrear(), BorderLayout.NORTH);
add(pnHilos, BorderLayout.CENTER);
add(pnIniciar, BorderLayout.SOUTH);

setTitle("Calculos con Hilos");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Formulario();
}
});
}




Y creo que con esto ya se tiene lo que se pide.

A ver si te sirve, y pregunta cualquier cosa que no entiendas.

Un saludo.
« Última modificación: 14 de Enero 2022, 19:33 por Ogramar »
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

fabricioom

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 5
    • Ver Perfil
Re: Hilos En Java
« Respuesta #2 en: 11 de Julio 2021, 06:02 »
Gracias bro @kabuto me ayudastes mucho enserio muchas gracias por tu ayuda fue un excelente aporte bendiciones abrazos a la distancia.

JeanPal

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 5
    • Ver Perfil
Yo soy nuevo en esto de la programación y me han implementado el mismo problema, entiendo cómo lo formula pero no cómo implementarlo en netbeans

¿Me podrian ayudar con la carpeta para estrcuturar un nuevo proyecto y saber como implementarlo?

Les quedaré sumamente agradecido.
« Última modificación: 02 de Junio 2022, 18:04 por Mario R. Rancel »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 860
    • Ver Perfil
El código lo escribí con Eclipse, pero eso no importa, sirve para NetBeans, IntelliJ y cualquier programa editor de código.

Si con "estructurar la carpeta" te refieres a cuáles podrían ser los packages para crear para el proyecto, pues eso va a gusto de cada uno.
Yo lo hice todo dentro de un único package, ya que es un proyecto pequeño, pero sí es buena costumbre separar las clases en distintos packages según su naturaleza.

Se podría crear un package llamado "vista", o "interfaz", o "gui"(de graphical user interface) que agrupe las clases que modelan los paneles y el marco del programa principal.
Es decir, las que crean lo que se ve en pantalla (la vista).
Estas serían las clases: PanelCrear, PanelHilo y Formulario.

En otro package se podrían poner las clases que modelan lo que "no se ve", lo que vienen a ser representación de datos y/o de procesos y que comúmente se llama el "modelo"
Estas serían las clases: Venta, Hilo1 e Hilo2.


Por otra parte, no se si al trabajar con NetBeans, te van a pedir que la interfaz de usuario la construyas usando el "GUI Builder".
Ahí poco o nada puedo ayudarte. Nunca lo uso, no se usarlo, siempre construyo las interfaces Swing tecleando código, no usando formularios de "click y arrastrar" para que autogenere el código.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

JeanPal

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 5
    • Ver Perfil
con respecto al código que ud implemento me va bien
pero en formulario
Código: [Seleccionar]
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class Formulario extends JFrame{

private PanelHilo pnHilo1;
private PanelHilo pnHilo2;
private JButton btIniciar;

public Formulario() {
pnHilo1 = new PanelHilo(1); //Especificamos que "tipo" de hilo ha de usar
pnHilo2 = new PanelHilo(2);

JPanel pnHilos = new JPanel();
pnHilos.add(pnHilo1);
pnHilos.add(pnHilo2);

btIniciar = new JButton("Iniciar Calculos");
btIniciar.addActionListener(new AccionIniciar());
JPanel pnIniciar = new JPanel();
pnIniciar.add(btIniciar);

setLayout(new BorderLayout());
add(new PanelCrear(), BorderLayout.NORTH);
add(pnHilos, BorderLayout.CENTER);
add(pnIniciar, BorderLayout.SOUTH);

setTitle("Calculos con Hilos");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Formulario();
}
});
}
}
en la parte de
Código: [Seleccionar]
btIniciar = new JButton("Iniciar Calculos");
btIniciar.addActionListener(new AccionIniciar());
JPanel pnIniciar = new JPanel();
pnIniciar.add(btIniciar);
me genera un error con el AccionIniciar, me muestra este mensaje AccionIniciar cannot be resolved to a type me podrías ayudar, se que debo implementar el método para llamar al objeto pero llevo horas pensando y al recién incorporarme no he podido completar el código en si y solo me falta ese apartado para que el código funcione como lo mencionas en tu comentario, de antemano muchas gracias @kabuto
estoy implementando este codigo en eclipse.
« Última modificación: 06 de Junio 2022, 06:09 por JeanPal »

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 860
    • Ver Perfil
¡Oh! :o

Pues es verdad que falta parte del código, parece que en su momento no lo publiqué correctamente.

AccionIniciar es una clase interna anidada dentro de la clase Formulario.
Es una clase que implementa la interfaz ActionListener y así crear una "acción" para el botón que inicia los cálculos.

Pongo aquí la clase Formulario, marcando en negrita la parte de código que faltaba:

Citar
public class Formulario extends JFrame{
   
   private PanelHilo pnHilo1;
   private PanelHilo pnHilo2;
   private JButton btIniciar;
   
   public Formulario() {
      pnHilo1 = new PanelHilo(1); //Especificamos que "tipo" de hilo ha de usar
      pnHilo2 = new PanelHilo(2);
      
      JPanel pnHilos = new JPanel();
      pnHilos.add(pnHilo1);
      pnHilos.add(pnHilo2);
      
      btIniciar = new JButton("Iniciar Calculos");
      btIniciar.addActionListener(new AccionIniciar());
      JPanel pnIniciar = new JPanel();
      pnIniciar.add(btIniciar);
      
      setLayout(new BorderLayout());
      add(new PanelCrear(), BorderLayout.NORTH);
      add(pnHilos, BorderLayout.CENTER);
      add(pnIniciar, BorderLayout.SOUTH);
      
      setTitle("Calculos con Hilos");
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      pack();
      setLocationRelativeTo(null);
      setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         @Override
         public void run() {
            new Formulario();   
         }
      });
   }
   
   private class AccionIniciar implements ActionListener {
      @Override
      public void actionPerformed(ActionEvent e) {
         File archivo = new File("DatosVentas.txt");
         if (archivo.exists()) {
            pnHilo1.calcular();
            pnHilo2.calcular();
         }
         else
            JOptionPane.showMessageDialog(null, "No existe ningun archivo de datos.",
                  "Iniciar Calculos", JOptionPane.WARNING_MESSAGE);
      }   
   }

}
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

JeanPal

  • Sin experiencia
  • *
  • APR2.COM
  • Mensajes: 5
    • Ver Perfil
un crak @kabuto
me has ayudado mucho, mira un problema similar que me gustaría obtener tu ayuda
Crear la data de 2 millones de registros con datos:
MULTA(ciudad,tipo,fecha,valor)
fecha entre año 2015 a 2020, tipo= A,B,C,D, valor entre 1 a 2000
- Hilo1: Porcentaje de año 2015 tipo A, año 2017 tipo C, año 2019 tipo D
- Hilo2: El menor valor de MANTA, PORTOVIEJO, CHONE de tipo A
Cada hilo muestre los tiempos de proceso y resultados planteados.
te agradecería si pudieras de dedicarle un poco de tu tiempo
   

Kabuto

  • Moderador Global
  • Experto
  • *******
  • Mensajes: 860
    • Ver Perfil
Hola.
El ejercicio es prácticamente el mismo, solo hay que modificarlo un poco para adaptarlo a lo que se pide.

En lugar de una clase Venta, ahora trabajaremos con una clase Multa.
La lógica es similar a la que usamos para las ventas, al generar un objeto Multa, sus atributos se van a autorellenar usando valores random al azar.
Como de nuevo para la fecha solo se va a trabajar con el año, lo he simplificado y ya no genero una fecha completa, solo un valor para el año entre 2015 y 2020.
Si se quiere generar una fecha completa, es simplemente copiar el código que usé para la clase Venta.

Clase Multa:
Código: [Seleccionar]
import java.util.Random;

public class Multa {

private String ciudad;
private String tipo;
private int fecha; //Solo año, sin dia ni mes
private int valor;

public Multa() {
Random azar = new Random();
ciudad = ciudadRandom(azar);
tipo = tipoRandom(azar);
fecha = azar.nextInt(6) + 2015;//Genera años entre 2015 y 2020
valor = azar.nextInt(2000) + 1;//Genera valores entre 1 y 2000
}

private String ciudadRandom(Random rnd) {
final String[] ciudades = {"Manta","Portoviejo","Chone","Montecristi","Jipijapa"};
return ciudades[rnd.nextInt(ciudades.length)];
}

private String tipoRandom(Random rnd) {
final String[] tipos = {"A", "B", "C", "D"};
return tipos[rnd.nextInt(tipos.length)];
}

public String toTXT() {
return String.format("%s,%s,%d,%d", ciudad, tipo, fecha, valor);
}

}

Ahora hay que modificar las clases para los threads.
Hilo1 ahora cuenta las multas de unos determinados años y tipo, para luego calcular un porcentaje sobre el total de multas.
Código: [Seleccionar]
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;

/**
 * Porcentaje de año 2015 tipo A, año 2017 tipo C, año 2019 tipo D
 */
public class Hilo1 extends Thread{

private JProgressBar progreso;
private JTextArea texto;

public void setProgreso(JProgressBar progreso) {
this.progreso = progreso;
}

public void setTexto(JTextArea texto) {
this.texto = texto;
}

@Override
public void run() {
progreso.setValue(0);
texto.setText("      Porcentajes de años > 2015 tipo A, 2017 tipo C, 2019 tipo D\n"
+ "      ----------------------------------------------------------------------------\n\n");
//Contadores de años
int cont2015 = 0, cont2017 = 0, cont2019 = 0;
int total = 0;

File archivo = new File("DatosMultas.txt");

try {
long inicio = System.currentTimeMillis();
BufferedReader lector = new BufferedReader(new FileReader(archivo));
String linea = lector.readLine();
while (linea != null) {
//Actualizamo la barra de progreso
progreso.setValue(progreso.getValue() + 1);

total++; //Contamos esta multa para luego calcular porcentaje sobre el total

//Separamos los datos de la línea leída
String[] datos = linea.split(",");
//Capturamos el año
int year = Integer.parseInt(datos[2]);
//Consultamos el tipo y contamos años, si corresponde.
switch(datos[1]) {
case "A":
if (year == 2015)
cont2015++;
break;
case "C":
if (year == 2017)
cont2017++;
break;
case "D":
if (year == 2019)
cont2019++;
}
//Pasamos a la siguiente línea
linea = lector.readLine();
}
lector.close();
long fin = System.currentTimeMillis();
//Resultados
float porc2015 = cont2015 * 100 / total;
float porc2017 = cont2017 * 100 / total;
float porc2019 = cont2019 * 100 / total;
texto.append("Total multas: " + total);
texto.append(String.format("\nAño 2015 tipo A: %.2f%%", porc2015));
texto.append(String.format("\nAño 2017 tipo C: %.2f%%", porc2017));
texto.append(String.format("\nAño 2019 tipo D: %.2f%%", porc2019));
texto.append("\n\nTiempo: " + (fin - inicio) + " ms");
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null, "No hay archivo de datos.", "Calculos de Hilo1",
JOptionPane.ERROR_MESSAGE);
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error accediendo a:\n" + archivo.getAbsolutePath(),
"Calculos de Hilo1", JOptionPane.ERROR_MESSAGE);
}
}

}

Hilo2 buscará el valor menor de multa de tipo A, para unas ciudades determinadas:
Código: [Seleccionar]
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.JTextArea;

/**
 * El menor valor de MANTA, PORTOVIEJO, CHONE de tipo A
 */
public class Hilo2 extends Thread{

private JProgressBar progreso;
private JTextArea texto;

public void setProgreso(JProgressBar progreso) {
this.progreso = progreso;
}

public void setTexto(JTextArea texto) {
this.texto = texto;
}

@Override
public void run() {
progreso.setValue(0);
texto.setText("      El menor valor de MANTA, PORTOVIEJO, CHONE de tipo A\n"
+ "      ----------------------------------------------------------------------------------\n\n");
//Variables donde ir guardando los valores menores
int menorManta = Integer.MAX_VALUE, menorPorto = Integer.MAX_VALUE, menorChone = Integer.MAX_VALUE;

File archivo = new File("DatosMultas.txt");

try {
long inicio = System.currentTimeMillis();
BufferedReader lector = new BufferedReader(new FileReader(archivo));
String linea = lector.readLine();
while (linea != null) {
progreso.setValue(progreso.getValue() + 1);
String[] datos = linea.split(",");
//Consultamos ciudad y guardamos valores sin son tipo A y menor que valor guardado actualmente
switch(datos[0]) {
case "Manta":
if (datos[1].equals("A")) {
int valor = Integer.parseInt(datos[3]);
if (valor < menorManta)
menorManta = valor;
}
break;
case "Portoviejo":
if (datos[1].equals("A")) {
int valor = Integer.parseInt(datos[3]);
if (valor < menorPorto)
menorPorto = valor;
}
break;
case "Chone":
if (datos[1].equals("A")) {
int valor = Integer.parseInt(datos[3]);
if (valor < menorChone)
menorChone = valor;
}
}
linea = lector.readLine();
}
lector.close();
long fin = System.currentTimeMillis();
//Resultados
texto.append("Menor valor de Manta: " + menorManta);
texto.append("\nMenor valor de Portoviejo: " + menorPorto);
texto.append("\nMenor valor de Chone: " + menorChone);
texto.append("\n\nTiempo: " + (fin - inicio) + " ms");
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null, "No hay archivo de datos.", "Calculos de Hilo2",
JOptionPane.ERROR_MESSAGE);
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error accediendo a:\n" + archivo.getAbsolutePath(),
"Calculos de Hilo2", JOptionPane.ERROR_MESSAGE);
}
}

}

Pasamos a las clases que componen la interfaz. El PanelCrear ahora va a crear 2 millones de multas y las volcará en un archivo de texto.
Código: [Seleccionar]
public class PanelCrear extends JPanel{

private JButton btCrear;

public PanelCrear() {
btCrear = new JButton("Crear nuevo fichero de datos");
btCrear.addActionListener(new AccionCrear());
add(btCrear);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(25, 25, 25, 25),
BorderFactory.createLoweredSoftBevelBorder()));
}

private class AccionCrear implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "A continuación se creará un archivo con 2 millones de líneas."
+ "\nEsto puede tardar unos segundos según las capacidades de tu PC.\nTen paciencia...",
"Crear Archivo", JOptionPane.WARNING_MESSAGE);
File archivo = new File("DatosMultas.txt");
try {
BufferedWriter escritor = new BufferedWriter(new FileWriter(archivo, false));
//Creamos 2 millones de registros
for (int i = 1; i <= 2000000; i++) {
Multa nueva = new Multa(); //Generamos nueva MULTA
escritor.write(nueva.toTXT()); //Creamos una línea de texto con los datos de la MULTA
escritor.newLine();
}
escritor.close();
JOptionPane.showMessageDialog(null, "Nuevos datos creados en:\n" + archivo.getAbsolutePath(),
"Crear Archivo", JOptionPane.INFORMATION_MESSAGE);
} catch (IOException e1) {
JOptionPane.showMessageDialog(null, "No se pudo crear archivo:\n" + archivo.getAbsolutePath(),
"Crear Archivo", JOptionPane.ERROR_MESSAGE);
}

}
}

}

En la clase PanelHilo, no hay que cambiar absolutamente nada. Es lo bueno de modular el código y conseguir abstraer unas clases de otras. Aunque las clases thread cambien, este panel conseguirá ponerlos en marcha sin que eso importe.
Código: [Seleccionar]
public class PanelHilo extends JPanel{

private JProgressBar barraProgreso;
private JTextArea areaTexto;
int tipoHilo; //Se le indica al panel si ha de usar un Hilo1 o un Hilo2

public PanelHilo(int hilo) {
barraProgreso = new JProgressBar(0,2000000);
barraProgreso.setStringPainted(true);
areaTexto = new JTextArea(10, 33);
areaTexto.setEditable(false);
tipoHilo = hilo;

setLayout(new BorderLayout());
JPanel pnBarra = new JPanel();
pnBarra.add(barraProgreso);
pnBarra.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(15,15,15,15),
BorderFactory.createRaisedSoftBevelBorder()));

JPanel pnArea = new JPanel();
pnArea.add(areaTexto);
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Hilo " + tipoHilo),
BorderFactory.createEtchedBorder(EtchedBorder.RAISED)));

add(pnBarra, BorderLayout.NORTH);
add(pnArea, BorderLayout.CENTER);
}

public void calcular() {
//Instanciamos un Hilo1 o un Hilo2 según el atributo tipoHilo
if (tipoHilo == 1) {
Hilo1 hiloCalculos = new Hilo1();
hiloCalculos.setProgreso(barraProgreso);
hiloCalculos.setTexto(areaTexto);
hiloCalculos.start();
}
else {
Hilo2 hiloCalculos = new Hilo2();
hiloCalculos.setProgreso(barraProgreso);
hiloCalculos.setTexto(areaTexto);
hiloCalculos.start();
}
}

}

Por último, la clase principal Formulario.
Aquí tampoco hay que hacer apenas ningún cambio, solo asegurarnos de que le estamos dando correctamente el nombre del archivo de texto con el que ha de trabajar.
Yo como le he cambiado el nombre a este archivo, pues sí tengo que reflejarlo en el código de esta clase:
Código: [Seleccionar]
public class Formulario extends JFrame{

private PanelHilo pnHilo1;
private PanelHilo pnHilo2;
private JButton btIniciar;

public Formulario() {
pnHilo1 = new PanelHilo(1); //Especificamos que "tipo" de hilo ha de usar
pnHilo2 = new PanelHilo(2);

JPanel pnHilos = new JPanel();
pnHilos.add(pnHilo1);
pnHilos.add(pnHilo2);

btIniciar = new JButton("Iniciar Calculos");
btIniciar.addActionListener(new AccionIniciar());
JPanel pnIniciar = new JPanel();
pnIniciar.add(btIniciar);

setLayout(new BorderLayout());
add(new PanelCrear(), BorderLayout.NORTH);
add(pnHilos, BorderLayout.CENTER);
add(pnIniciar, BorderLayout.SOUTH);

setTitle("Calculos con Hilos");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
setVisible(true);
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Formulario();
}
});
}

private class AccionIniciar implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
File archivo = new File("DatosMultas.txt");
if (archivo.exists()) {
pnHilo1.calcular();
pnHilo2.calcular();
}
else
JOptionPane.showMessageDialog(null, "No existe ningun archivo de datos.",
"Iniciar Calculos", JOptionPane.WARNING_MESSAGE);
}
}
}


Y ya podemos ejecutar el programa.
Sin embargo, en este ejercicio los resultados son un poco decepcionantes, porque la inmensa mayoría de las veces, siempre son los mismos:


Esto es porque la muestra de datos es muy grande (2 millones) así que aunque los datos se generan al azar, al ser tan grande los datos generados tienen oportunidades de sobra para quedar muy igualados.
Así que todas las ciudades van a tener multas de valor 1  y los porcentajes de tipo y año apenas hay diferencias.
Si hacemos una prueba con una muestra menor, por ejemplo 200 multas, o 2000... entonces se obtienen resultados menos igualados. (y más interesantes)

Por favor, no dudes en preguntar lo que no entiendas.

Un saludo.
NO respondo dudas por mensaje privado
Publicando vuestras dudas en el foro público conseguimos:
- Que más gente aporte respuestas mejores o complementarias.
- Que otras personas puedan aprender de vuestras dudas.

Mejor en PÚBLICO que en privado. Gracias

 

Sobre la educación, sólo puedo decir que es el tema más importante en el que nosotros, como pueblo, debemos involucrarnos.

Abraham Lincoln (1808-1865) Presidente estadounidense.

aprenderaprogramar.com: Desde 2006 comprometidos con la didáctica y divulgación de la programación

Preguntas y respuestas

¿Cómo establecer o cambiar la imagen asociada (avatar) de usuario?
  1. Inicia sesión con tu nombre de usuario y contraseña.
  2. Pulsa en perfil --> perfil del foro
  3. Elige la imagen personalizada que quieras usar. Puedes escogerla de una galería de imágenes o subirla desde tu ordenador.
  4. En la parte final de la página pulsa el botón "cambiar perfil".