Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package com.rmb.randomnodatabase;
- import java.io.IOException;
- import java.nio.file.Files;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.nio.file.StandardOpenOption;
- import java.sql.SQLException;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.Map;
- import java.util.Random;
- import java.util.SortedMap;
- import java.util.concurrent.ConcurrentSkipListMap;
- import com.rmb.randomnodatabase.taskmanagement.StopListener;
- /**
- * <p>
- * Endlessly flip a coin and report on how long it took to get the same result
- * 10, 20, 30, 40 etc times in a row.
- * </p>
- *
- * <p>
- * Compiled and run in JDK 7.
- * </p>
- *
- * <p>
- * Discuss this code: <a href=
- * "http://robertmarkbramprogrammer.blogspot.com.au/2014/09/java-process-that-can-be-stopped-via.html"
- * >on my blog</a> (short URL: http://bit.ly/1oKSnHu).
- * </p>
- *
- * <p>
- * See this code in pastebin:
- * </p>
- * <ul>
- * <li><a href="http://pastebin.com/8MF52JPJ">RandomInARow.java</a></li>
- * <li><a href="http://pastebin.com/ZUCV9EFw">StopSender.java</a></li>
- * <li><a href="http://pastebin.com/n4AAaRNW">StopListener.java</a></li>
- * </ul>
- *
- * <p>
- * See results so far from one continuous run: http://pastebin.com/MG50TYED.
- * </p>
- *
- * <p>
- * Updates.
- * </p>
- * <ol>
- * <li>Tuesday 09 September 2014, 12:33:53 PM. Replaced ints with longs for
- * numbers that are getting long. Allow path to be set for where to write log.
- * Changed time format.</li>
- * <li>Wednesday 10 September 2014, 04:31:31 PM. Added a bit more time
- * information when reporting new counts.</li>
- * <li>Monday 15 September 2014, 12:21:29 AM. Version 2 of this code, now with
- * the ability to stop the process via sockets.</li>
- * </ol>
- *
- * @author Robert Mark Bram
- */
- public final class RandomInARow implements Runnable {
- /** How many milliseconds in a second. */
- static final int TIME_MILLISECONDS_IN_A_SECOND = 1000;
- /** How many milliseconds in a minute. */
- static final int TIME_SECONDS_IN_A_MINUTE =
- TIME_MILLISECONDS_IN_A_SECOND * 60;
- /** How many milliseconds in an hour. */
- static final int TIME_MINUTES_IN_AN_HOUR = TIME_SECONDS_IN_A_MINUTE * 60;
- /** How many milliseconds in a day. */
- static final int TIME_HOURS_IN_A_DAY = TIME_MINUTES_IN_AN_HOUR * 24;
- /** Increment runs by this amount. */
- static final int INCREMENT_BY = 10;
- /** Thread we listen to for stop signal. */
- private final StopListener stopListener;
- /**
- * File we write the log to. This will be different each run because it will
- * include a datestamp from {@link #FORMAT_TIMESTAMP}.
- */
- private Path logFile;
- /** Where to put files - logs and DB. */
- private String pathToFiles = "";
- /** Timestamp format for the log when I wish to report on events. */
- public static final ThreadLocal<SimpleDateFormat> FORMAT_TIMESTAMP_READABLE =
- new ThreadLocal<SimpleDateFormat>() {
- @Override
- protected synchronized SimpleDateFormat initialValue() {
- return new SimpleDateFormat("dd MMM yyyy, hh:mm:ss.SSS a");
- }
- };
- /** Timestamp format for the log file name. */
- public static final ThreadLocal<SimpleDateFormat> FORMAT_TIMESTAMP =
- new ThreadLocal<SimpleDateFormat>() {
- @Override
- protected synchronized SimpleDateFormat initialValue() {
- return new SimpleDateFormat("yyyyMMddHHmmssSSS");
- }
- };
- /**
- * Run the simulation.
- *
- * @param args
- * 0 - path to put logs file, can be null/empty.
- */
- public static void main(final String[] args) {
- String path = null;
- if (args.length > 0) {
- path = args[0];
- }
- try {
- new RandomInARow(path).run();
- } catch (ClassNotFoundException | SQLException e) {
- System.err.println("Unable to start or run application ["
- + e.getLocalizedMessage() + "].");
- e.printStackTrace();
- }
- }
- /**
- * @param thePathToFiles
- * - where to store logs and DB. May be null or empty, in which
- * case they will be written to the current directory.
- * @throws ClassNotFoundException
- * unable to load SQLITE JDBC driver
- * @throws SQLException
- * if there is any problem opening DB or working with it
- */
- public RandomInARow(final String thePathToFiles)
- throws ClassNotFoundException, SQLException {
- // Set up path for files we write.
- if (thePathToFiles != null && thePathToFiles.length() > 0) {
- pathToFiles = thePathToFiles;
- }
- // Spin up different thread - socket to listen for STOP signal.
- stopListener = new StopListener();
- stopListener.start();
- }
- /**
- * Endlessly run the simulation, starting at 10 tosses in a row and
- * incrementing by 10 each time.
- */
- @Override
- public void run() {
- // Now do our own work in this thread.
- int target = INCREMENT_BY;
- while (stopListener.isActive()) {
- repeatUntilWeGetCount(target);
- target += INCREMENT_BY;
- }
- System.out.println("See results in log file [" + logFile.toAbsolutePath()
- + "].\n");
- }
- /**
- * @param target
- * keep going until we get this number of repetitions of the same
- * flip result
- */
- public void repeatUntilWeGetCount(final int target) {
- long start = reportPhase(true, target);
- /* Sorted map that supports thread safe access of a snapshot. See:
- * http://stackoverflow.com/a/25792259/257233. */
- final SortedMap<Integer, Long> counts =
- new ConcurrentSkipListMap<Integer, Long>();
- final Random random = new Random();
- do {
- rememberCountOfContiguousOccurrences(random, counts, start, target);
- } while (counts.lastKey() < target && stopListener.isActive());
- long finish = reportPhase(false, target);
- outputResult(counts, start, finish, target);
- }
- /**
- * Return a string such as
- * "hours, [0], minutes [0], seconds [0], milliseconds [59]" based on a
- * millisecond timestamp.
- *
- * @param millisecondsTotal
- * milliseconds taken to perform task
- * @return stringing saying how many hours, minutes, seconds and milliseconds
- * were taken by <code>millisecondsTotal</code>
- */
- private String reportTime(final long millisecondsTotal) {
- long left = 0;
- // Milliseconds.
- long milliseconds = millisecondsTotal % TIME_MILLISECONDS_IN_A_SECOND;
- left = millisecondsTotal - milliseconds;
- // Seconds.
- long seconds =
- left % TIME_SECONDS_IN_A_MINUTE / TIME_MILLISECONDS_IN_A_SECOND;
- left = left - TIME_MILLISECONDS_IN_A_SECOND * seconds;
- // Minutes.
- long minutes = left % TIME_MINUTES_IN_AN_HOUR / TIME_SECONDS_IN_A_MINUTE;
- left = left - TIME_SECONDS_IN_A_MINUTE * minutes;
- // Hours.
- long hours = left % TIME_HOURS_IN_A_DAY / TIME_MINUTES_IN_AN_HOUR;
- left = left - TIME_MINUTES_IN_AN_HOUR * hours;
- // Days
- long days = left / TIME_HOURS_IN_A_DAY;
- left = left - TIME_HOURS_IN_A_DAY * days;
- // Formulate time string.
- StringBuilder sb = new StringBuilder();
- if (days > 0) {
- sb.append(days).append(" days, ");
- }
- if (hours > 0 || days > 0) {
- sb.append(hours).append(" hours, ");
- }
- if (minutes > 0 || hours > 0 || days > 0) {
- sb.append(minutes).append(" minutes, ");
- }
- if (seconds > 0 || minutes > 0 || hours > 0 || days > 0) {
- sb.append(seconds).append(" seconds and ");
- }
- sb.append(milliseconds).append(" milliseconds");
- return sb.toString();
- }
- /**
- * Report start or finish.
- *
- * @param startTime
- * true if this is reporting the start, false to report the end
- * @param target
- * number of times we want to achieve the same result in a row.
- * @return new Date().getTime() - millisecond time stamp.
- */
- private long reportPhase(final boolean startTime, final int target) {
- Date date = new Date();
- long millis = date.getTime();
- String prefix;
- String suffix;
- if (startTime) {
- prefix = "\nStarted";
- suffix = " with target [" + target + "]\n";
- } else {
- prefix = "Finished";
- suffix = " with target [" + target + "]\n";
- }
- message(prefix + " at [" + FORMAT_TIMESTAMP_READABLE.get().format(date)
- + "]" + suffix);
- return millis;
- }
- /**
- * Output results.
- *
- * @param counts
- * map of (count, occurrences of count). Count = number of times we
- * got same result in a row. Occurrences of count = number of times
- * we kept flipping and got that count.
- * @param start
- * time stamp (from {@link Date#getTime()} where we started
- * flipping coins to achieve the same result <code>target</code>
- * number of times in a row.
- * @param finish
- * time stamp (from {@link Date#getTime()} where we <b>either</b>
- * flipped coins and achieved the same result <code>target</code>
- * number of times in a row <b>OR</b> the user cancelled the
- * program.
- * @param target
- * number of times we want to achieve the same result in a row.
- */
- private void outputResult(final SortedMap<Integer, Long> counts,
- final long start, final long finish, final int target) {
- // Did we hit the target or did the user cancel?
- if (counts.lastKey() < target) {
- message("-----\nUser cancelled operation. It took "
- + reportTime(finish - start) + " to get [" + counts.lastKey()
- + "] results in a row - with target [" + target + "].\n");
- message("Reason for stopping: " + stopListener.getMessage()
- + "\n-----\n");
- } else {
- message("-----\nIt took " + reportTime(finish - start)
- + " to achieve at least target [" + target
- + "] results in a row (actual [" + counts.lastKey()
- + "]).\n-----\n");
- }
- for (Integer count : counts.keySet()) {
- message(String.format("How often we flipped the same result [%4d] "
- + "times in a row: %d.\n", count, counts.get(count)));
- }
- }
- /**
- * Flip a coin and count how many times we got the same result. Increment the
- * count for that number.
- *
- * @param random
- * random number generator
- * @param counts
- * map of (count, occurrences of count). Count = number of times we
- * got same result in a row. Occurrences of count = number of times
- * we kept flipping and got that count.
- * @param start
- * time stamp (from {@link Date#getTime()} where we started
- * flipping coins to achieve the same result <code>target</code>
- * number of times in a row.
- * @param target
- * target number of repetitions of the same flip result
- */
- private void rememberCountOfContiguousOccurrences(final Random random,
- final Map<Integer, Long> counts, final long start, final int target) {
- Integer count = countContiguousOccurences(random);
- long countOccurences;
- if (counts.containsKey(count)) {
- countOccurences = counts.get(count);
- countOccurences++;
- } else {
- countOccurences = 1L;
- Date currentTime = new Date();
- message(String.format("New count [%3d] at [" //
- + FORMAT_TIMESTAMP_READABLE.get().format(currentTime)
- + "] after %s.\n", count, reportTime(currentTime.getTime()
- - start)));
- }
- counts.put(count, countOccurences);
- }
- /**
- * Flip a coin repeatedly until we get a different result to the first toss.
- *
- * @param random
- * random number generator
- * @return number of times we tossed and got the same result
- */
- private int countContiguousOccurences(final Random random) {
- int count = 1;
- final int first = random.nextInt(2);
- while (random.nextInt(2) == first) {
- count++;
- }
- return count;
- }
- /**
- * Output message to file and standard out.
- *
- * @param message
- * message to output
- */
- private void message(final String message) {
- if (logFile == null) {
- String timestamp = FORMAT_TIMESTAMP.get().format(new Date());
- logFile = Paths.get(pathToFiles, "results_" + timestamp + ".txt");
- }
- System.out.print(message);
- try {
- Files.write(logFile, message.getBytes(), StandardOpenOption.CREATE,
- StandardOpenOption.APPEND);
- } catch (IOException ioe) {
- System.err.println("Failed to write message [" + message + "].");
- throw new RuntimeException(ioe);
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement