Monday, January 16, 2017

Funky fibs

import java.util.*;

//=========================
// Custom Timeout Exception
//=========================

class TimeoutException extends Exception {

  public TimeoutException() {
    super();
  }

  public TimeoutException(String message) {
    super(message);
  }

}

//=====================================
// Utility that Adds, Counts, and Times
//=====================================

class Util {

  public static Integer timeLimitMs = 500;
  public static Long count = 0L;
  private static Long t0 = 0L;

  public static Long add(Long a, Long b)
  throws TimeoutException {
    count++;
    if (count % 10000 == 0) {
      checkTime();
    }
    return Math.addExact(a, b);
  }

  public static void start() {
    count = 0L;
    t0 = System.nanoTime();
  }

  public static Double stop() {
    return elapsedMs();
  }

  private static Double elapsedMs() {
    return (System.nanoTime() - t0)/1000000d;
  }

  private static void checkTime()
  throws TimeoutException {
    if (elapsedMs() > timeLimitMs) {
      throw new TimeoutException(timeLimitMs + " ms limit exceeded.");
    }
  }

}

//==========================
// Generic Abstract Function
//==========================

abstract class Func<In, Out> {

  public abstract Out call(Func<In, Out> self, In x)
  throws TimeoutException;

  public Out call(In x)
  throws TimeoutException {
    return this.call(this, x);
  }

}

//==========================
// Generic Memoized Function
//==========================

class MemoizedFunc<In, Out> extends Func<In, Out> {

  private Map<In, Out> cache = new HashMap<In, Out>();
  private Func<In, Out> otherFunc;

  public MemoizedFunc(Func<In, Out> func) {
    this.otherFunc = func;
  }

  public Out call(Func<In, Out> self, In x)
  throws TimeoutException {
    if (!cache.containsKey(x)) {
      cache.put(x, otherFunc.call(self, x)); 
    }
    return cache.get(x);
  }

}

//============================
// Abstract Fibonacci Function
//============================

abstract class Fib extends Func<Integer, Long> {

}

//============================
// Memoized Fibonacci Function
//============================

class MemoizedFib extends MemoizedFunc<Integer, Long> {

  MemoizedFib(Fib func) {
    super(func);
  }

}
//===========================
// Amnesic Fibonacci Function
//===========================
class AmnesicFib extends Fib {

  public Long call(Func<Integer, Long> self, Integer i)
  throws TimeoutException {
    if (i == 0) { // return first Fibonacci number
      return 0L;
    }
    else if (i == 1) { // return second Fibonacci number
      return 1L;
    }
    else if (i > 0) { // return ith Fibonacci number
      return Util.add(
        self.call(self, i-2),
        self.call(self, i-1));
    }
    else { // return ith "negafibonacci" number
      return Util.add(
        self.call(self, i+2),
        -1 * self.call(self, i+1));
    }
  }

}
//===========================
// Eidetic Fibonacci Function
//===========================
class EideticFib extends MemoizedFib {

  EideticFib() {
    super(new AmnesicFib());
  }

}
//===========================
// Grinder Fibonacci Function
//===========================
class GrinderFib extends Fib {

  public Long call(Func<Integer, Long> self, Integer i)
  throws TimeoutException {

    //----------------------------------------
    //  1 multiplier => Fibonacci number
    // -1 multiplier => "negafibonacci" number
    //  0 is the "bipolafibonacci" number
    //----------------------------------------

    Integer multiplier = i < 0 ? -1 : 1;

    Integer magnitude = multiplier * i;

    Long f2 = -1L;
    Long f1 = 1L;
    Long f0 = 0L;

    for (Integer j = 0; j < magnitude; j++) {
      f2 = f1;
      f1 = f0;
      f0 = Util.add(f2, (multiplier * f1));        
    }

    return f0;
  }

}
//===========================
// Unitary Fibonacci Function
//===========================
class UnitaryFib extends MemoizedFib {

  UnitaryFib() {
    super(new GrinderFib());
  }

}
//===========================
// Lacunar Fibonacci Function
//===========================
class LacunarFib extends Fib {

  public Long call(Func<Integer, Long> self, Integer i)
  throws TimeoutException {
    Double sqrt5 = Math.sqrt(5);
    Double phi = (1 + sqrt5)/2;
    Double phiHat = -1/phi;
    Double phiPowI = Math.pow(phi, i);
    Double phiHatPowI = Math.pow(phiHat, i);
    Double fib = (phiPowI - phiHatPowI)/sqrt5;
    return Math.round(fib);
  }

}

//=======
// Report
//=======

public class FunkyFibs {

  private final static int STACK_OVERFLOW = 1;
  private final static int DATATYPE_OVERFLOW = 2;
  private final static int TIMEOUT = 3;

  private final static String[] PRTF = { "%5s", "%55s", "%12d", "%12.3f" };
  private final static String[] PRTH = { "%5s", "%55s", "%12s", "%12s" };

  private static void printData(Func<Integer, Long> F, String[] seqNums, int i0) {

    String  nm = F.getClass().getName();
    Integer n;
    Long    Fn = null;   // nth Fibonacci number
    Double  dt = 0d;     // delta (change in) time in ms
    Double  sumTm = 0d;  // sum of times
    Long    sumCt = 0L;  // sum of counts (number of operations)
    Integer err;         // error code

    //---------------------------------------
    // print one data row per sequence number
    //---------------------------------------

    for (Integer i = i0; i < seqNums.length; i++) {
      err = 0;
      n = Integer.parseInt(seqNums[i]);
      Util.start();
      try {
        Fn = F.call(n);
      }
      catch(StackOverflowError soe) {
        Fn = null;
        err = STACK_OVERFLOW;
      }
      catch(TimeoutException toe) {
        Fn = null;
        err = TIMEOUT;
      }
      catch(Exception e) {
        Fn = null;
        err = DATATYPE_OVERFLOW;
      }
      dt = Util.stop();
      sumTm += dt;
      sumCt += Util.count;
      printDataRow(n, Fn, Util.count, dt, nm, err);
    }

    //----------------------------------------------------
    // if more than one sequence number, print summary row
    //----------------------------------------------------

    if (seqNums.length - i0 > 1) {
      printDataRow(null, null, sumCt, sumTm, "TOTALS", 0);
      System.out.println();
    }
  }

  private static void printHeaderRow() {
    System.out.println("FunkyFibs: " + (new Date()).toString());
    System.out.println();
    System.out.printf(PRTH[0], "i");
    System.out.printf(PRTH[1], "Fib(i)");
    System.out.printf(PRTH[2], "operations");
    System.out.printf(PRTH[3], "time (ms)");
    System.out.println();
    System.out.printf(PRTH[0], "-----");
    System.out.printf(PRTH[1], "------------------------------------------------------");
    System.out.printf(PRTH[2], "-----------");
    System.out.printf(PRTH[3], "-----------");
    System.out.println();
  }

  private static void printDataRow
  (Integer n, Long Fn, Long ct, Double dt, String name, Integer err) {
    if (n != null) {
      System.out.printf(PRTF[0], n.toString());
    }
    else {
      System.out.printf(PRTF[0], "");
    }
    if (Fn == null && err == 0) {
      System.out.printf(PRTF[1], "");
    }
    else {
      switch(err) {
      case 0:
        System.out.printf(PRTF[1], Fn);
        break;
      case STACK_OVERFLOW:
        System.out.printf(PRTF[1], "STACK_OVERFLOW");
        break;
      case DATATYPE_OVERFLOW:
        System.out.printf(PRTF[1], "DATATYPE_OVERFLOW");
        break;
      case TIMEOUT:
        System.out.printf(PRTF[1], "TIMEOUT");
        break;
      default:
        System.out.printf(PRTF[1], "UNEXPECTED_ERROR");
        break;
      }
    }
    System.out.printf(PRTF[2], ct);
    System.out.printf(PRTF[3], dt);
    System.out.print(' ');
    System.out.print(name);
    System.out.println();
  }

  public static void unoReport(Func<Integer, Long> fib, String[] args, int i0) {
    printHeaderRow();
    printData(fib, args, i0);
    if (args.length == 1) {
      System.out.println();
    }
  }

  public static void comboReport(String[] args, int i0) {
    printHeaderRow();
    printData(new AmnesicFib(), args, i0);
    printData(new EideticFib(), args, i0);
    printData(new GrinderFib(), args, i0);
    printData(new UnitaryFib(), args, i0);
    printData(new LacunarFib(), args, i0);
    if (args.length == 1) {
      System.out.println();
    }
  }

  public static void main(String[] args) {
    if (args.length == 0) {
      return;
    }
    switch(args[0].toLowerCase()) {
    case "amnesic":
      unoReport(new AmnesicFib(), args, 1);
      break;
    case "eidetic":
      unoReport(new EideticFib(), args, 1);
      break;
    case "grinder":
      unoReport(new GrinderFib(), args, 1);
      break;
    case "unitary":
      unoReport(new UnitaryFib(), args, 1);
      break;
    case "lacunar":
      unoReport(new LacunarFib(), args, 1);
      break;
    default:
      comboReport(args, 0);
      break;
    }
  }

}