/* FL started 1-7-2008
	 Former attempt April 2008 as oddity\Werk\JJ\BigOPedigree\BigOPedigree3.java
   Former attempt June 2008 as programmer\Werk\JJ\bigOpera_OVERDONE\bigOpera.java
	 
	 cd ..\..\Users\programmer\Werk\JJ\
   java bigOpera/BigOpera 2
	 bigOpera.bat
            javac bigOpera/BigOpera.java
            java bigOpera/BigOpera 10
            javadoc bigOpera -private -d bigOpera_docs -author -version
*/

package bigOpera;

import java.io.*;                // for serialization and PP-file writing
import java.util.Arrays;         // to expand an existing data array to size yEnd

/**
 * List numbers by counting the number of ones needed to construct those numbers
 * using the {@link #bigO} method and positive integers.
 * 
 * @author Frans Lelieveld,
 * @version B&egrave;ta 2008/07/04 cannot restart with serialized data
 */
public class BigOpera implements Serializable
{
  /**
   * File path to serialize this BigOpera object. <br />
   * Set to <code>null</code> if serialization is unwanted. Default is <code>"BigOpera.bin"</code>
   */
  public String serialPath = null;   // "BigOpera.bin";  //PP leads to problems in buildNumbers() !!!
  
  /**
   * Print the totals and gains every 1000th multiple of <code>PRINT_MULTI_STATS</code> inbetween. <br />
   * BigOpera always prints stats at the bottom of the list, even if <code>endY</code> is not a multiple.
   */
  public int PRINT_MULTI_STATS = 1000;
  
  /**
   * Print a second line with the experimental mathematical "Obit" representation of a number.<br />
   * Eg. <code>12 = 111111111111</code><br />
   * <code>15 = O(3,5,1) = 0111011111011</code><br />
   * <code>23 = O(3,O(4,5,1)) = 011100111101111101101</code><br />
   * <code>226 = 01000111011111011011011101</code>
   */
  public boolean PRINT_OBITS = true;
  
  /**
   * Prefer to record and display those equations that have the lowest c when fingers are equal.<br />
   * By default set to <code>false</code> the order of creation prevails, which favors higher c.
   * The default also minimizes the construction stack counts, eg. <code>16 9 8 8 2^^3 7 5 4</code><br />
   * As less wide c are as yet unrecorded, only the widest fields are effected by this flag.
   */
  boolean PREFER_LOWER_C = false;
  
  /** 
   * Array of supported operators as Strings. 
   * That is all positive operators <code>{ +, *, ^, ^^ }</code><br />
   * Call with <code>c</code> as index. 
   */
  public static String[] OPERATORS = { "+", "*", "^", "^^" };
  
  /**
   * An absolute maximum set to the results of bigO that can be recorded. <br /><br />
   * An <code>Integer.MAX_VALUE</code> number of integers takes <code>2^31*4bytes = 8Gb</code><br />
   * So it's wise to set it lower. The default is:<br />
   * <code>Y_MAX = 2097152 =&gt; Y_MAX*data.length = 8Mb*6 = 48Mb</code><br />
   * Beware: printed data for <code>endY==1000000</code> contains hundreds of megabytes!
   */
  int Y_MAX = Integer.MAX_VALUE/1024;        // Y_MAX=524287 => 2Mb*data.length = 12Mb
  
  /** 
   * Maximum operator number. Given a maximum of <code>a+b</code> where <code>a&gt;1</code>.<br /> 
   * Initially when <code>a+b&lt;11</code> this <code>cMax = 3</code> or <code>^^</code> 
   * because <code>2^^4 = 2^16 = 65536 = 2^^^3</code> (same <em>Vingers</em> value too)
   * and <code>9^^2 = 9^9 = 387420489</code> and <code>10^10 &gt; Integer.MAX_VALUE</code><br />
   * Then when <code>a+b&lt;33</code> this is 2 because <code>2^31 = Integer.MAX_VALUE+1</code>
   */
  int cMax = OPERATORS.length-1;            // initial cMax
  
  /**
   * Array with absolute maximum values <code>b</code> for each <code>c</code>.<br />
   * <code>B_MAX = { 524286, 262143, 18, 4 }</code>
   */
  private int[] B_MAX = { Y_MAX-1, Y_MAX/2, (int)(Math.log(Y_MAX+.2)/Math.log(2)), 4 };
  
  /**
   * Default limits to decide when to reduce the operator number c for good. 
   * Can be adapted to <code>yEnd</code> later.<br />
   * If <code>ab</code> in the construction loop gets bigger put <code>cMax--</code><br />
   * <code>abLimits[2]</code> for <code>^</code> powers is the maximal root plus 2. <br />
   */
  private int[] abLimits = { Y_MAX, 
                             Y_MAX/2+2, 
                             (int)Math.sqrt(Y_MAX+.2)+2,  // Y_MAX=2097152 => 1450
                             11 };                        // 9^^2 = 387420489
  
  /** The ID <code>7625597484987L</code> for serialization of this class. */
  static final long serialVersionUID = 7625597484987L;        // O(3,3,3)
  
  /**
    * Writer for storing the console data directly in a user specified file.
    * Currently this <code>BufferedWriter</code> is not constructed to append text.
    * @see #main
    * @see #print
    */
  private BufferedWriter textWriter;                      // eg. to file "operadata.txt"

  /**
   * Simple integer array to store the result data.
   * Keeps the number of Java array objects on the heap to a minimum.<br />
   * Assigns different data to each primary indexes as follows: 
   * <ol><li value="0">The <em>Vingers</em> value <code>V<sub>1</sub></code> for <code>+*</code> 
   * &nbsp; is the number of ones needed to create a number with <code>+</code> and <code>*</code></li>
   * <li><code>V<sub>2</sub></code> for <code>+*^</code> 
   * &nbsp; is the number of ones needed to create a number with <code>c={0,1,2}</code></li>
   * <li><code>V<sub>n</sub></code> for <code>+*^..</code> 
   * &nbsp; is the number of ones needed to create a number with <code>c&gt;=0</code></li>
   * <li><code>a</code> &nbsp; <em>first operant</em> in last equation with least <code>V</code></li>
   * <li><code>b</code> &nbsp; <em>second operant</em> in last equation with least <code>V</code></li>
   * <li><code>c</code> &nbsp; <em>operator number</em> of last equation with least <code>V</code></li>
   * <li>Counts the number of bigO operations <code>W<sub>1</sub></code> 
   * passed in its <code>+*</code> construction stack.</li>
   * <li>Counts the number of bigO operations <code>W<sub>2</sub></code> 
   * passed in its <code>+*^</code> construction stack.</li>
   * <li>Counts the number of bigO operations <code>W<sub>n</sub></code> 
   * passed in its widest <code>+*^..</code> construction stack.</li></ol>
   * The array is initialized with data of number 0 and 1 (printing starts at one).
   * Increase the array size when needed, with {@link #setModel}.
   */
  int[][] data = { {0,1}, {0,1}, {0,1},
                   {0,1}, {0,0}, {0,0},               // initially 0+0=0  1+0=1
                   {0,0}, {0,0}, {0,0} };
                   
  /**
   * Cummulative totals for <em>Vingers</em> values <code>V</code> and bigO stack values <code>W</code>.
   * From these the percentage gain from allowing a wider operator can be calculated.<br />
   * <code>totals</code> is indexed as follows:
   * <ol><li value="0">Data total of <code>V<sub>1</sub></code> for <code>+*</code></li>
   * <li>Data total of <code>V<sub>2</sub></code> for <code>+*^</code></li>
   * <li>Data total of <code>V<sub>n</sub></code> for <code>+*^..</code></li>
   * <li>Data total of <code>W<sub>1</sub></code> for <code>+*</code></li>
   * <li>Data total of <code>W<sub>2</sub></code> for <code>+*^</code></li>
   * <li>Data total of <code>W<sub>n</sub></code> for <code>+*^..</code></li>
   * <li>The total String length of the Obit number representations.</li></ol>
   */
  long[] totals = { 0,0,0, 0,0,0, 0 };    // the initial 1 in data will be counted in printData()
  
  /**
   * Sum of deviations of the Obit length from the mean of Obit lengths. <br />
   * Build the total deviation from the mean as we go at {@link #printData} 
   * to display Obit trends at each call to {@link #printFooter}.
   */
  double obitDeviationTotal = 0.0;
  
  /**
   * Array to keep track of cummulative  <em>Vingers</em> "number of ones" data in the list. 
   * Index 0 be y, index 1 be the highest widest <code>V</code> value thus far.
   * Set to highlight "~" the start of higher V at number y in {@link #printData}.
   */ 
  private int[] yNextVn = { 0, 0 };               // increased by printData() while printing
  
  /**
   * Array to keep track of cummulative <em>bigO stack</em> data in the list. 
   * Index 0 be y, index 1 be the highest widest <code>W</code> value thus far.
   * Set to highlight "~" the start of higher W at number y in {@link #printData}.
   */
  private int[] yNextWn = { 0, 0 };               // increased by printData() while printing
  
  
  /** Default constructor. */
  public BigOpera()
  {}
   
  /**
   * Set up the data model to store and display new results. <br />
   * Prints header for new data.
   * Increases the array size of the secondary arrays of {@link #data} to accomodate more numbers.
   * @param yEnd  the last result as to be calculated by {@link #buildNumbers}.
   * @return <code>index</code> if the model was set up and can be expanded by new data<br />
   *         <code>-1</code> if all data upto <code>yEnd</code> has already been collected
   * @throws IllegalArgumentException   if <code>yEnd &gt; Y_MAX</code>
   */
  int setModel(int yEnd)
  {
    if(yEnd > Y_MAX)
      throw new IllegalArgumentException(yEnd + " is larger than Y_MAX " + Y_MAX);
    
    abLimits[1] = yEnd/2+2;
    abLimits[2] = (int)Math.sqrt(yEnd+.2)+2;         // reset this most important abLimit
    
    printHeader();                      // print data header on the prompt
    
    int len = data[0].length;           // number of y stored

    // Check if all data is already available
    if(yEnd < len)
    {
      // Then print it (again) upto the requested yEnd
      for(int y = 1; y <= yEnd; y++)
        printData(y);
      return -1;                        // no need to seek for new data
    }
    
    // Only print a moderate amount of old data
    if(len < 100)
      for(int y = 1; y < len; y++)      // NB: START_AT_ONE
        printData(y);                   // print initial data lines
    
    // Set up the model to store all data upto yEnd and copy secondary arrays, padding with zeros
    for(int i = 0; i < data.length; i++)
      data[i] = Arrays.copyOf(data[i], yEnd+1);
    
    return len;
  }
  
  
  /**
   * Start building numbers from where you've ended last time.
   * @param yEnd   the result <code>y = bigO(a,b,c)</code> upto where to build number space
   */
  public void buildNumbers(int yEnd)
  {
// int Y_TESTMIN = 9, Y_TESTMAX = 9;

    // Accomodate new data request - prints header
    int yStart = setModel(yEnd);         // gets next y to store in the data
    if(yStart == -1)
      return;                            // -1 meant we are done!
    
    // Variable ab holds the sum of a+b which is maximally the coming result y with addition
    for(int ab = yStart; ab <= yEnd; ab++)  // loops through all relevant combinations ab
    {
// if(ab >= Y_TESTMIN && ab <= Y_TESTMAX) System.out.println("\n~ ab=" + ab);
  
      // Check if ab exceeds the abLimits for yEnd given a certain c
      if(ab > abLimits[1])               // * limit varies: yEnd/2+2
        cMax = 0;                        // from now on only apply +
      else if(ab > abLimits[2])          // ^ limit varies: sqrt(yEnd)+2
        cMax = 1;                        // from now on only apply + and *
      else if(ab > abLimits[3])          // ^^ limit is 9^^2 = 11 (soon done)
        cMax = 2;                        // from now on only apply + * ^
      
      /* As the equation y=a+b is left justified, larger "a" look better on display, 
         also "b" is the "smaller power value" because "b" increases y=a^b faster than "a".
         The choice is to have small "b", so we count "b" upwards.
         But for addition we count it down from halfway, because halfway takes precedence.
         The SCHEME in order of operators c is:
             ^^  count up b    from 2 all the way to ab-2 or the maximum operant B_MAX[3]
             ^   count up b    from 2 all the way to ab-2 or B_MAX[2]
             *   count up b    from 2             to halfway "ab"      until it gets too big
             +   count down b  from halfway "ab"  to 1                 ab = a+b is never too big 
         PP The mainstay of this program are superfluous additions, maybe PP can avoid some? */

      int bInc = 1;            // incremental loop b counter for ^^ and ^ and *
      int bStart = 2;          // set minimum operant b for ^^ and ^ and * countup
      int bEnd = 0;            // c loop sets maximum operant b for powers countdown

      // Count down operators c: ^^ ^ * +
      for(int c = cMax; c > -1; c--)
      {
        // Reset bStart, bEnd, bInc if necessary
        if(c == 0)                            // see SCHEME for +
        {
          bInc = -1;                          // count down
          bStart = ab/2;                      // floors bStart <= halfway, eg. 11/2=5 and 12/2=6
          bEnd = 1;                           // no MAX for "a"
        }
        else if(c == 1)                       // see SCHEME for *
          bEnd = ab/2;
        else                                  // see SCHEME for ^ and ^^
          bEnd = Math.min( B_MAX[c], ab-2 );
          
// if(ab >= Y_TESTMIN && ab <= Y_TESTMAX) System.out.println("\n~ bStart=" + bStart + " bEnd=" + bEnd);

        for(int b = bStart; 
            bInc == 1 ? (b <= bEnd) : (b >= bEnd);        // up or down comparison
            b += bInc)
        {
          // The a and b we use must for ALL configurations c already have MINIMUM Vingers data
          int a = ab - b;                     // a is counted down
          
          int y = 0;
          if(c == 0)
            y = ab;                           // bigO addition a+b = ab
          else
            y = bigO(a,b,c);
          
// if(ab >= Y_TESTMIN && ab <= Y_TESTMAX) System.out.println("\n~ O(" + a + "," + b + "," + c + ")=" + y);
          
          // Result y got too big!
          if(y > yEnd)
          {
            // But discarding all/half of remaining powers is troublesome PP!
            if(c > 1)                         // I like to avoid missing some y for powers c is ^..
              continue;

            break;                            // break multiplication - go to + or next ab
          }

          setData(a,b,c,y);                   // record new data if it is any good
        }
      }
      // The next y=ab has minimized its Vingers in all constructions and is ready to be displayed
      printData(ab);
    }
    // End with statistics
    printFooter(yEnd);                        // prints end totals and stats
  }
  
  /**
   * Check if a candidate solution for bigO can be stored in the {@link #data} array. <br />
   * If a solution uses less <em>Vingers</em> in its "minimum operant" construction 
   * <code>setData()</code> stores it for all operants <code>&gt;= c</code>
   * @param a  first bigO operant
   * @param b  second bigO operant
   * @param c  bigO operator number
   * @param y  the result <code>y = bigO(a,b,c)</code>
   */
  void setData(int a, int b, int c, int y)
  {
    // Get Vinger and Stack data for operants a, b in an "any operator +*^.." construction
    int abcVn = data[2][a] + data[2][b] + c;
    
    // Try to get Vinger (with "any operator") data for number y, or 0 if y is new
    int yVn = data[2][y];
    
    // Apply lower c mode if already initialized?  (abcVn == yVn implies initialization)
    boolean applyLowerC = (PREFER_LOWER_C && abcVn == yVn && c < data[5][y]);
    
    if(yVn == 0 || abcVn < yVn || applyLowerC)
    {
      // Store the Vingers for the improved "any operator" construction of y
      data[2][y] = abcVn;
      
      // This bigO equations is best, because it has the least fingers at the widest construction
      data[3][y] = a;
      data[4][y] = b;
      data[5][y] = c;
      
      int abcWn = 1 + data[8][a] + data[8][b];           // bigO construction stack count
      if(yVn == 0 || abcWn < data[8][y])                 // new or better?
        data[8][y] = abcWn;                              // store!
      
      // If yVn wasn't stored before, then yV2 and yV1 weren't stored before
      if(yVn == 0)
      {
        if(c < 3)                                        // DON'T retrieve yV2, yV1 if not required
        {
          data[1][y] = data[1][a] + data[1][b] + c;      // new yV2
          data[7][y] = 1 + data[7][a] + data[7][b];      // new yW2
          if(c < 2)
          {
            data[0][y] = data[0][a] + data[0][b] + c;    // new yV1
            data[6][y] = 1 + data[6][a] + data[6][b];    // new yW1
          }
        }
        return;                                          // all data set
      }
    }
    if(c > 2)
      return;                                            // or remains +*^ to set
    
    // Get Vinger (operator c < 3) data for number y, or 0 if y is new
    int yV2 = data[1][y];
    int abcV2 = data[1][a] + data[1][b] + c;             // +*^ construction Vingers

    // If yV2 wasn't stored before, then yV1 wasn't stored before
    if(yV2 == 0 || abcV2 < yV2)
    {
      data[1][y] = abcV2;
      int abcW2 = 1 + data[7][a] + data[7][b];           // bigO constructions count
      if(yV2 == 0 || abcW2 < data[7][y])                 // new or better?
        data[7][y] = abcW2;                              // store!
    
      if(yV2 == 0)
      {
        if(c < 2)                                        // DON'T retrieve yV1 if not required
        {
          data[0][y] = data[0][a] + data[0][b] + c;      // new yV1
          data[6][y] = 1 + data[6][a] + data[6][b];      // new yW1
        }
        return;                                          // all data set
      }
    }
    if(c == 2)
      return;                                            // or remains +* to set
    
    // Get Vinger (operator c < 2) data for number y, or 0 if y is new
    int yV1 = data[0][y];
    int abcV1 = data[0][a] + data[0][b] + c;             // +* construction Vingers
    
    // Insert data for yV1 if it is missing..
    // ..or if the current entry has more Vingers then the new argument bigO
    if(yV1 == 0 || abcV1 < yV1)
    {
      data[0][y] = abcV1;
      int abcW1 = 1 + data[6][a] + data[6][b];           // bigO constructions count
      if(yV1 == 0 || abcW1 < data[6][y])                 // new or better?
        data[6][y] = abcW1;                              // store!
    }
    // All data set
  }
  
  /**
   * Calculate <code>bigO(a,b,c)</code> for integer types.
   * <ul>
   * <li><code>c=0</code> addition <code>a+b</code></li>
   * <li><code>c=1</code> multiplication <code>a*b</code></li>
   * <li><code>c=2</code> power <code>a^b</code></li>
   * <li><code>c=3</code> doublepower <code>a^^b</code></li>
   * <li><code>c=4</code> triplepower <code>a^^^b</code></li>
   * <li><code>c=-1</code> division <code>b/a</code></li>
   * <li><code>c=-2</code> logarithm <code>log(b)/log(a)</code></li>
   * </ul>
   * @param a   first number
   * @param b   second number
   * @param c   operator number
   * @return the result as an <code>int</code> number,<br />
   *         or <code>Integer.MAX_VALUE = 2^31-1 = 2147483647</code> if the result is too big
   *         or <code>Integer.MIN_VALUE = -2^31 = -2147483648</code> if the result is too small
   * @throws ArithmeticException  if the calculation is impossible or unsupported
   *         or if the result is not exactly integer<br />
   *         Note that <code>^...</code> operations never throw this exception for positive <code>b</code>
   */
  public static int bigO(int a, int b, int c)
  {
    switch(c)
    {
      case 0:                               // add
        if(a > 0) {
          if(b > Integer.MAX_VALUE-a)
            return Integer.MAX_VALUE;
        }
        else
          if(a < 0 && b < Integer.MIN_VALUE-a)
            return Integer.MIN_VALUE;
        return a + b;
      case 1:                               // multiply
        if(a > 0) {
          if(b > Integer.MAX_VALUE/a)
            return Integer.MAX_VALUE;
          else if(b < Integer.MIN_VALUE/a)
            return Integer.MIN_VALUE;
        }
        else if(a < 0)
          if(b > Integer.MIN_VALUE/a)
            return Integer.MIN_VALUE;
          else if(b < Integer.MAX_VALUE/a)
            return Integer.MAX_VALUE;
        return a * b;
      case -1:                              // division b/a
        int div = b / a;                    // throws ArithmeticException("Zero divisor")
        if(a*div != b)
          throw new ArithmeticException(b + "/" + a + " is not integer");
        return div;
      case 2:                               // powers
        if(a != 0 && b > Math.log(Integer.MAX_VALUE)/Math.log(Math.abs(a)))
          // If a is negative and b is even, then y is positive
          if(a > 0 || b % 2 == 0)
            return Integer.MAX_VALUE;
          else
            return Integer.MIN_VALUE;
        return (int)Math.round(Math.pow(a, b));
      case -2:                              // logarithms log b/log a
        if(b <= 0 || a <= 0)
          if(b == 0 || a == 0)
            throw new ArithmeticException("Logarithm of zero is infinite");
          else
            throw new ArithmeticException("Complex logarithms are not supported");
        int alogb = (int)Math.round(Math.log(b)/Math.log(a));
        int test = (int)Math.round(Math.pow(a, alogb));
        if(test != b)
          throw new ArithmeticException("log(" + b + ")/log(" + a + ") is not integer");
        return alogb;
      default:                              // non-standard bigO operations
        // Negative operators like -3
        if(c < 0)
          throw new ArithmeticException("Negative operator number " + c + " is not supported");
        // Return trivial cases
        if(a == 1 || b == 1)
          return 1;
        else if(a == 2 && b == 2)
          return 4;
        else if(b < 1)
          if(c > 1-b)                       // apply special formula for b <= 0
            return b + 1;
          else
            throw new ArithmeticException("O(" + a + "," + b + "," + c + ") is not integer");
        if(a == 0)
          return 0;
        // If the result becomes too big.. 
        if(a > 0)
        {
          if(c > 4 || (c > 3 && a > 2))       // eg. O(3,3,3) = 3^(3^3) = 7625597484987
            return Integer.MAX_VALUE;         // ..always return Integer.MAX_VALUE
        }
        else if(a < -1 && b > 2)
          throw new ArithmeticException("O(" + a + "," + b + "," + c + ") can hardly be integer");
        
        // Calculate multiple power, let ^^ recursively call bigO() ^
        int y = a;
        for(int i = 1; i < b; i++)
        {
          y = bigO(a, y, c-1);
          if(y == Integer.MAX_VALUE || y == Integer.MIN_VALUE)
            return y;
        }
        return y; 
    }
  }
  
  /**
   * Create and print the header for the results of bigO equations. <br />
   * Does not print initial data.
   */
  public void printHeader()
  {
    // Create output string with header
    String line = "\n   Ones in a number construction with   bigO stack count   O(a,b,c)\n";
    line += String.format("%13s%9s%7s%8s%8s%5s%7s%n", 
                          "N+", "+*", "+*^", "+*^..", "+*", "+*^", "+*^..");
    
    line += PRINT_OBITS ? "Obits\n" : "\n";        // mathematical "Obits"
    print(line);
  }
  
  /**
   * Print data for the next number constructions with the bigO equation. 
   * @param y  the data for number <code>y</code> that has to printed next
   * @throws IllegalStateException  if data is missing
   */
  public void printData(int y)
  {
    // Retrieve data
    int v1 = data[0][y];      // minimum of Vingers in a construction with +*
    int v2 = data[1][y];      // minimum of Vingers in a construction with +*^
    int vn = data[2][y];      // minimum of Vingers in a construction with +*^..
    int a = data[3][y];       // best equation a
    int b = data[4][y];       // best equation b
    int c = data[5][y];       // best equation c
    int w1 = data[6][y];      // bigO stack size in a construction with +*
    int w2 = data[7][y];      // bigO stack size in a construction with +*^
    int wn = data[8][y];      // bigO stack size in a construction with +*^..
    
    // Check the data
    if(v1 == 0 || v2 == 0 || vn == 0 || a == 0 || b == 0)
      if(y > 1)
        throw new IllegalStateException("Data was missing for number " + y);
      
    // Add data up to totals
    totals[0] += v1;
    totals[1] += v2;
    totals[2] += vn;
    totals[3] += w1;
    totals[4] += w2;
    totals[5] += wn;
    
    String line = "";                // output string with the line of data for y
    
    if(vn > yNextVn[1])              // print special sign for special V occassion
    {
      yNextVn[0] = y;
      yNextVn[1] = vn;
      line += "~";                   // first ~ y begins next V era
    }
    else
      line += " ";                   // else print space
    
    if(wn > yNextWn[1])              // print special sign for special W occassion
    {
      yNextWn[0] = y;
      yNextWn[1] = wn;
      line += "~";                   // second ~ y begins next W era
    }
    else
      line += " ";                   // else print space
    
    // y is also the total of Vingers in a construction with only +
    String equation = a + OPERATORS[c] + b;
    //PP alternative start print: String equation = y < 8 ? ("" + y) : (a + OPERATORS[c] + b);
    
    // Format data for y
    line += String.format("%11d%9d%7d%7d   %5d%5d%5d       %-15s%n",
                          y, v1, v2, vn,   w1, w2, wn,     equation );
    
    // Experimental mathematical bit expression of the number
    if(PRINT_OBITS)
    {
      String obits = OBitExpression(y);
      line += obits + "\n";
      
      // Record statistical data about y's Obits 
      long len = (long)obits.length();
      // Show Obit trends with the deviation from the mean = "Obits" - "mean Obits"
      obitDeviationTotal += Math.abs(len - (0.+totals[6])/y);
      // Update total Obit lengths to compare Obits with binary numbers 
      totals[6] += len;
    }
    
    print(line);                     // print data for y - has line end!
    
    if(y%PRINT_MULTI_STATS == 0 &&   // print totals and gains at regular intervals
       y != data[0].length-1)        // if y is not the last number, else buildNumbers prints footer
    {
      printFooter(y);                // print statistics
      printHeader();                 // always start with the header 
    }
  }
  
  /**
   * Create and print the footer with the totals and statistical percentages.
   * @param y  can be the <code>yEnd</code> or some inbetween statistics request
   */
  public void printFooter(int y)
  {
    long yTotal = ((y+1L)*y)/2L;          // the apocryphal child Gauss total upto y
    
    // Create output string with footer
    String line = "   +-------------------------------------------------------------------------+\n";
    line += String.format("%16d%10d%10d%10d%10d%10d%10d%n%n", yTotal, 
                          totals[0], totals[1], totals[2], totals[3], totals[4], totals[5]);
    // 5 percentage stats
    double[] perc = { 100. - 100.*totals[0]/yTotal, 
                      100. - 100.*totals[1]/totals[0], 
                      100. - 100.*totals[2]/totals[1],
                      100. - 100.*totals[4]/totals[3], 
                      100. - 100.*totals[5]/totals[4] };
    
    // Print percentages gain for V and W, eg. how ^*+ beats +* Vingers 30.7% at endY=200000
    line += String.format("%16s%10.3g%%%9.3g%%%9.3g%%%9s%10.3g%%%9.3g%%%n%n", 
                          "gains v:", perc[0], perc[1], perc[2], 
                          "w:", perc[3], perc[4]);
    
    // Signs for the stats
    line += String.format("%16s%10s%10s%10s%10s%10s%10s%n", 
                          "N+", "+*", "+*^", "+*^..", "+*", "+*^", "+*^..");
    
    line += "    -------------------------------------------------------------------------\n";

    if(PRINT_OBITS)           // print Obits average binary loss
    {
      double yBinaryExp = Math.floor(Math.log(y+1.2)/Math.log(2.));   // eg. 7 => 3, 8 => 3
      int yBinaryBits = 0;
      int bits = 1;
      for(int k = 1; bits <= yBinaryExp; k *= 2, bits++)
        yBinaryBits += bits*k;                                        // 7 => 17, 8 => 17

      // Complete bits done, follows remaining bits
      int yDone = (int)(Math.pow(2.,yBinaryExp)-.5);                  // 7 => 7, 8 => 7
      yBinaryBits += (y - yDone)*bits;                                // 7 => 17+0, 8 => 17+4 = 21

      double obituary = (0.+totals[6] - yBinaryBits)/y;               // average Obit loss
      double ovary = obitDeviationTotal/y;                            // mean sum of deviations at y

      line += String.format("Obituary: %5.4g - %5.4g = %5.4g extra bits, Ovary: %5.4g%n", 
                            (0. + totals[6])/y, (0. + yBinaryBits)/y, obituary, ovary);
    }
    
    print(line);
  }
  
  /**
   * Print some text to the console.
   *
   * @param line  one or two lines of text
   */
  private void print(String line)
  {
    System.out.print(line);        // line should already have line end
    
    if(textWriter != null)         // also write to the file specified at main()
    {
      // Replace Unix '\n' by the Windows line.separator "\r\n" (but keep existing "\r\n")
      line = line.replaceAll("(?<!\\r)\\n", "\r\n");     // regex with negative lookbehind for '\r'
      
      try                          // write or PP-append new text to file
      {
        textWriter.write(line);
      }
      catch(IOException e)
      {
        System.err.println(e);
      }
    }
  }
  
  /**
   * Get a bigO mathematical bit expression (or "Obit") for a number. <br />
   * A small enough number will be a straight String of ones, eg. <code>"5 = 11111"</code>,
   * but larger numbers have a more succint mathematical expression starting with the prefix
   * <code>"0"</code> to distinguish a matBit number that's created with bigO.<br />
   * Each bigO has 3 arguments, where <code>c</code> is expressed as:
   * <code>1=+ 11=* 111=^ 1111=^^ 11111=^^^</code><br />
   * A series of <code>"0"</code> means a nesting of bigO inside itself, as in 
   * <code>30 = 3+3^3 = 0111001110111011101</code><br />
   * This is a test method - a binary bit representation of an integer will usually be smaller,
   * eg. <code>30 = 11110</code><br />
   * @param n   any natural number <code>n >= 0</code>
   * @return  the expression in "mathematical bits" 
              where every zero is a comma and every one adds 1 to a number.<br />
   * @throws IllegalArgumentException  if <code>n > MIN_ONES</code> and no data is available for it
   *         (MIN_ONES = 26)
   */
  public String OBitExpression(int n)
  {
    // Used variables
    String str = "";                   // the result string DOES NOT start with a BITMARK
    int MIN_ONES = 11;                 // for all n <= MIN_ONES ones are terser, not for MIN_ONES+1
    
    if(n <= MIN_ONES)                  // handle the smallest numbers first
    {
      for(int i = 0; i < n; i++)       // translate all n to a strings of ones
        str += "1";                    // add 1
      return str;                      // string of ones ready! eg. 5="11111" and 0=""
    }
    
    if(data[3][n] == 0)                // no data available for n
      throw new IllegalArgumentException("No data available for N=" + n);
    
    // Get best [PP?? consider stack for deep-nested bitO] last bigO equation
    String a = OBitExpression(data[3][n]);           // a in bigO(a,b,c)
    String b = OBitExpression(data[4][n]);           // b in bigO(a,b,c)
    String c = OBitExpression(data[5][n]) + "1";     // c in bigO(a,b,c) plus "1"
    String bitO = "0" + a + "0" + b + "0" + c;       // eg. 27=3^^2= 11101101111
    
    if(bitO.length() < n)              // where n is the length of its own row of ones
      str += bitO;                     // use bigO representation if it is at least as economical
    else
      for(int i = 0; i < n; i++)       // translate n to a string of ones
        str += "1";

    return str;                        // ready!
  }
  
  /**
   * Console method to use this class.<br /><br />
   * If the serialPath is specified this method tries to deserialize a file, 
   * and serializes a file at serialPath with all data when it's done.
   * @param args  the 1st argument should be an end number <code>yEnd</code>,<br />
   *        the 2nd optional argument is a file path to store data as text, like so:<br /> 
   *         <code>java bigOpera/BigOpera 10000 "operadata.txt"</code>
   * @throws SecurityException  if the security manager doesn't allow files to be read
   */
	public static void main(String[] args)
	{
    // Get 1st input argument for end number yEnd
    if(args.length < 1)
    {
      System.out.println("Enter the end number for the list as an integer.");
      return;
    }
    int nextY = Math.abs(Integer.parseInt(args[0]));        // no minimum
    
    // Get 2nd input argument for textFile
    File textFile = args.length > 1 ? (new File(args[1])) : null;   // null if not specified
    
    // Declare BigOpera object
    BigOpera app = new BigOpera();
    
    // Declare serialization streams
    ObjectInputStream ois = null;              // deserialization
    ObjectOutputStream oos = null;             // serialization
    
    try                                          // be careful to flush and close the textWriter
    {
      File bin = null;
      if(app.serialPath != null)                 // programmer must set the serialization file
      {
        // Create File for reading and writing
        bin = new File(app.serialPath);
        
        // Check if we can read a file that saved the last object of this class
        if(bin.exists() && bin.isFile() && bin.canRead())
        {
          // Try to read in the fields from a saved BigOpera object
          try
          {
            ois = new ObjectInputStream(
                  new BufferedInputStream(
                  new FileInputStream(bin)));    // like Ivor does it, page 392
            
            app = (BigOpera)ois.readObject();    // get old object
            ois.close();
          }
          catch(IOException e)
          {
            System.err.println(e);
            app = new BigOpera();                // continue anew
          }
          catch(ClassNotFoundException e)
          {
            System.err.println(e);
            app = new BigOpera();                // continue anew
          }
        }
      }
  
      if(textFile != null)
      {
        // Set up the textWriter 
        try
        {
          // Create a new, empty file if the file does not yet exist
          textFile.createNewFile();
          
          app.textWriter = new BufferedWriter(
                           new FileWriter(textFile, false));    // if true -> append new text to file
        }
        catch(IOException e)
        {
          System.err.println(e);
        }
      }
  
      // Set up stopwatch
      long time = System.currentTimeMillis();    // tic
      
      // Check for increase
      int len = app.data[0].length;              // end of stored numbers
      if(nextY <= len)                           // if number nextY is already stored
        nextY += len-1;                          // ..consider it an increase by nextY (minus zero index)
      
      // Start routine
      app.buildNumbers(nextY);
      
      // Record elapsed time
      time = System.currentTimeMillis() - time;  // tictoc
      String timeLine = String.format("%78s", "elapsed time " + time + "ms");
      
      System.out.println(timeLine);              // print time
      
      if(app.textWriter != null)                 // close the file specified at main()
      {
        try                                      // write or append new text to file
        {
          app.textWriter.write(timeLine);
          app.textWriter.close();                // data writing is done
        }
        catch(IOException e)
        {
          System.err.println(e);
        }
        finally
        {
          app.textWriter = null;                 // no remaining writer object
        }
      }
  
      if(bin != null)                            // if(serialPath != null)
      {
        // Try to write the fields of this BigOpera object
        try
        {
          // Create a new, empty file if the file does not yet exist
          bin.createNewFile();
          
          oos = new ObjectOutputStream(
                new BufferedOutputStream(
                new FileOutputStream(bin)));     // like Ivor does it, page 392
          
          oos.writeObject(app);                  // write the BigOpera object
          oos.close();
        }
        catch(NotSerializableException e)
        {
          System.err.println(e);
        }
        catch(InvalidClassException e)
        {
          System.err.println(e);
        }
        catch(IOException e)
        {
          System.err.println(e);
        }
      }
    }
    catch(Exception e)                           // catch any! exception
    {
      System.err.println(e);
    }
    catch(Error er)         // I am weary of any interruption that might loose data
    {
      try
      {
        app.textWriter.flush();
        app.textWriter.close();                  // flush and close the stream
        app.textWriter = null;
      }
      catch(IOException e)
      {}
    }
    finally
    {
      if(app.textWriter != null)
      {
        try
        {
          app.textWriter.flush();
          app.textWriter.close();                // flush and close the stream
        }
        catch(IOException e)
        {
          System.err.println(e + "\nData file is probably corrupted!");
        }
        app.textWriter = null;                   // no remaining writer object
      }
      ois = null;
      oos = null;
    }
  }
}
