Advertisement
robertmarkbram

Flip a coin endlessly v2 - with threads and sockets to close

Sep 14th, 2014
437
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 13.12 KB | None | 0 0
  1. package com.rmb.randomnodatabase;
  2.  
  3. import java.io.IOException;
  4. import java.nio.file.Files;
  5. import java.nio.file.Path;
  6. import java.nio.file.Paths;
  7. import java.nio.file.StandardOpenOption;
  8. import java.sql.SQLException;
  9. import java.text.SimpleDateFormat;
  10. import java.util.Date;
  11. import java.util.Map;
  12. import java.util.Random;
  13. import java.util.SortedMap;
  14. import java.util.concurrent.ConcurrentSkipListMap;
  15.  
  16. import com.rmb.randomnodatabase.taskmanagement.StopListener;
  17.  
  18. /**
  19.  * <p>
  20.  * Endlessly flip a coin and report on how long it took to get the same result
  21.  * 10, 20, 30, 40 etc times in a row.
  22.  * </p>
  23.  *
  24.  * <p>
  25.  * Compiled and run in JDK 7.
  26.  * </p>
  27.  *
  28.  * <p>
  29.  * Discuss this code: <a href=
  30.  * "http://robertmarkbramprogrammer.blogspot.com.au/2014/09/java-process-that-can-be-stopped-via.html"
  31.  * >on my blog</a> (short URL: http://bit.ly/1oKSnHu).
  32.  * </p>
  33.  *
  34.  * <p>
  35.  * See this code in pastebin:
  36.  * </p>
  37.  * <ul>
  38.  * <li><a href="http://pastebin.com/8MF52JPJ">RandomInARow.java</a></li>
  39.  * <li><a href="http://pastebin.com/ZUCV9EFw">StopSender.java</a></li>
  40.  * <li><a href="http://pastebin.com/n4AAaRNW">StopListener.java</a></li>
  41.  * </ul>
  42.  *
  43.  * <p>
  44.  * See results so far from one continuous run: http://pastebin.com/MG50TYED.
  45.  * </p>
  46.  *
  47.  * <p>
  48.  * Updates.
  49.  * </p>
  50.  * <ol>
  51.  * <li>Tuesday 09 September 2014, 12:33:53 PM. Replaced ints with longs for
  52.  * numbers that are getting long. Allow path to be set for where to write log.
  53.  * Changed time format.</li>
  54.  * <li>Wednesday 10 September 2014, 04:31:31 PM. Added a bit more time
  55.  * information when reporting new counts.</li>
  56.  * <li>Monday 15 September 2014, 12:21:29 AM. Version 2 of this code, now with
  57.  * the ability to stop the process via sockets.</li>
  58.  * </ol>
  59.  *
  60.  * @author Robert Mark Bram
  61.  */
  62. public final class RandomInARow implements Runnable {
  63.  
  64.    /** How many milliseconds in a second. */
  65.    static final int TIME_MILLISECONDS_IN_A_SECOND = 1000;
  66.  
  67.    /** How many milliseconds in a minute. */
  68.    static final int TIME_SECONDS_IN_A_MINUTE =
  69.          TIME_MILLISECONDS_IN_A_SECOND * 60;
  70.  
  71.    /** How many milliseconds in an hour. */
  72.    static final int TIME_MINUTES_IN_AN_HOUR = TIME_SECONDS_IN_A_MINUTE * 60;
  73.  
  74.    /** How many milliseconds in a day. */
  75.    static final int TIME_HOURS_IN_A_DAY = TIME_MINUTES_IN_AN_HOUR * 24;
  76.  
  77.    /** Increment runs by this amount. */
  78.    static final int INCREMENT_BY = 10;
  79.  
  80.    /** Thread we listen to for stop signal. */
  81.    private final StopListener stopListener;
  82.  
  83.    /**
  84.     * File we write the log to. This will be different each run because it will
  85.     * include a datestamp from {@link #FORMAT_TIMESTAMP}.
  86.     */
  87.    private Path logFile;
  88.  
  89.    /** Where to put files - logs and DB. */
  90.    private String pathToFiles = "";
  91.  
  92.    /** Timestamp format for the log when I wish to report on events. */
  93.    public static final ThreadLocal<SimpleDateFormat> FORMAT_TIMESTAMP_READABLE =
  94.          new ThreadLocal<SimpleDateFormat>() {
  95.             @Override
  96.             protected synchronized SimpleDateFormat initialValue() {
  97.                return new SimpleDateFormat("dd MMM yyyy, hh:mm:ss.SSS a");
  98.             }
  99.          };
  100.  
  101.    /** Timestamp format for the log file name. */
  102.    public static final ThreadLocal<SimpleDateFormat> FORMAT_TIMESTAMP =
  103.          new ThreadLocal<SimpleDateFormat>() {
  104.             @Override
  105.             protected synchronized SimpleDateFormat initialValue() {
  106.                return new SimpleDateFormat("yyyyMMddHHmmssSSS");
  107.             }
  108.          };
  109.  
  110.    /**
  111.     * Run the simulation.
  112.     *
  113.     * @param args
  114.     *           0 - path to put logs file, can be null/empty.
  115.     */
  116.    public static void main(final String[] args) {
  117.       String path = null;
  118.       if (args.length > 0) {
  119.          path = args[0];
  120.       }
  121.       try {
  122.          new RandomInARow(path).run();
  123.       } catch (ClassNotFoundException | SQLException e) {
  124.          System.err.println("Unable to start or run application ["
  125.                + e.getLocalizedMessage() + "].");
  126.          e.printStackTrace();
  127.       }
  128.    }
  129.  
  130.    /**
  131.     * @param thePathToFiles
  132.     *           - where to store logs and DB. May be null or empty, in which
  133.     *           case they will be written to the current directory.
  134.     * @throws ClassNotFoundException
  135.     *            unable to load SQLITE JDBC driver
  136.     * @throws SQLException
  137.     *            if there is any problem opening DB or working with it
  138.     */
  139.    public RandomInARow(final String thePathToFiles)
  140.          throws ClassNotFoundException, SQLException {
  141.       // Set up path for files we write.
  142.       if (thePathToFiles != null && thePathToFiles.length() > 0) {
  143.          pathToFiles = thePathToFiles;
  144.       }
  145.  
  146.       // Spin up different thread - socket to listen for STOP signal.
  147.       stopListener = new StopListener();
  148.       stopListener.start();
  149.    }
  150.  
  151.    /**
  152.     * Endlessly run the simulation, starting at 10 tosses in a row and
  153.     * incrementing by 10 each time.
  154.     */
  155.    @Override
  156.    public void run() {
  157.       // Now do our own work in this thread.
  158.       int target = INCREMENT_BY;
  159.       while (stopListener.isActive()) {
  160.          repeatUntilWeGetCount(target);
  161.          target += INCREMENT_BY;
  162.       }
  163.       System.out.println("See results in log file [" + logFile.toAbsolutePath()
  164.             + "].\n");
  165.    }
  166.  
  167.    /**
  168.     * @param target
  169.     *           keep going until we get this number of repetitions of the same
  170.     *           flip result
  171.     */
  172.    public void repeatUntilWeGetCount(final int target) {
  173.       long start = reportPhase(true, target);
  174.       /* Sorted map that supports thread safe access of a snapshot. See:
  175.        * http://stackoverflow.com/a/25792259/257233. */
  176.       final SortedMap<Integer, Long> counts =
  177.             new ConcurrentSkipListMap<Integer, Long>();
  178.       final Random random = new Random();
  179.  
  180.       do {
  181.          rememberCountOfContiguousOccurrences(random, counts, start, target);
  182.       } while (counts.lastKey() < target && stopListener.isActive());
  183.  
  184.       long finish = reportPhase(false, target);
  185.       outputResult(counts, start, finish, target);
  186.    }
  187.  
  188.    /**
  189.     * Return a string such as
  190.     * "hours, [0], minutes [0], seconds [0], milliseconds [59]" based on a
  191.     * millisecond timestamp.
  192.     *
  193.     * @param millisecondsTotal
  194.     *           milliseconds taken to perform task
  195.     * @return stringing saying how many hours, minutes, seconds and milliseconds
  196.     *         were taken by <code>millisecondsTotal</code>
  197.     */
  198.    private String reportTime(final long millisecondsTotal) {
  199.       long left = 0;
  200.  
  201.       // Milliseconds.
  202.       long milliseconds = millisecondsTotal % TIME_MILLISECONDS_IN_A_SECOND;
  203.       left = millisecondsTotal - milliseconds;
  204.  
  205.       // Seconds.
  206.       long seconds =
  207.             left % TIME_SECONDS_IN_A_MINUTE / TIME_MILLISECONDS_IN_A_SECOND;
  208.       left = left - TIME_MILLISECONDS_IN_A_SECOND * seconds;
  209.  
  210.       // Minutes.
  211.       long minutes = left % TIME_MINUTES_IN_AN_HOUR / TIME_SECONDS_IN_A_MINUTE;
  212.       left = left - TIME_SECONDS_IN_A_MINUTE * minutes;
  213.  
  214.       // Hours.
  215.       long hours = left % TIME_HOURS_IN_A_DAY / TIME_MINUTES_IN_AN_HOUR;
  216.       left = left - TIME_MINUTES_IN_AN_HOUR * hours;
  217.  
  218.       // Days
  219.       long days = left / TIME_HOURS_IN_A_DAY;
  220.       left = left - TIME_HOURS_IN_A_DAY * days;
  221.  
  222.       // Formulate time string.
  223.       StringBuilder sb = new StringBuilder();
  224.       if (days > 0) {
  225.          sb.append(days).append(" days, ");
  226.       }
  227.       if (hours > 0 || days > 0) {
  228.          sb.append(hours).append(" hours, ");
  229.       }
  230.       if (minutes > 0 || hours > 0 || days > 0) {
  231.          sb.append(minutes).append(" minutes, ");
  232.       }
  233.       if (seconds > 0 || minutes > 0 || hours > 0 || days > 0) {
  234.          sb.append(seconds).append(" seconds and ");
  235.       }
  236.       sb.append(milliseconds).append(" milliseconds");
  237.       return sb.toString();
  238.    }
  239.  
  240.    /**
  241.     * Report start or finish.
  242.     *
  243.     * @param startTime
  244.     *           true if this is reporting the start, false to report the end
  245.     * @param target
  246.     *           number of times we want to achieve the same result in a row.
  247.     * @return new Date().getTime() - millisecond time stamp.
  248.     */
  249.    private long reportPhase(final boolean startTime, final int target) {
  250.       Date date = new Date();
  251.       long millis = date.getTime();
  252.       String prefix;
  253.       String suffix;
  254.       if (startTime) {
  255.          prefix = "\nStarted";
  256.          suffix = " with target [" + target + "]\n";
  257.       } else {
  258.          prefix = "Finished";
  259.          suffix = " with target [" + target + "]\n";
  260.       }
  261.       message(prefix + " at [" + FORMAT_TIMESTAMP_READABLE.get().format(date)
  262.             + "]" + suffix);
  263.       return millis;
  264.    }
  265.  
  266.    /**
  267.     * Output results.
  268.     *
  269.     * @param counts
  270.     *           map of (count, occurrences of count). Count = number of times we
  271.     *           got same result in a row. Occurrences of count = number of times
  272.     *           we kept flipping and got that count.
  273.     * @param start
  274.     *           time stamp (from {@link Date#getTime()} where we started
  275.     *           flipping coins to achieve the same result <code>target</code>
  276.     *           number of times in a row.
  277.     * @param finish
  278.     *           time stamp (from {@link Date#getTime()} where we <b>either</b>
  279.     *           flipped coins and achieved the same result <code>target</code>
  280.     *           number of times in a row <b>OR</b> the user cancelled the
  281.     *           program.
  282.     * @param target
  283.     *           number of times we want to achieve the same result in a row.
  284.     */
  285.    private void outputResult(final SortedMap<Integer, Long> counts,
  286.          final long start, final long finish, final int target) {
  287.       // Did we hit the target or did the user cancel?
  288.       if (counts.lastKey() < target) {
  289.          message("-----\nUser cancelled operation. It took "
  290.                + reportTime(finish - start) + " to get [" + counts.lastKey()
  291.                + "] results in a row - with target [" + target + "].\n");
  292.          message("Reason for stopping: " + stopListener.getMessage()
  293.                + "\n-----\n");
  294.       } else {
  295.          message("-----\nIt took " + reportTime(finish - start)
  296.                + " to achieve at least target [" + target
  297.                + "] results in a row (actual [" + counts.lastKey()
  298.                + "]).\n-----\n");
  299.       }
  300.       for (Integer count : counts.keySet()) {
  301.          message(String.format("How often we flipped the same result [%4d] "
  302.                + "times in a row: %d.\n", count, counts.get(count)));
  303.       }
  304.  
  305.    }
  306.  
  307.    /**
  308.     * Flip a coin and count how many times we got the same result. Increment the
  309.     * count for that number.
  310.     *
  311.     * @param random
  312.     *           random number generator
  313.     * @param counts
  314.     *           map of (count, occurrences of count). Count = number of times we
  315.     *           got same result in a row. Occurrences of count = number of times
  316.     *           we kept flipping and got that count.
  317.     * @param start
  318.     *           time stamp (from {@link Date#getTime()} where we started
  319.     *           flipping coins to achieve the same result <code>target</code>
  320.     *           number of times in a row.
  321.     * @param target
  322.     *           target number of repetitions of the same flip result
  323.     */
  324.    private void rememberCountOfContiguousOccurrences(final Random random,
  325.          final Map<Integer, Long> counts, final long start, final int target) {
  326.       Integer count = countContiguousOccurences(random);
  327.       long countOccurences;
  328.       if (counts.containsKey(count)) {
  329.          countOccurences = counts.get(count);
  330.          countOccurences++;
  331.       } else {
  332.          countOccurences = 1L;
  333.          Date currentTime = new Date();
  334.          message(String.format("New count [%3d] at [" //
  335.                + FORMAT_TIMESTAMP_READABLE.get().format(currentTime)
  336.                + "] after %s.\n", count, reportTime(currentTime.getTime()
  337.                - start)));
  338.       }
  339.       counts.put(count, countOccurences);
  340.  
  341.    }
  342.  
  343.    /**
  344.     * Flip a coin repeatedly until we get a different result to the first toss.
  345.     *
  346.     * @param random
  347.     *           random number generator
  348.     * @return number of times we tossed and got the same result
  349.     */
  350.    private int countContiguousOccurences(final Random random) {
  351.       int count = 1;
  352.       final int first = random.nextInt(2);
  353.       while (random.nextInt(2) == first) {
  354.          count++;
  355.       }
  356.       return count;
  357.    }
  358.  
  359.    /**
  360.     * Output message to file and standard out.
  361.     *
  362.     * @param message
  363.     *           message to output
  364.     */
  365.    private void message(final String message) {
  366.       if (logFile == null) {
  367.          String timestamp = FORMAT_TIMESTAMP.get().format(new Date());
  368.          logFile = Paths.get(pathToFiles, "results_" + timestamp + ".txt");
  369.       }
  370.       System.out.print(message);
  371.       try {
  372.          Files.write(logFile, message.getBytes(), StandardOpenOption.CREATE,
  373.                StandardOpenOption.APPEND);
  374.       } catch (IOException ioe) {
  375.          System.err.println("Failed to write message [" + message + "].");
  376.          throw new RuntimeException(ioe);
  377.       }
  378.    }
  379.  
  380. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement