Prime Generator Timing

how do I add a timer to my program?

  • (2 Pages)
  • +
  • 1
  • 2

17 Replies - 2674 Views - Last Post: 31 December 2009 - 11:44 PM Rate Topic: -----

#1 noctolater  Icon User is offline

  • D.I.C Head

Reputation: 3
  • View blog
  • Posts: 72
  • Joined: 29-April 09

Prime Generator Timing

Posted 02 December 2009 - 05:38 PM

I have written a prime number program that creates and saves prime numbers. I am constantly working on ways to improve its efficiency, even though I know that a sieve method would work better. But, I am starting to have trouble figuring out how much more efficient I have made it. I was wondering what everyone thought in terms of how I could implement a timer, so I can see how long it takes to test through a million numbers or so. That way I can clearly see how much efficiency I am gaining.

Here's the code for the program:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.text.DecimalFormat;

/**
 * Primes is my experiments with prime number generation.
 * 
 * version 1.0 -uses Integers, up to a maximum value of around 2 * 10^9.
 * 
 * version 2.0 - uses Longs, up to a maximum value of around 9 * 10^18.
 * 
 * version 3.0 - uses BIG_DECIMAL, up to a maximum value of whatever fits in
 * memory.
 * 
 * @author Sam Lanzo
 * @version 2.0
 */
public class Primes {
	private static int increment = 4;
	private static BufferedWriter writer;
	private static final JFrame frame = new JFrame("Primes");
	private static JLabel progress = new JLabel("");
	private static final JButton quit = new JButton("QUIT");
	private static final long MAXIMUM = Long.MAX_VALUE;
	private static boolean run = true;
	private static long lastPrime = -1;

	/**
	 * setConstraints creates the parameters for the simple GUI window that
	 * displays the status of the prime number algorithm.
	 */
	public static void setConstraints() {
		frame.setLayout(new GridBagLayout());
		frame.setSize(250, 100);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLocationRelativeTo(null);
		frame.setResizable(false);
		frame.setVisible(true);
	}

	/**
	 * updateView updates the simple GUI window to display the current progress
	 * of the prime number algorithm.
	 */
	public static void updateView(long lastPrime) {
		frame.getContentPane().removeAll();
		Primes.view(lastPrime);
		frame.repaint();
	}

	/**
	 * view creates a simple window displaying the last known prime, and a quit
	 * button that saves the progress.
	 */
	public static void view(final long lastPrime) {
		DecimalFormat latestPrime = new DecimalFormat("###,###,###,###,###,###");
		DecimalFormat percentFormat = new DecimalFormat("##.###########%");
		GridBagConstraints c = new GridBagConstraints();
		double percent = lastPrime / (double) MAXIMUM;
		progress = new JLabel("<html>Last Prime Checked: "
				+ latestPrime.format(lastPrime) + "<P>Percent Done: "
				+ percentFormat.format(percent));

		frame.add(progress, c);

		c.gridy = 1;
		frame.add(quit, c);
		quit.addActionListener(new ActionListener() {
			/**
			 * This will break the loop of the prime search, saving the last
			 * prime and closing the program.
			 * 
			 * @param theEvent
			 *			and ActionEvent
			 */
			public void actionPerformed(ActionEvent theEvent) {
				run = false;
			}
		});
		frame.validate();
		frame.repaint();
	}

	/**
	 * readLastPrime reads the last known prime from the disk, allowing the
	 * program to restart where it had left off.
	 */
	public static void readLastPrime() {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(
					"lastPrime.txt"));
			lastPrime = Integer.parseInt(reader.readLine());
			reader.close();
			// if this is true, then you add 4 to skip the multiple of 3.
			if ((lastPrime + 2) % 3 == 0) {
				increment = 4;
			} else {
				increment = 2;
			}
		} catch (Exception ex) {
			System.out.println("Exception: " + ex);
		}
	}

	/**
	 * addPrimeadds the last known prime to the end of the prime number file. It
	 * breaks the prime list into files that contain all the primes within a
	 * range of 1 billion.
	 */
	public static void addPrime(long lastPrime) {
		try {
			long fileNumber = lastPrime / 1000000000;
			writer = new BufferedWriter(new FileWriter("primes" + fileNumber
					+ ".txt", true));
			writer.write("" + lastPrime);
			writer.newLine();
			writer.flush();
		} catch (Exception ex) {
			System.out.println("Exception: " + ex);
		}
	}

	/**
	 * writeLastPrime writes the last known prime to disk, allowing the program
	 * to begin again from where it left off.
	 */
	public static void writeLastPrime(long lastPrime) {
		try {
			writer = new BufferedWriter(new FileWriter("lastPrime.txt"));
			writer.write("" + lastPrime);
			writer.close();
		} catch (Exception ex) {
			System.out.println("Exception: " + ex);
		}
	}

	/**
	 * multipleOfFive simply checks the last digit of the number to see if it's
	 * a 5, and therefore a multiple of 5.
	 */
	public static boolean multipleOfFive(long suspectPrime) {
		String suspectedPrime = "" + suspectPrime;
		if (suspectedPrime.substring(suspectedPrime.length() - 1).equals("5")) {
			return false;
		}
		return true;
	}

	/**
	 * isPrime checks if a number is prime. First it checks the number against a
	 * multiple of 3 and 5. Then, it starts to go through the list of all known
	 * primes up to and including the square root of the number it is checking.
	 * 
	 * Since it's reading primes0.txt, it will only work up to 1 billion
	 * squared, or 1 with 18 zeroes after it. Since Long.MAX_VALUE is a little
	 * more than 9 with 18 zeroes, it will stop being reliable after checking
	 * 1/9th of the numbers.
	 * 
	 * 3,037,000,500 is what it should check up to for the whole range of
	 * Long.MAX_VALUE
	 */
	public static boolean isPrime(long suspectPrime) {
		try {
			BufferedReader reader = new BufferedReader(new FileReader(
					"primes0.txt"));
			if ((Primes.multipleOfFive(suspectPrime) == false)) {
				return false;
			} // squareRoot rounds up to the nearest whole number.
			long squareRoot = (long) (Math.sqrt(suspectPrime) + 1);

			long testPrime = Long.parseLong(reader.readLine());
			while (testPrime != 7) {
				testPrime = Long.parseLong(reader.readLine());
			}
			// while the number being tested as a factor is less than or equal
			// to the square root, and it does not divide evenly into the
			// suspect prime.
			while ((testPrime <= (squareRoot))
					&& (suspectPrime % testPrime != 0)) {
				testPrime = Integer.parseInt(reader.readLine());
			}
			reader.close();
			// if the last number tested is greater than the square root, none
			// of the numbers tested were factors.
			if (testPrime < (squareRoot)) {
				return false;
			}
		} catch (Exception ex) {
			System.out.println("Exception: " + ex);
		}
		return true;
	}

	/**
	 * main reads in the last known prime, adds 2, and starts checking if
	 * numbers are prime from there. It also updates the simple GUI to display
	 * the last known prime when 50,000 primes are found.
	 */
	public static void main(String args[]) {
		Primes.readLastPrime();
		if (lastPrime == -1) {
			Primes.addPrime(2);
			Primes.addPrime(3);
			Primes.addPrime(5);
			Primes.addPrime(7);
			Primes.writeLastPrime(7);
			Primes.readLastPrime();
		}

		Primes.setConstraints();
		Primes.view(lastPrime);
		int counter = 0;

		for (long i = lastPrime + (long) increment; i > 0 && i <= MAXIMUM
				&& run; i += (long) increment) {
			if (Primes.isPrime(i)) {
				Primes.addPrime(i);
				lastPrime = i;
				counter++;
			} // for every 50,000 primes found, update the GUI.
			if (counter == 50000) {
				Primes.updateView(i);
				counter = 0;
			}
			// adding 2 and 4 alternately automatically skips all multiples of 3.
			switch (increment) {
			case 2:
				increment = 4;
				break;
			case 4:
				increment = 2;
				break;
			}
		} // if the quit button has been pressed.
		if (!run) {
			Primes.writeLastPrime(lastPrime);
			System.exit(0);
		}
		Primes.writeLastPrime(lastPrime);
		System.exit(0);
	}

	/**
	 * checkMethod checks if the program gets the right answer when it adds up
	 * all the prime numbers under 2 million.  This is merely here to make sure
	 * any changes to the class do not skip or include extra numbers.
	 */
	public static void checkMethod() {
		try {
			long answer = 0;
			BufferedReader reader = new BufferedReader(new FileReader(
					"primes0.txt"));
			String nextPrime = reader.readLine();
			while ((nextPrime != null) && (Long.parseLong(nextPrime) < 2000000)) {
				answer += Long.parseLong(nextPrime);
				nextPrime = reader.readLine();
			}
			System.out.println("142913828922 should be the answer.");
			System.out.println(answer + " is the answer.");
			System.out.println(nextPrime);
		} catch (Exception ex) {
			System.out.println("Exception: " + ex);
		}
	}
}



Is This A Good Question/Topic? 0
  • +

Replies To: Prime Generator Timing

#2 mostyfriedman  Icon User is offline

  • The Algorithmi
  • member icon

Reputation: 727
  • View blog
  • Posts: 4,473
  • Joined: 24-October 08

Re: Prime Generator Timing

Posted 02 December 2009 - 05:55 PM

well you can use the System.currentTimeMillis() method, it will return the current time in milliseconds, call it before you run the prime generator and once again afterwards and subtract both times from each other to know how long did it take to run
Was This Post Helpful? 1
  • +
  • -

#3 noctolater  Icon User is offline

  • D.I.C Head

Reputation: 3
  • View blog
  • Posts: 72
  • Joined: 29-April 09

Re: Prime Generator Timing

Posted 02 December 2009 - 06:34 PM

Java has so many commands that I don't even know to ask about, thanks! :P

Also, I was wondering about maybe making the program generate an Array List of 1,000 prime numbers, and having another method that dumped it to a file. I was really wondering if this would be possible using multiple threads, since my computer has a dual core. Does anyone know how feasible this is, and if there are any good guides to thread programming around?

This post has been edited by noctolater: 02 December 2009 - 06:35 PM

Was This Post Helpful? 0
  • +
  • -

#4 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8332
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Re: Prime Generator Timing

Posted 02 December 2009 - 11:22 PM

View Postnoctolater, on 2 Dec, 2009 - 05:34 PM, said:

Java has so many commands that I don't even know to ask about, thanks! :P

Also, I was wondering about maybe making the program generate an Array List of 1,000 prime numbers, and having another method that dumped it to a file. I was really wondering if this would be possible using multiple threads, since my computer has a dual core. Does anyone know how feasible this is, and if there are any good guides to thread programming around?


class Prime {
	int[] numbers = new int[1000];
....
	// assuming numbers[] are filled
	DumpNumbers dumpNumbers = new DumpNumbers(numbers);
	dumpNumbers.start();
....

	 class DumpNumbers extends Thread {
		 private int[] numbers;

		 // constructor
		 DumpNumbers(int[] numbers) {
			 this.numbers = numbers;
		 }

		 // will be invoke when you'll start() the thread
		 public void run() {
			// open file
			....
			for(int i = 0; i < numbers.lenght; i++) {
			  ... write numbers[i];
			}
			... close file
		}   // end class DumpNumbers
}



Have fun
Was This Post Helpful? 0
  • +
  • -

#5 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8332
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Re: Prime Generator Timing

Posted 21 December 2009 - 09:14 PM

Took a few hours but here is a good compromise between efficiency and a nice GUI
It use the Erathosthene sieve algorithm.

Usually Eratosthene algorithm used a BitSet but if we want to check uo to Long.MAX_VALUE which is 19 digits long it wont fit int memory and we need to be able to run the program in multiple steps.

So I wrote a class that is used to set/clear bit in a file so the file, if you run the program up to Long.MAX_VALUE will be Long.MAX_VALUE / 8 size
I have already post that class in the CodeSnippet but here it is again so everything will be grouped together
import java.io.*;

/*
 * Mimics the behaviour of a Java standard API BitSet class but use a file as storage
 * (Usefull for the ones who wants to play with the Eratosthene sieve algorithm)
 */
public class BitSetFile {

	// to set or clear a bit
	static final int[] SET = {1, 2, 4, 8, 16, 32, 64, 128};
	// the file used to stored the bits
	protected RandomAccessFile raf;
	// lastByte accessed 
	private byte lastByteAccessed;
	// its position in file
	private long lastBytePosition = -1;
	/*
	 * Constructors that receive the File as parameter
	 */
	public BitSetFile(File file) throws IOException {
		raf = new RandomAccessFile(file, "rws");
	}
	
	/*
	 * returns the length in bytes
	 */
	public long length(){
		try {
			return raf.length();
		}
		catch(IOException e) {
			return 0;
		}
	}
	
	/*
	 * Set a bit
	 */
	public void set(long bit) throws IOException {
		set(bit, true);
	}
	/*
	 * Clear a bit
	*/
	public void clear(long bit) throws IOException {
		set(bit, false);
	}
	/*
	 * Clear or set the bit
	*/
	private void set(long bit, boolean state) throws IOException {
		long fileOffset = bit / 8;
		int bitOffset = (int) bit % 8; 
		
		// if it is not the same byte as the one I have in cache
		if(fileOffset != lastBytePosition) {
			raf.seek(fileOffset);	   // might throw an IOException
			// read the byte
			try {
				lastByteAccessed = raf.readByte();
			}
			catch(EOFException e) {  // but catch if after end of file	
				lastByteAccessed = 0;
			}				
			lastBytePosition = fileOffset;
		}
		// save stored byte (we wont update if it does not change)
		byte saved = lastByteAccessed;
		if(state)
			lastByteAccessed |= SET[bitOffset];
		else
			lastByteAccessed &= ~SET[bitOffset];
		// if it changes nothing exit
		if(saved == lastByteAccessed)
			return;
		// ok write it (that might throw an exception)
		writeByte(fileOffset, lastByteAccessed);
	}
	
	/*
	 * Returns if a bit is set or not
	 */
	public boolean get(long bit) throws IOException{
		// the byte to use
		long filePos = bit / 8;
		// if it is not the one already cached
		if(filePos != lastBytePosition) {
			raf.seek(filePos);	   // might throw an IOException
			// read the byte
			try {
					lastByteAccessed = raf.readByte();
			}
			catch(EOFException e) {  // but catch if after end of file	
				lastByteAccessed = 0;
			}				
			// in both case that will be the next position
			lastBytePosition = filePos;			
		}
		int bitOffset = (int) bit % 8;
		byte shifted = (byte) (lastByteAccessed >>> bitOffset);
		return (shifted & 1) == 1;
	}
	
	/*
	 *  return the last bit set 
	 *  -1 if none
	 */
	long getLastBitSet() throws IOException {
		long lastSet = length();
		// if file empty returns -1
		if(lastSet == 0)
			return -1L;
		// the max value will be 8 * that offset
		long maxVal = lastSet * 8;
		for(long i = maxVal; i >= 0; i--) {
			if(get(i))
				return i;
		}
		return -1;
	}
	
	/*
	 * Write the pending byte
	 */
	private void writeByte(long bytePos, byte byteToWrite) throws IOException{
		raf.seek(bytePos);
		raf.writeByte(byteToWrite);
	}
	/*
	 * Nicely close the file
	 * @return boolean if succeed or failed
	 */
	public boolean close() throws IOException {
		boolean success = true;
		try {
			raf.close();
		}
		catch(IOException e) {
			success = false;
		}
		
		raf = null;
		return success;
	}
	
	/*
	 * If no reference to this object try to close nicely the file
	 */
	protected void finalize() {
		if(raf == null)
			return;
		try {
			close();
		}
		catch(IOException e) {
		}
	}

	/*
	 * To unit test the whole thing with the sieve of Erathostene
	 */
	public static void main(String[] args) throws IOException {
		final int MAX = 32;
		BitSetFile bsf = new BitSetFile(new File("a.dat"));
		
		// set the first MAX odd to true (possible prime)
		for(long i = 1; i <= MAX; i += 2) {
			bsf.set(i);
		}
		// clear all the multiples of each prime
		for(long i = 3; i < MAX; i += 2) {
			if(bsf.get(i)) {
				// clear all the multiples... just make it for the first MAX
				for(long j = i; j < MAX; j += 2) {
					long val = i * j;
					// but break if we exceed MAX
					if(val > MAX)
						break;
					// if we wrap around (too big for a long)
					if(val < 0)
						break;
					bsf.clear(val);
				}
			}
		}
		// print the ones that are prime
		for(int i = 0; i <= MAX; i++) {
			if(bsf.get(i))
				System.out.println("" + i);
		}
		// printd the last one
		System.out.println("Last one in the list: " + bsf.getLastBitSet());
	}
}



Now having this, we need a few more things:
- just to for display it would be nice to have the number of primes found
- the last prime found (when we stop and restart the program)
- and actually there is no need to have in the BitSetFile all the even numbers (we can by that divide the size of the file by 2) and speed up access to it

So here is the RegisteringOddBitSetFile that extends BitSetFile but keeps only tract of the odd numbers.
The first 2 long of the file are reserved to keep: the number of primes found and the last prime found
Another feature of this class is that inverse the true/false concepts :) When a BitSet (or in this case a RandomAccessFile) is extended new bytes are set to 00 which is false. So in a normal BitSet operation (or in that case with the file) we have to set to true (or 1) all the bits in the extension as ptential primes. Inversing the clear/set operations makes that the file when extended (with all the bits to 0) will return true if they are tested.
So the clearBit() operation in that class calls the setBit() method in BitSetFile and vice-versa. And getBit() return false is the bit is set and true otherwise.
import java.io.*;

/*
 * This class extends BitSetFile but for the ones who like to play with the Eratosthene sieve
 * it presents the following fetaures:
 * - it only supports odd bit number (because we know that all even numbers can't be prime) so
 *   bit[0] -> 3  bit[1] -> 5  bit[2] -} 7... this reduce the size of the file by 2
 * - true and false are reverses so true is 0 and false is 1 so when bytes are appended (initialized to
 *   0 by the file system) these will be considered as true to potential prime numbers no need to reset
 *   them to true as in a regular BitSet
 * The first 2 longs in the file contain the number of prime already found and the last prime found
 * so when another run is performed:
 * - there is no need to scan the file to find the last prime found
 * - if the program is interrupted while filling the multiple of a prime it can be restart at that point
 */
public class RegisteringOddBitSetFile extends BitSetFile {

	public RegisteringOddBitSetFile(File file) throws IOException {
		super(file);
	}

	/*
	 * Set a bit (which means clear as set/clear are reversed)
	 */
	public void set(long bit) throws IOException {
		long realBit = (bit / 2L) + (16 * 8);
		if(realBit < 0) {
			System.out.println("set of " + bit + " exceed Long.MAX_VALUE");
			return;
		}
		super.clear(realBit);
	}
	/*
	 * Clear a bit (which means set as set/clear are reversed)
	*/
	public void clear(long bit) throws IOException {
		long realBit = (bit / 2L) + (16 * 8);
		if(realBit < 0) {
			System.out.println("clear of " + bit + " exceed Long.MAX_VALUE");
			return;
		}
		super.set(realBit);
	}

	public long length() {
		long nb = super.length() - 16;
		if(nb < 0)
			nb = 0;
		return nb;
	}
	/*
	 * Returns if a bit is set or not
	 */
	public boolean get(long bit) throws IOException{
		long realBit = (bit / 2L) + (16 * 8);
		// exceed Long.MAX_VALUE
		if(realBit < 0) {
			System.out.println("get of " + bit + " exceed Long.MAX_VALUE");
			return false;
		}
		return !super.get(realBit);
	}
	
	/*
	 * to write the number of prime found and the last prime found
	 */
	void writeNbAndLast(long nb, long last) throws IOException {
		raf.seek(0L);
		raf.writeLong(nb);
		raf.seek(8L);
		raf.writeLong(last);
	}
	
	/*
	 * the get the number of primes already found
	 */
	long getNb() throws IOException {
		raf.seek(0L);
		return raf.readLong();
	}
	/*
	 * get the last prime found
	 */
	long getLast() throws IOException {
		raf.seek(8L);
		return raf.readLong();
	}
}


Now to display the primes... 2 JSpinners. One PrimeSpinner allows to display all the primes in the file. The second PrimeSpinnerRange is used to set, by 100,000 increments which should be the first prime displayed by PrimeSpinner
The Spinner need the value and a String represention of the numbers they display... So we need first 2 little class
One to format the long into coma separated String like 1,234,567,789
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;

/*
 * One static class to format Long
 */
public class FormatLong {

	private DecimalFormat decimalFormat;
	
	FormatLong() {
		// The decimal format used for big numbers
		decimalFormat = new DecimalFormat("#,###,###,###,###,###,##0");
		// the format of our long
		DecimalFormatSymbols dfc = decimalFormat.getDecimalFormatSymbols();
		dfc.setGroupingSeparator(',');
		decimalFormat.setDecimalFormatSymbols(dfc);	
	}
	// to return 
	String format(long n) {
		return decimalFormat.format(n) + " ";
	}
}


And one that represent they "objects" displayed by the JSpinners

/*
 * The data object displayed by the spinners
 */
public class SpinnerData {
	long value;
	String output;
	static FormatLong fm = new FormatLong();
	
	SpinnerData(long n) {
		value = n;
		output = fm.format(n);
	}
	public String toString() {
		return output;
	}
}



And finally the 2 spinners
import java.io.*;
import javax.swing.*;

/*
 * A Spinner to display the primes its model uses a RegisteringOddBitSetFile to return Primes
 */
public class PrimeSpinner extends JSpinner {

	// the max in the file
	private long max;
	// the file itself with it's getter/setter
	RegisteringOddBitSetFile obsf;
	// the component showed
	JLabel label;
	// my model
	PrimeSpinnerModel model;
	
	// constructor disable it until we get the file
	PrimeSpinner() {
		setEnabled(false);
		model = new PrimeSpinnerModel(this);
		setModel(model);
		label = new JLabel();
		label.setHorizontalAlignment(JLabel.RIGHT);
		setEditor(label);
	}

	/*
	 * Once the file has been opened you pass the obsf and the first max
	 */
	void setFile(RegisteringOddBitSetFile obsf, long max) {
		this.max = max;
		this.obsf = obsf;
		label.setText("3  ");
		setEnabled(true);
	}

	/*
	 * To set the new max
	 */
	void setMax(long max) {
		this.max = max;
	}
	
	// the other spinner asked me to show starting at sd
	void setStartingValue(SpinnerData sd) {
		model.setStartingValue(sd.value + 1);
	}

	class PrimeSpinnerModel extends AbstractSpinnerModel {
		// the current that I display
		private SpinnerData current;
		private SpinnerData lastNextValue, lastPreviousValue;

		JSpinner father;
		PrimeSpinnerModel(JSpinner father) {
			this.father = father;
			current = new SpinnerData(3L);
			lastNextValue = new SpinnerData(0);
			lastPreviousValue = new SpinnerData(0);
		}
		
		public Object getPreviousValue() {
			try {
				for(long i = current.value+2; i <= max; i += 2) {
					if(obsf.get(i)) {
						if(i != lastPreviousValue.value)
							lastPreviousValue = new SpinnerData(i);
						return lastPreviousValue;
					}
				}
			}
			catch(IOException e) {
			}
			return null;
		}

		public Object getNextValue() {
			if(current.value == 3)
				return null;
			try {
				for(long i = current.value-2; i >= 3; i -= 2) {
					if(obsf.get(i)) {
						if(i != lastNextValue.value)
							lastNextValue = new SpinnerData(i);
						return lastNextValue;
					}
				}
			}
			catch(IOException e) {
			}
			return null;
		}

		public Object getValue() {
			return current;
		}

		@Override
		public void setValue(Object value) {
			current = (SpinnerData) value;
			label.setText(current.toString() + "  ");
		}
		
		/*
		 * Show values from
		 */
		void setStartingValue(long from) {
			try {
				// search the first prime from from
				for(long i = from; i <= max; i += 2) {
					if(obsf.get(i)) {
						setValue(new SpinnerData(i));
						return;	// done
					}
				}
			}
			catch(IOException e) {
			}
		}
	}
}


import javax.swing.*;
import java.awt.*;

/*
 * A Spinner to display to display the starting value for the other one
 */
public class PrimeSpinnerRange extends JSpinner {

	// the max in the file
	private long max = 1;
	// the component showed
	JLabel label;
	SpinnerPanel fatherPanel;
	
	// constructor disable it until we get the file
	PrimeSpinnerRange(SpinnerPanel father) {
		fatherPanel = father;
		setModel(new RangeModel(this));
		label = new JLabel("0  ");
		label.setHorizontalAlignment(JLabel.RIGHT);
		label.setForeground(Color.RED);
		setEditor(label);
		setEnabled(false);
	}

	/*
	 * To set the new max
	 */
	void setMax(long max) {
		this.max = max;
	}

	class RangeModel extends AbstractSpinnerModel {
		// the current that I display
		private SpinnerData current;
		private SpinnerData lastNextValue, lastPreviousValue;

		JSpinner father;
		RangeModel(JSpinner father) {
			this.father = father;
			current = new SpinnerData(0L);
			lastNextValue = new SpinnerData(0);
			lastPreviousValue = new SpinnerData(0);
		}
		
		public Object getNextValue() {
			if(current.value == 0)
				return null;
			long value = current.value - 100000;
			if(value != lastNextValue.value)
				lastNextValue = new SpinnerData(value);
			return lastNextValue;
		}

		public Object getPreviousValue() {
			long value = current.value + 100000L;
			value /= 100000L;
			value *= 100000L;
			if(value > max)
				return null;
			if(value != lastPreviousValue.value)
				lastPreviousValue = new SpinnerData(value);
			return lastPreviousValue;
		}

		public Object getValue() {
			return current;
		}

		@Override
		public void setValue(Object value) {
			long currentValue = ((SpinnerData) value).value;			
			current = new SpinnerData(currentValue);
			label.setText(current.toString() + "  ");
			fatherPanel.rangeChanged(current);
		}
	}
}


Now a JPanel to hold the 2 spinners and allow the GUI to infoirm the spinner if a new prime is found
import javax.swing.*;
import javax.swing.event.*;

import java.awt.*;

/*
 * Hold the spinners
 */
public class SpinnerPanel extends JPanel {

	private PrimeSpinner primeSpinner;
	private PrimeSpinnerRange primeSpinnerRange;
	
	SpinnerPanel() {
		super(new GridLayout(1, 3, 3, 3));
		JLabel label = new JLabel("Display primes from:");
		label.setHorizontalAlignment(JLabel.CENTER);
		add(label);
		primeSpinnerRange = new PrimeSpinnerRange(this);
		primeSpinner = new PrimeSpinner();
		add(primeSpinnerRange);
		add(primeSpinner);
	}

	/*
	 * To enable/disable the primeSpinner 
	 */
	void setEnableSpinner(boolean state) {
		primeSpinner.setEnabled(state);
		primeSpinnerRange.setEnabled(state);
	}
	/*
	 * Once the file has been opened you pass the obsf and the first max
	 */
	void setFile(RegisteringOddBitSetFile obsf, long max) {
		primeSpinner.setFile(obsf, max);
		primeSpinnerRange.setMax(max);
		primeSpinnerRange.setEnabled(true);
	}

	/*
	 * A new prime has been found we inform the spinners of this new maximum
	 */
	void setMax(long max) {
		primeSpinner.setMax(max);
		primeSpinnerRange.setMax(max);
	}
	/*
	 * Change in the PrimeSpinnerRange
	 */	
	void rangeChanged(SpinnerData sd) {	
		primeSpinner.setStartingValue(sd);
	}
}

Attached image(s)

  • Attached Image

This post has been edited by pbl: 22 December 2009 - 07:35 PM

Was This Post Helpful? 1
  • +
  • -

#6 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8332
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Re: Prime Generator Timing

Posted 22 December 2009 - 07:36 PM

And finally the GUI to hold the whole thing
It has a FileChooser to select a file (previous one or old file)
RadioButtons to specify how many new number to check from 1,000 to 1,000,000 but feel free to change it
ProgressBar that shows what is is doing
The program might be stopped when you want. It will restart at the last prime found
import java.awt.*;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;

import java.awt.event.*;
import java.io.*;

public class PrimeGui extends JFrame implements ActionListener {

	JButton btnOpenFile, btnGo;
	JLabel fileLabel, sizeLabel, statusLabel, lastFoundLabel, nbFoundLabel, timeLabel;
	// title label
	JLabel clearingLabel, searchingLabel;
	// title default header
	String clearingLabelText = "Clearing this prime multiples";
	String searchingLabelText = "Searching primes progress";
	
	JRadioButton[] radioBtn;
	// values of the RadioButton (Caution: must be multiple of 2)
	int[] radioValue = {1000, 10000, 100000, 1000000, 10000000};
	// the file to store the find numbers
	RegisteringOddBitSetFile obsf;
	// the numbers of new numbers to find
	int nbToCheck;
	// length of file
	long fileSize;
	// the last found for sure
	long lastFound, nbFound;
	// timer to update GUI
	Timer timer;
	// if the thread is running
	boolean threadRunning;
	// if multipes of new prime need to be check in the rest of the file
	boolean fillStillPossible;
	// the text of the statusLabel
	String statusLabelText;
	// time we start
	long startTime;
	// The JProgress bar for the filling and the searching
	JProgressBar progressBarFilling = new JProgressBar();
	JProgressBar progressBarSearching = new JProgressBar();
	// LighYellow color
	Color lightYellow = new Color(255, 255, 120);
	// to format my long
	FormatLong fl = new FormatLong();
	SpinnerPanel spinnerPanel;


	PrimeGui() {
		super("Prime Numbers calculator");
		setSize(700, 380);		
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);


		// master panel inside the frame
		JPanel masterPanel = new JPanel(new GridLayout(11, 1, 3, 3));
		// the buttons panel
		JPanel p = new JPanel(new GridLayout(1, 3, 3, 3 ));
		// the file chooser
		btnOpenFile = new JButton("Open file");
		btnOpenFile.addActionListener(this);
		// just to center the button
		Box box = new Box(BoxLayout.X_AXIS);
		box.add(Box.createHorizontalGlue());
		box.add(btnOpenFile);
		box.add(Box.createHorizontalGlue());
		p.add(box);
		// the label for the time elapsed
		timeLabel = new JLabel("");
		timeLabel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
		timeLabel.setHorizontalAlignment(JLabel.CENTER);
		p.add(timeLabel); 
		// the button to start the process
		btnGo = new JButton("Go");
		btnGo.setEnabled(false);
		btnGo.addActionListener(this);
		box = new Box(BoxLayout.X_AXIS);
		box.add(Box.createHorizontalGlue());
		box.add(btnGo);
		box.add(Box.createHorizontalGlue());
		p.add(box);
		masterPanel.add(p);

		// the file name 
		fileLabel = new JLabel("File:");
		fileLabel.setHorizontalAlignment(JLabel.CENTER);
		masterPanel.add(fileLabel);

		p = new JPanel(new GridLayout(1, 6, 3, 3));
		p.add(centeredLabel("File size:"));
		sizeLabel = new JLabel();
		sizeLabel.setHorizontalAlignment(JLabel.CENTER);
		p.add(sizeLabel);
		p.add(centeredLabel("Last found:"));
		lastFoundLabel = new JLabel();
		lastFoundLabel.setHorizontalAlignment(JLabel.CENTER);
		p.add(lastFoundLabel);
		p.add(centeredLabel("Nb found:"));
		nbFoundLabel = new JLabel();
		nbFoundLabel.setHorizontalAlignment(JLabel.CENTER);
		p.add(nbFoundLabel);
		masterPanel.add(p);

		// the number of new numbers to check
		JLabel label = new JLabel("Number of new numbers to check");
		label.setHorizontalAlignment(JLabel.CENTER);
		masterPanel.add(label);

		// the radio and the go
		p = new JPanel(new GridLayout(1,radioValue.length, 3, 3));
		radioBtn = new JRadioButton[radioValue.length];
		ButtonGroup bg = new ButtonGroup();
		for(int i = 0; i < radioBtn.length; i++) {
			radioBtn[i] = new JRadioButton(fl.format(radioValue[i]));
			bg.add(radioBtn[i]);
			radioBtn[i].addActionListener(this);
			radioBtn[i].setEnabled(false);
			radioBtn[i].setForeground(Color.BLUE);
			p.add(radioBtn[i]);
		}
		masterPanel.add(p);

		// the Progress Bars
		// the one used to show the progress while serachiing fro prime
		searchingLabel = borderedLabel(searchingLabelText);
		masterPanel.add(searchingLabel);
		progressBarSearching.setBorderPainted(true);
		progressBarSearching.setMinimum(0);
		progressBarSearching.setForeground(Color.BLUE);
		masterPanel.add(progressBarSearching);
		// the one used to show the progress when clearing the multiples of a prime in the rest of the file
		clearingLabel = borderedLabel(clearingLabelText);
		masterPanel.add(clearingLabel);		
		progressBarFilling.setBorderPainted(true);
		progressBarFilling.setMinimum(0);
		progressBarFilling.setForeground(Color.BLUE);
		masterPanel.add(progressBarFilling);

		// The spinners
		spinnerPanel = new SpinnerPanel();
		masterPanel.add(spinnerPanel);
		
		// the status label
		statusLabel = new JLabel("Select a file to start the application.");
		masterPanel.add(statusLabel);
		
		// add the panel to the frame
		add(new JLabel(" "), BorderLayout.NORTH);
		add(new JLabel("   "), BorderLayout.EAST);
		add(new JLabel("   "), BorderLayout.WEST);
		add(masterPanel, BorderLayout.CENTER);

	}

	// opening the file (initiliae it if required)
	private void openFile(File file) {
		try {
			// create the OddBitSetFile to stored the found primes
			obsf = new RegisteringOddBitSetFile(file);
			fileSize = obsf.length();
			// update the 2 labels about name and size
			fileLabel.setText("File: " + file);
			sizeLabel.setText("" + fileSize);		
		}
		catch(IOException e) {
			statusLabel.setText("Problem opening file: " + e);
			return;
		}

		// if new file init it
		if(fileSize == 0) {
			// the first non primes it the first byte to create the file
			int[] firstNonPrime = {9, 15};
			try {
				// adding the first prime in the first byte
				for(int i = 0; i < firstNonPrime.length; i++)
					obsf.clear((long) firstNonPrime[i]);
				fileSize = obsf.length();
				// the number of prime is 8 - the non prime + 2 for 1 and 2
				nbFound =  8 - firstNonPrime.length + 2; 
				// first byte contains 3,5,7,9,11,13,15,17
				lastFound = 17L;	
				obsf.writeNbAndLast(nbFound, lastFound);
			}
			catch(IOException e) {
				statusLabel.setText("Init file error: " + e);
				return;
			}
		}
		// read where I am at
		try {
			lastFound = obsf.getLast();
			nbFound = obsf.getNb();
			lastFoundLabel.setText(fl.format(lastFound));
			nbFoundLabel.setText(fl.format(nbFound));
		}
		catch (IOException e) {
			statusLabel.setText("The file does not seem to be a valid prime data file");
			return;
		}

		// enable the JRadioButton
		for(int i = 0; i < radioBtn.length; i++) 
			radioBtn[i].setEnabled(true);
		statusLabel.setText("Select the number of numbers to check");
		
		// model for the Spinner
		spinnerPanel.setFile(obsf, lastFound);
	}

	/*
	 * A centered label with a border
	 */
	private JLabel borderedLabel(String str) {
		JLabel label = centeredLabel(str);
		label.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		label.setOpaque(true);
		label.setBackground(lightYellow);
		label.setForeground(Color.BLUE);
		return label;
	}
	/*
	 * Returns a centered JLabel from a String
	 */
	private JLabel centeredLabel(String str) {
		JLabel label = new JLabel(str);
		label.setHorizontalAlignment(JLabel.CENTER);
		label.setForeground(Color.BLUE);
		return label;
	}
	public void actionPerformed(ActionEvent e) {
		Object o = e.getSource();
		// if it is the timer (will be the one calling more often)
		if(o == timer) {
			// update the JLabel
			sizeLabel.setText(fl.format(fileSize));
			lastFoundLabel.setText(fl.format(lastFound));
			nbFoundLabel.setText(fl.format(nbFound));
			statusLabel.setText(statusLabelText);
			timeLabel.setText(formatSince(startTime));
			repaint();
			// if the thread has finished
			if(!threadRunning) {
				// no need to continue to update the display
				timer.stop();
				btnGo.setEnabled(true);
				searchingLabel.setText(searchingLabelText);
				clearingLabel.setText(clearingLabelText);
				// we can re-emable the radio buttons
				for(int i = 0; i < radioBtn.length; i++)
					radioBtn[i].setEnabled(true);
				// re-enable spinner access
				spinnerPanel.setEnableSpinner(true);
				// clear last message
				statusLabel.setText("Press GO button to start");
				// clear the seraching progress bar
				progressBarSearching.setValue(0);
			}
			return;
		}

		// button to open file
		if(o == btnOpenFile) {
			JFileChooser chooser = new JFileChooser();
			// allow only to select 1 file
			chooser.setMultiSelectionEnabled(false);
			// our types .dat and .prim
			FileNameExtensionFilter filter = new FileNameExtensionFilter(
					"Data file", "dat", "prim");
			chooser.setFileFilter(filter);
			// check if a file was selected
			int returnVal = chooser.showOpenDialog(this);
			if(returnVal == JFileChooser.APPROVE_OPTION) {
				// if it is the case andd it is not a directory
				openFile(chooser.getSelectedFile());
			}
			return;
		}

		// test if radioButtons
		for(int i = 0; i < radioBtn.length; i++) {
			if(radioBtn[i] == o) {
				nbToCheck = radioValue[i];
				btnGo.setEnabled(true);
				statusLabel.setText("Press GO button to start");
				return;
			}
		}

		// test if it is the go button
		if(btnGo == o) {
			// disable spinners file chooser
			spinnerPanel.setEnableSpinner(false);
			btnOpenFile.setEnabled(false);
			btnGo.setEnabled(false);
			for(int i = 0; i < radioBtn.length; i++)
				radioBtn[i].setEnabled(false);
			// start the thread that calculates the prime numbers
			Thread t = new Calculate();
			t.start();
			// starts the timer that updates the GUI
			timer = new Timer(500, this);
			timer.start();
			startTime = System.currentTimeMillis();
			return;
		}
	}

	/*
	 * Dump the content of the file (for debug purpose)
	 */
	private void dump(long to, String title) {
		System.out.println("Dump " + title + " from 3 to " + lastFound);
		try {
			StringBuilder str = new StringBuilder(100);
			for(long i = 3; i <= to; i += 2) {
				if(obsf.get(i)) {
					str.append(fl.format(i));	

					if(str.length() >= 80) {
						System.out.println(str);
						str = new StringBuilder(100);
					}
				}
			}
			System.out.println(str);
		}
		catch(IOException e) {
		}
	}

	/*
	 * Format the time elapsed since the time passed as parameter
	 */
	private String formatSince(long begin) {
		int delta = (int) (System.currentTimeMillis() - begin);
		int milli = delta % 1000;
		delta /= 1000;
		int second = delta % 60;
		delta /= 60;
		int minute = delta % 60;
		delta /= 60;
		return String.format("%d:%02d:%02d.%03d", delta, minute, second, milli);
	}
	// to stat the whole thing
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new PrimeGui().setVisible(true);
			}
		});
	}


	// the inner class that actually perform the calculation
	class Calculate extends Thread {

		// to run in a different thread to the GUI can be updated correctly
		public void run() {
			threadRunning = true;

			// update our target based on the last found
			long target = nbToCheck + lastFound;
			if(target < 0) {				 // wrap around number to big
				target = Long.MAX_VALUE;
				if(target % 2 == 0)
					target--;
			}
			// prepare the progress bar for searching
			long from = lastFound + 2;
			int nb = (int) (target - from) / 2;
			progressBarSearching.setMaximum(nb);
			progressBarSearching.setValue(0);
			searchingLabel.setText("Searching primes from " + fl.format(from) + " to " + fl.format(target));
			// clear the non primes to the end of the new file 
			try {
				clearFileExtensionWithOldPrimes(target);
			}
			catch(IOException e) {
				System.out.println("IOException clearing the none primes in the extendion: " + e);
				threadRunning = false;
				return;
			}

			// now let's find new primes to register them and fill their multiples
			fillStillPossible = true;
			nb = 0;
			try {
				for(long i = from; i <= target; i += 2) {
					progressBarSearching.setValue(++nb);
					// if not prime continue
					if(!obsf.get(i))
						continue;
					if(fillStillPossible) {
						statusLabelText = fl.format(i) + "is prime. Filling the rest of the file...";
						// we have to fill the extension of the file with all the multiples
						// of this new found number from 3 to target / that number
						clearFileWithNewPrime(i, target);
						progressBarFilling.setValue(0);
					}
					else {
						statusLabelText = "The multiples of " + fl.format(i) +"exceed the file capacity.";						
					}
					// save the new prime found in file has it's multiples are cleared
					lastFound = i;
					nbFound++;
					obsf.writeNbAndLast(nbFound, lastFound);
					spinnerPanel.setMax(lastFound);
				}
			}
			catch(IOException e) {
				System.out.println("Exception updating multiples of " + lastFound + ": " + e);
				threadRunning = false;
				return;
			}
			threadRunning = false;
		}


		/*
		 * Clear in the rest of the file all the multiples of the new prime found
		 * return true if we filled some
		 */
		private void clearFileWithNewPrime(long prime, long target) throws IOException{
			// we will have to clear all the multiple of prime from 3
			// to target / prime
			statusLabelText = "";
			long to = target / prime;
			if(to % 2 == 0)
				to--;
			// any multiplication of the prime won't fit in the file
			if(to < 3) {
				clearingLabel.setText("");
				fillStillPossible = false;			// no need to call this method again
				return;
			}
			// number of iterations for the progress bar
			int nb = (int) (to - 3) + 1;
			progressBarFilling.setMaximum(nb);
			clearingLabel.setText("Setting as non prime the " + fl.format(nb) + " multiples of " + 
					fl.format(prime) + " up to " + fl.format(to));
			
			int n = 0;
			for(long i = 3; i <= to; i += 2) {
				// test if it is prime
				if(obsf.get(i)) {
					obsf.clear(i * prime);
				}
				progressBarFilling.setValue(++n);
			}
			clearingLabel.setText("");
			return;
		}
		/*
		 * Clear the non prime in the extended part of the file with the primes
		 * already registered
		 */
		private void clearFileExtensionWithOldPrimes(long target) throws IOException {

			for(long i = 3; i <= lastFound; i += 2) {
				// if not prime continue
				if(!obsf.get(i)) {
					continue;
				}
				// calculate the range to fill
				FillExtension fe = new FillExtension(i, lastFound, target);
				// none to fill for that one I can ignore the others which are bigger
				int nb = fe.nb;
				if(nb == 0)
					break;
				nb /= 2;
				nb++;
				clearingLabel.setText("Setting as non prime the " + fl.format(nb) + " multiples of " + 
							   fl.format(i) + " up to " + fl.format(target) + " in the new file extension");
				fe.fill(obsf);
			}
		}
	}

	/*
	 * This class is used to clear the non Prime numbers in the new extension of the file
	 * For the from value:
	 * ------------------
	 * Let's say the file contains already the prime numbers up to 37
	 * For 3 I dont'have to do
	 * 3 * 3 = 9  3 * 5 = 15  3 * 7 = 21  3 * 9 = 27 3 * 11 = 33 should start with 3 * 13 = 39
	 * 37 / 3 = 12 I should start with 13
	 * For 5 I don't have to do
	 * 5 * 5 = 25  5 * 7 = 35 should start with 5 * 9 = 45
	 * 39 / 5 = 7 I should start with 9
	 * For 7 
	 * I should start with 7 * 7 = 49
	 * 39 / 7 = 5 and I should start with 7
	 * For 11
	 * I should start with 11 * 11 = 121
	 * 39 / 11 = 3 and I should start with 11
	 * 
	 * From these I think we can conclude to these empriric rules:
	 * - divide the number of the last prime by the prime to fill
	 * - if the result of the division is even add 1 to that number
	 * - if the result of the division is odd add 2 to that number
	 * - if that number is < to the original prime use that original prime
	 * 
	 * Now for the end:
	 * ---------------
	 * A lot simpler: just divide the target by the prime... if that number is even subtract 1 to it
	 */
	class FillExtension {
		// the prime for which we will perform the operation
		long prime;
		//  by wich numbers we will multiply it
		long from, to;
		// number to do
		int nb;

		FillExtension(long prime, long lastPrime, long target) {
			this.prime = prime;
			// find from where to start
			from = lastPrime / prime;
			// if odd add 1
			if(from % 2 == 0)
				from++;
			else // if even add 2
				from += 2;
			// if smaller than original one
			if(prime > from)
				from = prime;		// take that one
			// the end
			to = target / prime;
			if(to % 2 == 0)
				to--;
			// number to to
			nb = (int) (to - from);   // will yield .... -4, -2, 0, 2, 4, ....
			// from 15 to 15 means 1
			nb += 2;
			// if from > to nothing to to
			if(nb < 0)
				nb = 0;
			nb /= 2;	   // number of time the loop will be executed
			// prepare the JProgressBar
			progressBarFilling.setValue(0);
			if(nb > 0) {
				progressBarFilling.setMaximum(nb);
			}			
		}

		// method to fill the file and the progress bar
		void fill(RegisteringOddBitSetFile file) {
			int n = 0;
			try {
				for(long i = from; i <= to; i += 2) {
					long val = i * prime;
					file.clear(val);
					progressBarFilling.setValue(++n);
					// update fileSize (not really OO) only for 3
					if(prime == 3)
						fileSize = file.length();
				}
			}
			catch(IOException e) {
				System.out.println("Problem filling the new area " + e);
			}
		}
	}
}



Was This Post Helpful? 1
  • +
  • -

#7 nick2price  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 562
  • View blog
  • Posts: 2,826
  • Joined: 23-November 07

Re: Prime Generator Timing

Posted 22 December 2009 - 07:42 PM

Show off ;) Nice work.
Was This Post Helpful? 0
  • +
  • -

#8 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8332
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Re: Prime Generator Timing

Posted 22 December 2009 - 08:09 PM

View Postnick2price, on 22 Dec, 2009 - 06:42 PM, said:

Show off ;) Nice work.

Was actually fun to do
First time I was using JSpinner never understood how the ChangeListener on them really worked... to I just used their setEditor() method and updated the JLabel "by hand"
Was This Post Helpful? 0
  • +
  • -

#9 LynnL  Icon User is offline

  • D.I.C Head

Reputation: 21
  • View blog
  • Posts: 109
  • Joined: 13-April 09

Re: Prime Generator Timing

Posted 23 December 2009 - 09:40 PM

Very very good show PBL :^:

I ran the application a few times with one million increment and gheeez it goes fast

But I have a question:
When you start the application, there is the step when you fill/clear the new extended file with the non-prime numbers

Now, at the beginning the progress bar goes quite fast and then it slows down at the end
What would explain that behavior ? I am just curious
Was This Post Helpful? 0
  • +
  • -

#10 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8332
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Re: Prime Generator Timing

Posted 23 December 2009 - 09:49 PM

View PostLynnL, on 23 Dec, 2009 - 08:40 PM, said:

Very very good show PBL :^:

I ran the application a few times with one million increment and gheeez it goes fast

But I have a question:
When you start the application, there is the step when you fill/clear the new extended file with the non-prime numbers

Now, at the beginning the progress bar goes quite fast and then it slows down at the end
What would explain that behavior ? I am just curious

That may explain it... I hope :D
The way I wrote it, when you set a bit to true/false in the file the RegisteringBitSetFile class as first to read the BYTE containing that number fromn the file and then set/clear the bit
It then check if the bit status has changed
If the bit status hasn't changed it does not bother writting it back to file
So when you extend the file, sure that all multiples of 3 will be cleared but when you clear the multiples of 5 a few of them will always been done and even more multiple of 7 and then 11
So it is normal that the progress bar goes faster from one prime to the other
Was This Post Helpful? 1
  • +
  • -

#11 LynnL  Icon User is offline

  • D.I.C Head

Reputation: 21
  • View blog
  • Posts: 109
  • Joined: 13-April 09

Re: Prime Generator Timing

Posted 26 December 2009 - 10:31 PM

OK another question
What happens if you stop the program when it fills the "non prime" ? (the multiples) of the registered prime

I also change to "new prime" check for 10,000,000 it seems to take long
How long does it take on your computer?
Was This Post Helpful? 0
  • +
  • -

#12 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8332
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Re: Prime Generator Timing

Posted 26 December 2009 - 10:42 PM

View PostLynnL, on 26 Dec, 2009 - 09:31 PM, said:

OK another question
What happens if you stop the program when it fills the "non prime" ? (the multiples) of the registered prime

As the last prime registered is in the file, the process will be performed again
Better to stop it while searching for new primes

View PostLynnL, on 26 Dec, 2009 - 09:31 PM, said:

I also change to "new prime" check for 10,000,000 it seems to take long
How long does it take on your computer?

Depends where you are at.... just tested it
On my Toshiba laptop, which is 2 years old with a dual Pentium T3400 it takes 8h00 for a 10,000,000 run
May be I should add a slider for how often the GUI should be refreshed... that must take much of the time
Was This Post Helpful? 1
  • +
  • -

#13 LynnL  Icon User is offline

  • D.I.C Head

Reputation: 21
  • View blog
  • Posts: 109
  • Joined: 13-April 09

Re: Prime Generator Timing

Posted 29 December 2009 - 09:49 PM

I have almost reach Long.MAX_VALUE in the file
now if I want to see the registered primes, using your spinner that increments by 100,000 it takes a while
how can I do faster
Was This Post Helpful? 0
  • +
  • -

#14 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8332
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Re: Prime Generator Timing

Posted 29 December 2009 - 10:16 PM

View PostLynnL, on 29 Dec, 2009 - 08:49 PM, said:

I have almost reach Long.MAX_VALUE in the file
now if I want to see the registered primes, using your spinner that increments by 100,000 it takes a while
how can I do faster

Never though this prime stuff would last for so long.... should really put it in the Snippet so anyhow this is a re-vamped version
Forget all the other "spinner" related classes
Here is a new PrimeGui class and a SpinnerPannel class
package prime;

import java.awt.*;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;

import java.awt.event.*;
import java.io.*;

public class PrimeGui extends JFrame implements ActionListener {

	JButton btnOpenFile, btnGo;
	JLabel fileLabel, sizeLabel, statusLabel, lastFoundLabel, nbFoundLabel, timeLabel, notPrimeLabel;
	// title label
	JLabel clearingLabel, searchingLabel;
	// title default header
	String clearingLabelText = "Clearing this prime multiples";
	String searchingLabelText = "Searching primes progress";

	JRadioButton[] radioBtn;
	// values of the RadioButton (Caution: must be multiple of 2)
	int[] radioValue = {1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
	// the file to store the find numbers and a readOnly for the displayed prime
	RegisteringOddBitSetFile obsf, obsfRead;
	// the numbers of new numbers to find
	int nbToCheck;
	// length of file
	long fileSize;
	// the last found for sure
	long lastFound, nbFound;
	// timer to update GUI
	Timer timer;
	// if the thread is running
	boolean threadRunning;
	// if multipes of new prime need to be check in the rest of the file
	boolean fillStillPossible;
	// the text of the statusLabel
	String statusLabelText, notPrimeTxt;
	// time we start
	long startTime;
	// The JProgress bar for the filling and the searching
	JProgressBar progressBarFilling = new JProgressBar();
	JProgressBar progressBarSearching = new JProgressBar();
	// LighYellow color
	Color lightYellow = new Color(255, 255, 120);
	// to format my long
	FormatLong fl = new FormatLong();

	// the spinner
	SpinnerPanel spinnerPanel;
	// the displayed prime
	long displayedPrime;
	JLabel displayedPrimeLabel;


	PrimeGui() {
		super("Prime Numbers calculator");
		setSize(700, 420);		
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

		// master panel inside the frame
		JPanel masterPanel = new JPanel(new GridLayout(13, 1, 3, 3));
		// the buttons panel
		JPanel p = new JPanel(new GridLayout(1, 3, 3, 3 ));
		// the file chooser
		btnOpenFile = new JButton("Open file");
		btnOpenFile.setBackground(Color.YELLOW);
		btnOpenFile.addActionListener(this);
		// just to center the button
		Box box = new Box(BoxLayout.X_AXIS);
		box.add(Box.createHorizontalGlue());
		box.add(btnOpenFile);
		box.add(Box.createHorizontalGlue());
		p.add(box);
		// the label for the time elapsed
		timeLabel = new JLabel("");
		timeLabel.setBorder(BorderFactory.createLineBorder(Color.BLUE));
		timeLabel.setHorizontalAlignment(JLabel.CENTER);
		p.add(timeLabel); 
		// the button to start the process
		btnGo = new JButton("Go");
		btnGo.setEnabled(false);
		btnGo.setForeground(Color.RED);
		btnGo.addActionListener(this);
		box = new Box(BoxLayout.X_AXIS);
		box.add(Box.createHorizontalGlue());
		box.add(btnGo);
		box.add(Box.createHorizontalGlue());
		p.add(box);
		masterPanel.add(p);

		// the file name 
		fileLabel = new JLabel("File:");
		fileLabel.setHorizontalAlignment(JLabel.CENTER);
		masterPanel.add(fileLabel);

		p = new JPanel(new GridLayout(1, 6, 3, 3));
		p.add(centeredLabel("File size:"));
		sizeLabel = new JLabel();
		sizeLabel.setHorizontalAlignment(JLabel.CENTER);
		p.add(sizeLabel);
		p.add(centeredLabel("Last found:"));
		lastFoundLabel = new JLabel();
		lastFoundLabel.setHorizontalAlignment(JLabel.CENTER);
		p.add(lastFoundLabel);
		p.add(centeredLabel("Nb found:"));
		nbFoundLabel = new JLabel();
		nbFoundLabel.setHorizontalAlignment(JLabel.CENTER);
		p.add(nbFoundLabel);
		masterPanel.add(p);

		// the number of new numbers to check
		JLabel label = borderedLabel("Number of new numbers to check");
		label.setForeground(Color.RED);
		masterPanel.add(label);

		// the radio and the go
		Box b = new Box(BoxLayout.X_AXIS);
		radioBtn = new JRadioButton[radioValue.length];
		ButtonGroup bg = new ButtonGroup();
		for(int i = 0; i < radioBtn.length; i++) {
			radioBtn[i] = new JRadioButton(fl.format(radioValue[i]));
			bg.add(radioBtn[i]);
			radioBtn[i].addActionListener(this);
			radioBtn[i].setEnabled(false);
			radioBtn[i].setForeground(Color.BLUE);
			b.add(radioBtn[i]);
		}
		masterPanel.add(b);

		// the Progress Bars
		// the one used to show the progress while serachiing fro prime
		searchingLabel = borderedLabel(searchingLabelText);
		masterPanel.add(searchingLabel);
		progressBarSearching.setBorderPainted(true);
		progressBarSearching.setMinimum(0);
		progressBarSearching.setForeground(Color.BLUE);
		masterPanel.add(progressBarSearching);
		// the one used to show the progress when clearing the multiples of a prime in the rest of the file
		clearingLabel = borderedLabel(clearingLabelText);
		masterPanel.add(clearingLabel);		
		progressBarFilling.setBorderPainted(true);
		progressBarFilling.setMinimum(0);
		progressBarFilling.setForeground(Color.BLUE);
		masterPanel.add(progressBarFilling);

		// the not prime cleared (centered with empty JLabels)
		p = new JPanel(new GridLayout(1,4));
		p.add(new JLabel(""));
		p.add(centeredLabel("Last non prime cleared:"));
		notPrimeLabel = new JLabel();
		p.add(notPrimeLabel);
		p.add(new JLabel());
		masterPanel.add(p);

		// The displayed prime and the spinners
		p = new JPanel(new GridLayout(1,2, 2, 2));
		p.add(centeredLabel("Primes list (use +/- spinners to update):"));
		displayedPrimeLabel = new JLabel("");
		displayedPrimeLabel.setHorizontalAlignment(JLabel.CENTER);
		displayedPrimeLabel.setForeground(Color.BLUE);
		p.add(displayedPrimeLabel);
		p.setBorder(BorderFactory.createLineBorder(Color.BLUE));
		masterPanel.add(p);
		// the spinners
		spinnerPanel = new SpinnerPanel(this);
		masterPanel.add(spinnerPanel);

		// the status label
		statusLabel = new JLabel("Select a file to start the application.");
		masterPanel.add(statusLabel);

		// add the panel to the frame
		add(new JLabel(" "), BorderLayout.NORTH);
		add(new JLabel("   "), BorderLayout.EAST);
		add(new JLabel("   "), BorderLayout.WEST);
		add(masterPanel, BorderLayout.CENTER);

	}

	// opening the file (initiliae it if required)
	private void openFile(File file) {
		try {
			// create the OddBitSetFile to stored the found primes
			obsf = new RegisteringOddBitSetFile(file, false);
			obsfRead = new RegisteringOddBitSetFile(file, true);
			fileSize = obsf.length();
			// update the 2 labels about name and size
			fileLabel.setText("File: " + file);
			sizeLabel.setText("" + fileSize);		
		}
		catch(IOException e) {
			statusLabel.setText("Problem opening file: " + e);
			return;
		}

		// if new file init it
		if(fileSize == 0) {
			// the first non primes it the first byte to create the file
			int[] firstNonPrime = {9, 15};
			try {
				// adding the first prime in the first byte
				for(int i = 0; i < firstNonPrime.length; i++) {
					obsf.clear((long) firstNonPrime[i]);
					notPrimeTxt = fl.format(firstNonPrime[i]);
				}
				fileSize = obsf.length();
				// the number of prime is 8 - the non prime + 2 for 1 and 2
				nbFound =  8 - firstNonPrime.length + 2; 
				// first byte contains 3,5,7,9,11,13,15,17
				lastFound = 17L;	
				obsf.writeNbAndLast(nbFound, lastFound);
			}
			catch(IOException e) {
				statusLabel.setText("Init file error: " + e);
				return;
			}
		}
		// read where I am at
		try {
			lastFound = obsf.getLast();
			nbFound = obsf.getNb();
			lastFoundLabel.setText(fl.format(lastFound));
			nbFoundLabel.setText(fl.format(nbFound));
		}
		catch (IOException e) {
			statusLabel.setText("The file does not seem to be a valid prime data file");
			return;
		}

		// enable the JRadioButton
		for(int i = 0; i < radioBtn.length; i++) 
			radioBtn[i].setEnabled(true);
		statusLabel.setText("Select the number of numbers to check");

		// enable the Spinner
		spinnerPanel.enableSpinner(true);
		// at application start use the latest prime as displayed number
		displayedPrime = lastFound;
		displayedPrimeLabel.setText(fl.format(displayedPrime));
	}

	/*
	 * A centered label with a border
	 */
	private JLabel borderedLabel(String str) {
		JLabel label = centeredLabel(str);
		label.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		label.setOpaque(true);
		label.setBackground(lightYellow);
		label.setForeground(Color.BLUE);
		return label;
	}
	/*
	 * Returns a centered JLabel from a String
	 */
	private JLabel centeredLabel(String str) {
		JLabel label = new JLabel(str);
		label.setHorizontalAlignment(JLabel.CENTER);
		label.setForeground(Color.BLUE);
		return label;
	}
	public void actionPerformed(ActionEvent e) {
		Object o = e.getSource();
		// if it is the timer (will be the one calling more often)
		if(o == timer) {
			// update the JLabel
			sizeLabel.setText(fl.format(fileSize));
			lastFoundLabel.setText(fl.format(lastFound));
			nbFoundLabel.setText(fl.format(nbFound));
			statusLabel.setText(statusLabelText);
			timeLabel.setText(formatSince(startTime));
			notPrimeLabel.setText(notPrimeTxt);
			repaint();
			// if the thread has finished
			if(!threadRunning) {
				// no need to continue to update the display
				timer.stop();
//				spinnerPanel.enableSpinner(true);
				btnGo.setEnabled(true);
				searchingLabel.setText(searchingLabelText);
				clearingLabel.setText(clearingLabelText);
				// we can re-emable the radio buttons
				for(int i = 0; i < radioBtn.length; i++)
					radioBtn[i].setEnabled(true);
				// clear last message
				statusLabel.setText("Press GO button to start");
				// clear the seraching progress bar
				progressBarSearching.setValue(0);
			}
			return;
		}

		// button to open file
		if(o == btnOpenFile) {
			JFileChooser chooser = new JFileChooser();
			// allow only to select 1 file
			chooser.setMultiSelectionEnabled(false);
			// our types .dat and .prim
			FileNameExtensionFilter filter = new FileNameExtensionFilter(
					"Data file", "dat", "prim");
			chooser.setFileFilter(filter);
			// check if a file was selected
			int returnVal = chooser.showOpenDialog(this);
			if(returnVal == JFileChooser.APPROVE_OPTION) {
				// if it is the case andd it is not a directory
				openFile(chooser.getSelectedFile());
			}
			return;
		}

		// test if radioButtons
		for(int i = 0; i < radioBtn.length; i++) {
			if(radioBtn[i] == o) {
				nbToCheck = radioValue[i];
				btnGo.setEnabled(true);
				statusLabel.setText("Press GO button to start");
				return;
			}
		}

		// test if it is the go button
		if(btnGo == o) {
			// apply interminatestate while searchin
			statusLabelText = "";
			// disable file chooser
			btnOpenFile.setEnabled(false);
			btnGo.setEnabled(false);
			for(int i = 0; i < radioBtn.length; i++)
				radioBtn[i].setEnabled(false);
			// start the thread that calculates the prime numbers
			Thread t = new Calculate();
			t.start();
			// starts the timer that updates the GUI
			timer = new Timer(500, this);
			timer.start();
			startTime = System.currentTimeMillis();
			return;
		}
	}

	/*
	 * Dump the content of the file (for debug purpose)
	 */
	private void dump(long to, String title) {
		System.out.println("Dump " + title + " from 3 to " + lastFound);
		try {
			StringBuilder str = new StringBuilder(100);
			for(long i = 3; i <= to; i += 2) {
				if(obsf.get(i)) {
					str.append(fl.format(i));	

					if(str.length() >= 80) {
						System.out.println(str);
						str = new StringBuilder(100);
					}
				}
			}
			System.out.println(str);
		}
		catch(IOException e) {
		}
	}

	/*
	 * Format the time elapsed since the time passed as parameter
	 */
	private String formatSince(long begin) {
		int delta = (int) (System.currentTimeMillis() - begin);
		int milli = delta % 1000;
		delta /= 1000;
		int second = delta % 60;
		delta /= 60;
		int minute = delta % 60;
		delta /= 60;
		return String.format("%d:%02d:%02d.%03d", delta, minute, second, milli);
	}

	// for the spinnerPanel to update the displayed prime
	void updateDisplayedPrime(int delta) {
		long attempt = displayedPrime + delta;
		// attempt before 3
		if(attempt <= 3L) {
			displayedPrime = 3L;
			displayedPrimeLabel.setText("3");
			return;
		}
		// attempt goes over the last one
		if(attempt >= lastFound) {
			displayedPrime = lastFound;
			displayedPrimeLabel.setText(fl.format(displayedPrime));
			return;
		}
		// we will try to find next prime or the previous one
		try {
			if(delta > 0) {
				for(long i = attempt; i <= lastFound; i += 2) {
					if(obsfRead.get(i)) {
						displayedPrime = i;
						displayedPrimeLabel.setText(fl.format(displayedPrime));
						return;					
					}
				}
			}
			// the previous if delta < 0 or if the attempt for next failed
			for(long i = attempt; i >= 3; i -= 2) {
				if(obsfRead.get(i)) {
					displayedPrime = i;
					displayedPrimeLabel.setText(fl.format(displayedPrime));
					return;					
				}
			}
		}
		catch(IOException e) {
			System.out.println("Error trying to find nextDisplayedPrime");
		}
	}

	// to stat the whole thing
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new PrimeGui().setVisible(true);
			}
		});
	}


	// the inner class that actually perform the calculation
	class Calculate extends Thread {

		// to run in a different thread to the GUI can be updated correctly
		public void run() {
			threadRunning = true;

			// update our target based on the last found
			long target = nbToCheck + lastFound;
			if(target < 0) {				 // wrap around number to big
				target = Long.MAX_VALUE;
				if(target % 2 == 0)
					target--;
			}
			// prepare the progress bar for searching
			long from = lastFound + 2;
			int nb = (int) (target - from) / 2;
			progressBarSearching.setMaximum(nb);
			progressBarSearching.setValue(0);
			searchingLabel.setText("Searching primes from " + fl.format(from) + " to " + fl.format(target));
			// clear the non primes to the end of the new file 
			try {
				clearFileExtensionWithOldPrimes(target);
			}
			catch(IOException e) {
				System.out.println("IOException clearing the none primes in the extendion: " + e);
				threadRunning = false;
				return;
			}

			// now let's find new primes to register them and fill their multiples
			fillStillPossible = true;
			nb = 0;
			try {
				for(long i = from; i <= target; i += 2) {
					progressBarSearching.setValue(++nb);
					// if not prime continue
					if(!obsf.get(i))
						continue;
					if(fillStillPossible) {
						statusLabelText = fl.format(i) + "is prime. Filling the rest of the file...";
						// we have to fill the extension of the file with all the multiples
						// of this new found number from 3 to target / that number
						clearFileWithNewPrime(i, target);
						progressBarFilling.setValue(0);
					}
					else {
						statusLabelText = "The multiples of " + fl.format(i) +"exceed the file capacity, so they will be cleared next time the file will be extended.";						
					}
					// save the new prime found in file has it's multiples are cleared
					lastFound = i;
					nbFound++;
					obsf.writeNbAndLast(nbFound, lastFound);
					////					spinnerArrayPanel.setMaxPrime(lastFound);
				}
			}
			catch(IOException e) {
				System.out.println("Exception updating multiples of " + lastFound + ": " + e);
				threadRunning = false;
				return;
			}
			threadRunning = false;
		}


		/*
		 * Clear in the rest of the file all the multiples of the new prime found
		 * return true if we filled some
		 */
		private void clearFileWithNewPrime(long prime, long target) throws IOException{
			// we will have to clear all the multiple of prime from 3
			// to target / prime
			statusLabelText = "";
			long to = target / prime;
			if(to % 2 == 0)
				to--;
			// any multiplication of the prime won't fit in the file
			if(to < 3) {
				clearingLabel.setText("");
				fillStillPossible = false;			// no need to call this method again
				return;
			}
			// number of iterations for the progress bar
			int nb = (int) (to - 3) + 1;
			progressBarFilling.setMaximum(nb);
			clearingLabel.setText("Setting as non prime the " + fl.format(nb) + " multiples of " + 
					fl.format(prime) + " up to " + fl.format(to));

			int n = 0;
			for(long i = 3; i <= to; i += 2) {
				// test if it is prime
				if(obsf.get(i)) {
					long val = i * prime;
					obsf.clear(val);
					notPrimeTxt = fl.format(val);
				}
				// do not bother filling the progressBar if nb < 1000
				// won't have to see it on the screen
				if(nb > 1000)
					progressBarFilling.setValue(++n);
			}
			clearingLabel.setText("");
			return;
		}
		/*
		 * Clear the non prime in the extended part of the file with the primes
		 * already registered
		 */
		private void clearFileExtensionWithOldPrimes(long target) throws IOException {

			for(long i = 3; i <= lastFound; i += 2) {
				// if not prime continue
				if(!obsf.get(i)) {
					continue;
				}
				// calculate the range to fill
				FillExtension fe = new FillExtension(i, lastFound, target);
				// none to fill for that one I can ignore the others which are bigger
				int nb = fe.nb;
				if(nb == 0)
					break;
				nb /= 2;
				nb++;
				clearingLabel.setText("Setting as non prime the " + fl.format(nb) + " multiples of " + 
						fl.format(i) + " up to " + fl.format(target) + " in the new file extension");
				fe.fill(obsf);
			}
			notPrimeTxt = "";
		}
	}

	/*
	 * This class is used to clear the non Prime numbers in the new extension of the file
	 * For the from value:
	 * ------------------
	 * Let's say the file contains already the prime numbers up to 37
	 * For 3 I dont'have to do
	 * 3 * 3 = 9  3 * 5 = 15  3 * 7 = 21  3 * 9 = 27 3 * 11 = 33 should start with 3 * 13 = 39
	 * 37 / 3 = 12 I should start with 13
	 * For 5 I don't have to do
	 * 5 * 5 = 25  5 * 7 = 35 should start with 5 * 9 = 45
	 * 39 / 5 = 7 I should start with 9
	 * For 7 
	 * I should start with 7 * 7 = 49
	 * 39 / 7 = 5 and I should start with 7
	 * For 11
	 * I should start with 11 * 11 = 121
	 * 39 / 11 = 3 and I should start with 11
	 * 
	 * From these I think we can conclude to these empriric rules:
	 * - divide the number of the last prime by the prime to fill
	 * - if the result of the division is even add 1 to that number
	 * - if the result of the division is odd add 2 to that number
	 * - if that number is < to the original prime use that original prime
	 * 
	 * Now for the end:
	 * ---------------
	 * A lot simpler: just divide the target by the prime... if that number is even subtract 1 to it
	 */
	class FillExtension {
		// the prime for which we will perform the operation
		long prime;
		//  by wich numbers we will multiply it
		long from, to;
		// number to do
		int nb;

		FillExtension(long prime, long lastPrime, long target) {
			this.prime = prime;
			// find from where to start
			from = lastPrime / prime;
			// if odd add 1
			if(from % 2 == 0)
				from++;
			else // if even add 2
				from += 2;
			// if smaller than original one
			if(prime > from)
				from = prime;		// take that one
			// the end
			to = target / prime;
			if(to % 2 == 0)
				to--;
			// number to to
			nb = (int) (to - from);   // will yield .... -4, -2, 0, 2, 4, ....
			// from 15 to 15 means 1
			nb += 2;
			// if from > to nothing to to
			if(nb < 0)
				nb = 0;
			nb /= 2;	   // number of time the loop will be executed
			// prepare the JProgressBar
			progressBarFilling.setValue(0);
			if(nb > 0) {
				progressBarFilling.setMaximum(nb);
			}
			notPrimeTxt = "";
		}

		// method to fill the file and the progress bar
		void fill(RegisteringOddBitSetFile file) {
			int n = 0;
			try {
				for(long i = from; i <= to; i += 2) {
					long val = i * prime;
					file.clear(val);
					notPrimeTxt = fl.format(val);
					// do not bother filling the progressBar if under 1000
					// will just make a glitch at the screen
					if(nb > 1000)
						progressBarFilling.setValue(++n);
					// update fileSize (not really OO) only for 3
					if(prime == 3)
						fileSize = file.length();
				}
			}
			catch(IOException e) {
				System.out.println("Problem filling the new area " + e);
			}
		}
	}
}



[code]
package prime;

import javax.swing.*;

public class SpinnerPanel extends Box {

// the delta in our spinner
int[] delta = {2, 1000, 10000, 100000, 1000000, 10000000, 100000000};
PrimeGui gui;
PrimeSpinner[] primeSpinner = new PrimeSpinner[delta.length];

SpinnerPanel(
Was This Post Helpful? 1
  • +
  • -

#15 LynnL  Icon User is offline

  • D.I.C Head

Reputation: 21
  • View blog
  • Posts: 109
  • Joined: 13-April 09

Re: Prime Generator Timing

Posted 30 December 2009 - 12:14 AM

Sorry does not compile
Something wrong with the constructor of RegisteringOddBitSetFile
Was This Post Helpful? 0
  • +
  • -

  • (2 Pages)
  • +
  • 1
  • 2