Pages

Angle

/*  http://scribbledecobble.blogspot.com/2018/03/angle.html
 *  by John P. Spurgeon
 *
 *
 *
 *  CONTENTS
 *
 *      0. Make Angle great again!
 *      1. Interfaces
 *      2. BigNum: Avoids Floats & Doubles
 *      3. Pseudo-continuous Measure
 *      4. Angle Multi-tool
 *      5. The Angle Superclass
 *      6. Some Angle Subclasses
 *      7. Smoke Test Algorithm
 *      8. Pseudo-random Data
 *      9. Songs
 *
 *
 *
 *              The use of subclassing is controversial... In this book, we avoid subclassing... [T]he
 *        designer of a class such as a Vector may have taken great care to make the Vector immutable,
 *           but a subclass, with full access to those instance variables, can recklessly change them.
 *
 *          — ROBERT SEDGEWICK and KEVIN WAYNE, Computer Science: An Interdisciplinary Approach (2017)
 *
 *
 *                                                                     You’ll shoot your eye out, kid.
 *
 *                                                         — SANTA CLAUS in “A Christmas Story” (1983)
 */

/*
 *                                      Well, here’s your box. Nearly everything I have is in it, and
 *                                 it is not full. Pain and excitement are in it, and feeling good or
 *                                   bad and evil thoughts and good thoughts — the pleasure of design
 *                                            and some despair and the indescribable joy of creation.
 *
 *                                                — JOHN STEINBECK, dedication of East of Eden (1952)
 *
 *
 *                           All of the major problems associated with computer programming—issues of
 *                         reliability, portability learnability, maintainability, and efficiency—are
 *                       ameliorated when programs and their dialogs with users become more literate.
 *
 *                                                     — DONALD E. KNUTH, Literate Programming (1992)
 *
 *
 *                          I believe that the time is ripe for significantly better documentation of
 *                    programs, and that we can best achieve this by considering programs to be works
 *                  of LITERATURE. Hence, my title: “Literate Programming.” ... By coining the phrase
 *                     “literate programming,” I am imposing a moral commitment on everyone who hears
 *                              the term; surely nobody wants to admit writing an ILLITERATE program.
 *
 *                                                     — DONALD E. KNUTH, Literate Programming (1992)
 *
 *
 *                         [A]n unforeseen problem has, however, arisen: I suddenly have a collection
 *                      of programs that seem quite beautiful in my own eyes, and I have a compelling
 *                        urge to publish all of them so that everyone can admire these works of art.
 *                          ... if I keep accumulating such gems, I'll soon run out of storage space,
 *                            and my office will be encrusted with webs of my own making. There is no
 *                                   telling what will happen if lots of other people catch WEB fever
 *                                                  and start foisting their creations on each other.
 *
 *                                                     — DONALD E. KNUTH, Literate Programming (1992)
 *
 *
 *                      I’m glad that the idea of STYLE in programming is now coming to the forefront
 *                        at last, and I hope that most of you have seen the excellent little book on
 *                      ELEMENTS OF PROGRAMMING STYLE by Kernighan and Plauger. In this connection it
 *                        is most important for us all to remember that there is no one “best” style;
 *                                 everybody has their own preferences, and it is a mistake to try to
 *                                                               force people into an unnatural mold.
 *
 *                                                     — DONALD E. KNUTH, Literate Programming (1992)
 *
 *
 *                                                                               Omit needless words.
 *
 *                                                   — STRUNK and WHITE, The Elements of Style (1979)
 *             
 */

/*  0. Make Angle great again!
 *
 *      It is generally very difficult to keep up with a field that is economically profitable, and
 *  so it is only natural to expect that today’s standardized systems of measurement will eventually
 *  be superseded by better ones. The good news is, nobody owns standardization. Systems of
 *  measurement are in competition with similar systems all over the world. Standards aren’t owed a
 *  career. They are employed at will. As Michael Corleone said, “The new overthrows the old. It’s
 *  natural.” Ergo, your software must be Agile to survive.
 *      Software developers are, first and foremost, businessmen and women. We want no conflict with
 *  any one. (Get it?) All kidding aside, when someone says they are sure you will find it very
 *  instructive to actually take all of the data from a given problem and convert it into, say,
 *  portrzebie units, work the problem in the new system, and then transfer the answer back into the
 *  original units again, the software developer queries, “How many times?”
 *      Rounding error and overflow notwithstanding, Angle is the solution to all that ails your app.
 *  (Who here cares about rounding error and overflow? Raise your hands. That’s what I thought.)
 *  Suppose you’ve patented new algorithms for division and addition that obviate the angst of all
 *  the rounding error and overflow worry warts out there. They’re sure to make you billions, give or
 *  take a few. But it’s not about the money anymore. It’s about world domination. You need everyone
 *  to pay and pay attention to you. You need fame and immortality. Angle supports units for that!
 *  Moreover, when the well runs dry — and it will — all you have to do is toss out the old standards
 *  and invent new ones. Drain the swamp, as they say. If you’ve got Angle, all you gotta do is
 *  change a few lines of code and you’re good to go to the bank again. But you'll need a great
 *  campaign slogan. Something really great.
 */

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Comparator;
import java.util.List;
import java.util.Stack;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.SortedSet;
import java.util.TreeSet;

/*  1. Interfaces
 *
 *                      “Can you do Addition?” the White Queen asked. “What’s one and one and one and
 *                                              one and one and one and one and one and one and one?”
 *                                                        “I don’t know,” said Alice. “I lost count.”
 *                       “She ca’n’t do Addition,” the Red Queen interupted. “Can you do Subtraction?
 *                                                                             Take nine from eight.”
 *                          “Nine from eight I ca’n’t, you know,” Alice replied very readily: “but——”
 *                           “She ca’n’t do Subtraction,” said the White Queen. “Can you do Division?
 *
 *                                                  — LEWIS CARROLL, Through The Looking Glass (1946)            
 */

interface Measure<N extends Comparable<N>> {
    N value();       // A possibly negative decimal or integral number.
    N size();        // The magnitude (absolute value) of value().
    String units();  // The units of size() and value().
}

interface StdMeasure<N extends Comparable<N>> {
    N stdValue();      // A standard equivalent of value().
    N stdSize();       // The size of stdValue().
    String stdUnits(); // The units of stdValue() and stdSize().
}

interface Num extends Comparable<Num> {
    /* You can make TINY_PIECE_OF_PI as large as you want, but it will still be very tiny. */
    static final String TINY_PIECE_OF_PI = "3.14159265358979323846264338327950288419716939937510";
    static final int PRECISION = 16; // Should be limited by length of TINY_PIECE_OF_PI.
    static final int ROUND = 14;     // Should be limited by PRECISION
    static final int DISPLAY = 4;    // Should be limited by ROUND
    static final Num ZERO = new BigNum("0");
    static final Num ONE = new BigNum("1");
    static final Num TWO = new BigNum("2");
    static final Num PI =  new BigNum(TINY_PIECE_OF_PI);
    static final Num TAO = TWO.multiply(PI);

    Num negate();
    Num abs();
    Num add(Num augend);
    Num subtract(Num subtrahend);
    Num multiply(Num multiplicand);
    Num divide(Num divisor);
    Num remainder(Num divisor);
    boolean isEqualTo(Num other);
    boolean isLessThan(Num other);
    boolean isMoreThan(Num other);
    double roundedValue();
    BigDecimal bigDecimalValue();
}

interface StdConMeas extends Measure<Num>, StdMeasure<Num> {
    // A type that is a mixture of types.
}

/*  2. BigNum: Avoids Floats & Doubles (a precise approach to approximation)
 *
 *                                      Item 48: Avoid float and double if exact answers are required
 *
 *                                                              — JOSHUA BLOCH, Effective Java (2008)
 */

final class BigNum implements Num {
    static final MathContext MC = new MathContext(Num.PRECISION);
    static final MathContext ROUND_MC = new MathContext(Num.ROUND);

    private final BigDecimal val;          // The "wrapped" or "boxed" BigDecimal value.

    public BigNum(BigDecimal bigDecimal)   { val = bigDecimal; }
    public BigNum(Num n)                   { val = n.bigDecimalValue(); }
    public BigNum(String strVal)           { val = new BigDecimal(strVal, BigNum.MC); }
    public BigNum(Integer n)               { this(n.toString()); }
    public BigDecimal bigDecimalValue()    { return val; }
    public final Num abs()                 { return new BigNum(val.abs(BigNum.MC)); }
    public final Num negate()              { return new BigNum(val.negate(BigNum.MC)); }
    public final Num add(Num n)            { return new BigNum(val.add(n.bigDecimalValue(), BigNum.MC)); }
    public final Num subtract(Num n)       { return new BigNum(val.subtract(n.bigDecimalValue(), BigNum.MC)); }
    public final Num multiply(Num n)       { return new BigNum(val.multiply(n.bigDecimalValue(), BigNum.MC)); }
    public final Num divide(Num n)         { return new BigNum(val.divide(n.bigDecimalValue(), BigNum.MC)); }
    public final Num remainder(Num n)      { return new BigNum(val.remainder(n.bigDecimalValue(), BigNum.MC)); }
    public final boolean isEqualTo(Num n)  { return subtract(n).roundedValue() == 0; }
    public final boolean isLessThan(Num n) { return subtract(n).roundedValue() < 0; }
    public final boolean isMoreThan(Num n) { return subtract(n).roundedValue() > 0; }
    public final double roundedValue()     { return val.round(BigNum.ROUND_MC).doubleValue(); }
    public final int compareTo(Num n)      { return val.compareTo(n.bigDecimalValue()); }
    public boolean equals(Object x) {
        if (x == null || !BigNum.class.isAssignableFrom(x.getClass())) return false;
        BigNum that = (BigNum) x;
        return this.val.equals(that.val); 
    }
}

/*                                  Eats, Shoots & Leaves: the zero tolerance approach to punctuation
 *
 *                                                        — The title of a book by Lynne Truss (2004)
 */

/*  3. Pseudo-continuous Measure
 *
 *                                  It is downright sinful to teach the abstract before the concrete.
 *
 *                                              — Z. A. MELZAK, quoted in Concrete Mathematics (1992)
 */

abstract class PseudoContinuousMeasure implements StdConMeas {

    // At the very beginning, let’s decide to pronounce the name right: zee-val, like potter-zee-bee.

    protected final Num zVal;                            // A standardized pidgin or bridge value.

    protected PseudoContinuousMeasure(Num convertedVal)  { zVal = convertedVal; }
    protected PseudoContinuousMeasure(String strVal)     { zVal = convertToZVal(strVal); }
    protected PseudoContinuousMeasure(StdConMeas other)  { zVal = other.stdValue(); }
    protected abstract Num toZFactor();                  // Implementations should be responsible.
    protected abstract Num fromZFactor();                // Implementations should do the right thing.
    protected final Num convertFromZVal()                { return fromZFactor().multiply(zVal); }
    protected final Num convertToZVal(String strVal)     { return toZFactor().multiply(new BigNum(strVal)); }
    public final Num value()                             { return convertFromZVal(); }
    public final Num size()                              { return convertFromZVal().abs(); }
    public String units()                                { return stdUnits(); /* Implementations will vary. */ }
    public final Num stdValue()                          { return zVal; }
    public final Num stdSize()                           { return zVal.abs(); }
    public abstract String stdUnits();                   // The buck should stop somewhere.
    public String toString()                             { return toString(Num.DISPLAY); }
    public String toString(int n) {
        return valueToString(n) + " (" + stdValueToString(n) + ")";
    }
    protected String valueToString(int n) {
        return PseudoContinuousMeasure.toString(value().roundedValue(), n, units());
    }
    protected String stdValueToString(int n) {
        return PseudoContinuousMeasure.toString(stdValue().roundedValue(), n, stdUnits());
    }
    protected static String toString(Double value, int decimalPlaces, String units) {
        return String.format("%." + decimalPlaces + "f " + units, value);
    }
    public boolean equals(Object x) {
        if (x == null || !PseudoContinuousMeasure.class.isAssignableFrom(x.getClass())) return false;
        PseudoContinuousMeasure that = (PseudoContinuousMeasure) x;
        return this.zVal.equals(that.zVal) && this.stdUnits().equals(that.stdUnits()); 
    }
}

/*                         That was Will all over. He scorned the vague, the tame, the colorless, the
 *                     irresolute. He felt it was worse to be irresolute than to be wrong. I remember
 *                     a day in class when he leaned far forward, in his characteristic pose—the pose
 *                 of a man about to impart a secret—and croaked, “If you don’t know how to pronounce
 *                      a word, say it loud! If you don’t know how to pronounce a word, say it loud!”
 *                           This comical piece of advice struck me as sound at the time, and I still
 *                            respect it. Why compound ignorance with inaudibility? Why run and hide?
 *
 *                                               — E. B. WHITE, The Elements of Style, 3rd ed. (1979)
 */

/*  4. Angle Multi-tool
 *
 *                  In recent years, a number of urban and outdoor multi-tools have sprouted offering
 *                  non-traditional tools one would not expect to find in a single unit. Substituting
 *                      a toolbox, these multi-tools functions include a hammer, spirit level, camera
 *                    tripod, LED light, lighter, tape measure and an assortment of screwdriver bits.
 *
 *                                 — https://en.wikipedia.org/wiki/Multi-tool (accessed 2 April 2018)
 */

final class AngleMultiTool {
    private final Num full, half, quarter;
    public AngleMultiTool(Num unitsPerCycle) {
        full = unitsPerCycle;
        half = full.divide(Num.TWO);
        quarter = half.divide(Num.TWO);
    }
    public final boolean isNegative(Num a)              { return a.isLessThan(Num.ZERO); }
    public final boolean isPositive(Num a)              { return a.isMoreThan(Num.ZERO); }
    public final boolean isZero(Num a)                  { return a.isEqualTo(Num.ZERO); }
    public final boolean isAcute(Num a)                 { return isPositive(a) && a.isLessThan(quarter); }
    public final boolean isRight(Num a)                 { return a.isEqualTo(quarter); }
    public final boolean isObtuse(Num a)                { return a.isMoreThan(quarter) && a.isLessThan(half);  }
    public final boolean isStraight(Num a)              { return a.isEqualTo(half); }
    public final boolean isConcave(Num a)               { return a.isLessThan(half); }
    public final boolean isConvex(Num a)                { return a.isMoreThan(half); }
    public final boolean areCongruent(Num a, Num b)     { return a.isEqualTo(b); }
    public final boolean areComplementary(Num a, Num b) { return a.add(b).isEqualTo(quarter); }
    public final boolean areSupplementary(Num a, Num b) { return a.add(b).isEqualTo(half); }
    public final boolean areExplementary(Num a, Num b)  { return a.add(b).isEqualTo(full); }
    public final boolean areCoterminal(Num a, Num b) {
        return toMinPosCoterminal(a).isEqualTo(toMinPosCoterminal(b));
    }
    public final Num toComplement(Num a) { return quarter.subtract(a); }
    public final Num toSupplement(Num a) { return half.subtract(a); }
    public final Num toConjugate(Num a)  { return full.subtract(a); }
    public final Num toReduced(Num a)    { return a.remainder(full); }
    public final Num toReference(Num a) {
        final Num r = a.remainder(full);                     // assert: -full < r < full
        if (r.isMoreThan(half)) return full.subtract(r);  // return: 0 < value < half
        if (r.isLessThan(half.negate())) return full.add(r); // return: 0 < value < half
        if (r.isLessThan(Num.ZERO)) return r.negate();       // return: 0 < value < half
        return r;                                            // return: 0 <= r < half
    }
    public final Num toMinPosCoterminal(Num a) {
        final Num r = a.remainder(full);                     // assert: -full < r < full
        return (r.isLessThan(Num.ZERO)) ? r.add(full) : r;   // return: 0 <= r < full
    }
}

/*  5. The Angle Superclass */

class Angle extends PseudoContinuousMeasure implements Comparable<Angle> {
    private static final class ValueComparator implements Comparator<Angle> {
        public final int compare(Angle a, Angle b) { return a.stdValue().compareTo(b.stdValue()); }
    }
    private static final class SizeComparator implements Comparator<Angle> {
        public final int compare(Angle a, Angle b) { return a.stdSize().compareTo(b.stdSize()); }
    }

    public static final String UNITS = "turns";
    public static final Num UNITS_PER_CYCLE = new BigNum("1");
    public static final Comparator<Angle> VALUE_COMPARATOR = new ValueComparator();
    public static final Comparator<Angle> SIZE_COMPARATOR = new SizeComparator();
    public static final Comparator<Angle> NATURAL_COMPARATOR = SIZE_COMPARATOR;

    private static final AngleMultiTool MULTI_TOOL =
        new AngleMultiTool(Angle.UNITS_PER_CYCLE); // Initialize after UNITS_PER_CYCLE has been initialized.

    private final AngleMultiTool tool()            { return Angle.MULTI_TOOL; }
    protected final Num toZFactor()                { return Angle.UNITS_PER_CYCLE.divide(unitsPerCycle()); }
    protected final Num fromZFactor()              { return unitsPerCycle().divide(Angle.UNITS_PER_CYCLE); }
    protected Num unitsPerCycle()                  { return Angle.UNITS_PER_CYCLE; }
    protected Angle newAngle(Num zVal)             { return new Angle(zVal); }
    protected Angle(Num zVal)                      { super(zVal); }
    protected Angle(String strVal)                 { super(strVal); }
    public Angle(Angle a)                          { super(a); }

    public final String stdUnits()                 { return Angle.UNITS; }
    public final boolean isNegative()              { return tool().isNegative(zVal); }
    public final boolean isZero()                  { return tool().isZero(zVal); }
    public final boolean isAcute()                 { return tool().isAcute(zVal); }
    public final boolean isRight()                 { return tool().isRight(zVal); }
    public final boolean isObtuse()                { return tool().isObtuse(zVal); }
    public final boolean isStraight()              { return tool().isStraight(zVal); }
    public final boolean isConcave()               { return tool().isConcave(zVal); }
    public final boolean isConvex()                { return tool().isConvex(zVal); }
    public final boolean isCongruentTo(Angle a)    { return tool().areCongruent(zVal, a.zVal); }
    public final boolean isComplementOf(Angle a)   { return tool().areComplementary(zVal, a.zVal); }
    public final boolean isSupplementOf(Angle a)   { return tool().areSupplementary(zVal, a.zVal); }
    public final boolean isConjugatOf(Angle a)     { return tool().areExplementary(zVal, a.zVal); }
    public final boolean isCoterminalWith(Angle a) { return tool().areCoterminal(zVal, a.zVal); } 
    public final Angle toComplement()              { return newAngle(tool().toComplement(zVal)); }
    public final Angle toSupplement()              { return newAngle(tool().toSupplement(zVal)); }
    public final Angle toConjugate()               { return newAngle(tool().toConjugate(zVal)); }
    public final Angle toReduced()                 { return newAngle(tool().toReduced(zVal)); }
    public final Angle toReference()               { return newAngle(tool().toReference(zVal)); }
    public final Angle toMinPosCoterminal()        { return newAngle(tool().toMinPosCoterminal(zVal)); }
    public final Angle negate()                    { return newAngle(zVal.negate()); }
    public final Angle abs()                       { return newAngle(zVal.abs()); }
    public final Angle add(Angle a)                { return newAngle(zVal.add(a.zVal)); }
    public final Angle subtract(Angle a)           { return newAngle(zVal.subtract(a.zVal)); }
    public final int compareTo(Angle a)            { return NATURAL_COMPARATOR.compare(this, a); }
}

/*  6. Some Angle Subclasses */

final class Radians extends Angle {
    public static final String UNITS = "radians";
    public static final Num UNITS_PER_CYCLE = Num.TAO;
    protected final Num unitsPerCycle()            { return Radians.UNITS_PER_CYCLE; }
    protected Angle newAngle(Num zVal)             { return new Radians(zVal); }
    protected Radians(Num zVal)                    { super(zVal); }
    public Radians(Angle angle)                    { super(angle); }
    public Radians(String strVal)                  { super(strVal); }
    public final String units()                    { return Radians.UNITS; }
}
class Degrees extends Angle {
    public static final String UNITS = "degrees";
    public static final Num UNITS_PER_CYCLE = new BigNum("360");
    protected final Num unitsPerCycle()            { return Degrees.UNITS_PER_CYCLE; }
    protected Angle newAngle(Num zVal)             { return new Degrees(zVal); }
    protected Degrees(Num zVal)                    { super(zVal); }
    public Degrees(Angle angle)                    { super(angle); }
    public Degrees(String strVal)                  { super(strVal); }
    public String units()                          { return Degrees.UNITS; }
}
final class DegMinSec extends Degrees {
    private static final Num NUM60 = new BigNum("60"), NUM3600 = new BigNum("3600");
    public static String toString(String d, String m, String s) {
        Num numD = new BigNum(d), numM = new BigNum(m), numS = new BigNum(s);
        return numD.add(numM.divide(NUM60)).add(numS.divide(NUM3600)).bigDecimalValue().toString();
    }
    public static final String UNITS = "deg-min-sec";
    protected Angle newAngle(Num zVal)             { return new DegMinSec(zVal); }
    protected DegMinSec(Num zVal)                  { super(zVal); }
    public DegMinSec(Angle angle)                  { super(angle); }
    public DegMinSec(String d, String m, String s) { super(DegMinSec.toString(d, m, s)); }
    public final String units()                    { return DegMinSec.UNITS; }
    protected final String valueToString(int n) {
        Num d = value(), m = NUM60.multiply(d.remainder(Num.ONE)), s = NUM60.multiply(m.remainder(Num.ONE));
        return PseudoContinuousMeasure.toString(d.roundedValue(), 0, "deg") + " " +
               PseudoContinuousMeasure.toString(m.roundedValue(), 0, "min") + " " +
               PseudoContinuousMeasure.toString(s.roundedValue(), Num.DISPLAY, "sec");
    }
}
class Zygo extends Angle { // A so-called revolutionary unit of measure.
    public static final String UNITS = "zygo";
    public static final Num UNITS_PER_CYCLE = new BigNum("100");
    protected final Num unitsPerCycle()            { return Zygo.UNITS_PER_CYCLE; }
    protected Angle newAngle(Num zVal)             { return new Zygo(zVal); }
    protected Zygo(Num zVal)                       { super(zVal); }
    public Zygo(Angle angle)                       { super(angle); }
    public Zygo(String strVal)                     { super(strVal); }
    public String units()                          { return Zygo.UNITS; }
}

/*                 Many of the basic potrzebie units have been acceptable for some time to the Google
 *                 Calculator as well as to Wolfram|Alpha™, so it is natural to wonder how many other
 *                                                    software systems will adopt them in the future.
 *
 *                                           — DONALD E. KNUTH, Selected Papers on Fun & Games (2011)
 */

/*  7. Smoke Test Algorithm */

public class AngleTester {
    private static final SortedSet<Angle> orderedSet = new TreeSet<Angle>(Angle.SIZE_COMPARATOR);
    private static final SortedSet<Angle> relatedSet = new TreeSet<Angle>(Angle.VALUE_COMPARATOR);
    private static void debug(String s) { /* System.out.print(s); */ }
    private static String angleToString(Angle a, String label) {
        return label + ": " + a.toString() + " " + (a.isZero() ? "zero" : a.isAcute() ? "acute" :
            a.isRight() ? "right" : a.isObtuse() ? "obtuse" : a.isStraight() ? "straight" :
            a.isConvex() ? "convex" : a.isNegative() ? "negative" : "snap!") + "\n";
    }
    private static void process(Angle a) { orderedSet.add(a);   debug(angleToString(a, "angle"));
        Angle com = a.toComplement();      relatedSet.add(com); debug(angleToString(com, "complement"));
        Angle sup = a.toSupplement();      relatedSet.add(sup); debug(angleToString(sup, "supplement"));
        Angle con = a.toConjugate();       relatedSet.add(con); debug(angleToString(con, "conjugate"));
        Angle ref = a.toReference();       relatedSet.add(ref); debug(angleToString(ref, "reference"));
    }
    private static List<Angle> parseAngles(String[] args) {
        final List<Angle> multiset = new LinkedList<Angle>();
        final Stack<String> values = new Stack<String>();
        for (String arg : args) {
            switch (arg) {
                case Angle.UNITS:     if (!values.empty()) multiset.add(new Angle(values.pop())); break;
                case Radians.UNITS:   if (!values.empty()) multiset.add(new Radians(values.pop())); break;
                case Degrees.UNITS:   if (!values.empty()) multiset.add(new Degrees(values.pop())); break;
                case Zygo.UNITS:      if (!values.empty()) multiset.add(new Zygo(values.pop())); break;
                case DegMinSec.UNITS: String deg, min, sec;
                                      if (!values.empty()) sec = values.pop(); else break;
                                      if (!values.empty()) min = values.pop(); else break;
                                      if (!values.empty()) deg = values.pop(); else break;
                                      multiset.add(new DegMinSec(deg, min, sec)); break;
                default:              values.push(arg);
            }
        }
        while (!values.empty()) multiset.add(new Zygo(values.pop())); // Use the default units du jour.
        return multiset;
    }
    public static void smokeTest(String[] input, String expected, String id) {    //  ALGORITHM S (Smoke Test).
        orderedSet.clear(); relatedSet.clear();                                   //  0. Clear data structures.
        final List<Angle> multiset = parseAngles(input);                          //  1. Parse input.
        for (Angle a : multiset) process(a);                                      //  2. Process input.
        StringBuilder results = new StringBuilder("\nTEST ID: " + id + "\n");     //  3. Begin with main heading.
        results.append("\nUNORDERED MULTISET\n");                                 //  4. Append first subheading.
        for (Angle a : multiset) results.append(angleToString(a, "angle"));       //  5. Append first result set.
        results.append("\nSET WITHOUT DUPLICATES ORDERED BY SIZE\n");             //  6. Append second subheading.
        for (Angle a : orderedSet) results.append(angleToString(a, "angle"));     //  7. Append second result set.
        results.append("\nRELATED SET WITHOUT DUPLICATES ORDERED BY VALUE\n");    //  8. Append third subheading.
        for (Angle a : relatedSet) results.append(angleToString(a, "angle"));     //  9. Append third result set.
        System.out.println(results);                                              // 10. Print results.
        if (expected == null) return;                                             // 11. If not verifiable: done.
        String actual = Data.compress(results.toString());                        // 12. Compress results.
        // Data.printAsCode(actual, 90);                                          // 13. (Print new expectations.)
        System.out.println(actual.equals(expected) ? "Passed!" : "FAILED");       // 14. Compare and report. (Done.)
    }
    public static void main(String[] args) {                                      //  ALGORITHM M (Main Program).
        System.out.println(new Song("Turn! Turn! Turn!"));                        //  0. Print theme song.
        smokeTest(Dataset1.input, Dataset1.expected, "SMOKE TEST 1");             //  1. Execute first smoke test.
        smokeTest(args, null, "AD HOC");                                          //  2. Execute ad hoc test.
    }
}

/*  8. Pseudo-random Data
 *
 *                 [Don] observed that a program running on a table of “real data,” ... is a lot more
 *                                        interesting than the same program running on “random data.”
 *
 *                                  — PAUL ROBERTS, in Literate Programming by Donald E. Knuth (1992)
 *
 *      Table 1 illustrates the typical behavior of comparison counting, by applying it to 16 numbers
 *    that were chosen at random by the author on March 19, 1963. The same 16 numbers will be used to
 *                            illustrate almost all of the other methods that we shall discuss later.
 *
 *                      — DONALD E. KNUTH, vol. 3 (2nd ed.) of The Art of Computer Programming (1998)
 */

final class Data {
    public static String compress(String s) { return s.toString().replaceAll("\\s+",""); } // Remove white space.
    public static void printAsCode(String s, int size) {
        int start = 0, firstSize = 56;
        String data = s.substring(start, Math.min(s.length(), start + firstSize));
        System.out.print("\"" + data + "\"");
        for (start += firstSize; start < s.length(); start += size) {
            data = s.substring(start, Math.min(s.length(), start + size));
            System.out.print(" +\n        \"" + data + "\"");
        }
        System.out.println(";");
    }
}

final class Dataset1 {
    public static final String[] input = { "503.087", "radians", "512.061", "degrees", "908", "170", "897",
        "deg-min-sec", "275", "zygo", "653.426", "154", "509", "deg-min-sec", "612", "677", "765.703"
    };
    public static final String expected = "TESTID:SMOKETEST1UNORDEREDMULTISETangle:503.0870radians(" +
        "80.0688turns)convexangle:512.0610degrees(1.4224turns)convexangle:911deg5min57.0000sec(2.53" +
        "08turns)convexangle:275.0000zygo(2.7500turns)convexangle:656deg8min2.6000sec(1.8226turns)c" +
        "onvexangle:765.7030zygo(7.6570turns)convexangle:677.0000zygo(6.7700turns)convexangle:612.0" +
        "000zygo(6.1200turns)convexSETWITHOUTDUPLICATESORDEREDBYSIZEangle:512.0610degrees(1.4224tur" +
        "ns)convexangle:656deg8min2.6000sec(1.8226turns)convexangle:911deg5min57.0000sec(2.5308turn" +
        "s)convexangle:275.0000zygo(2.7500turns)convexangle:612.0000zygo(6.1200turns)convexangle:67" +
        "7.0000zygo(6.7700turns)convexangle:765.7030zygo(7.6570turns)convexangle:503.0870radians(80" +
        ".0688turns)convexRELATEDSETWITHOUTDUPLICATESORDEREDBYVALUEangle:-501.5162radians(-79.8188t" +
        "urns)negativeangle:-499.9454radians(-79.5688turns)negativeangle:-496.8038radians(-79.0688t" +
        "urns)negativeangle:-740.7030zygo(-7.4070turns)negativeangle:-715.7030zygo(-7.1570turns)neg" +
        "ativeangle:-665.7030zygo(-6.6570turns)negativeangle:-652.0000zygo(-6.5200turns)negativeang" +
        "le:-627.0000zygo(-6.2700turns)negativeangle:-587.0000zygo(-5.8700turns)negativeangle:-577." +
        "0000zygo(-5.7700turns)negativeangle:-562.0000zygo(-5.6200turns)negativeangle:-512.0000zygo" +
        "(-5.1200turns)negativeangle:-250.0000zygo(-2.5000turns)negativeangle:-821deg-5min-57.0000s" +
        "ec(-2.2808turns)negativeangle:-225.0000zygo(-2.2500turns)negativeangle:-731deg-5min-57.000" +
        "0sec(-2.0308turns)negativeangle:-175.0000zygo(-1.7500turns)negativeangle:-566deg-8min-2.60" +
        "00sec(-1.5726turns)negativeangle:-551deg-5min-57.0000sec(-1.5308turns)negativeangle:-476de" +
        "g-8min-2.6000sec(-1.3226turns)negativeangle:-422.0610degrees(-1.1724turns)negativeangle:-3" +
        "32.0610degrees(-0.9224turns)negativeangle:-296deg-8min-2.6000sec(-0.8226turns)negativeangl" +
        "e:-152.0610degrees(-0.4224turns)negativeangle:0.4322radians(0.0688turns)acuteangle:12.0000" +
        "zygo(0.1200turns)acuteangle:64deg52min57.4000sec(0.1774turns)acuteangle:23.0000zygo(0.2300" +
        "turns)acuteangle:25.0000zygo(0.2500turns)rightangle:34.2970zygo(0.3430turns)obtuseangle:15" +
        "2.0610degrees(0.4224turns)obtuseangle:169deg55min3.0000sec(0.4692turns)obtuse";
}

/*  9. Songs
 *
 *    In music, segue is a direction to the performer. It means continue (the next section) without a
 *     pause. In live performance, a segue can occur during a jam session, where the improvisation of
 *  the end of one song progresses into a new song. Segues can even occur between groups of musicians
 *   during live performance. For example, as one band finishes its set, members of the following act
 *                   replace members of the first band one by one, until a complete band swap occurs.
 *
 *                                     — https://en.wikipedia.org/wiki/Segue (accessed 31 March 2018)
 */

final class Songs {
    public static List<String> getVerses(String songTitle) { return new LinkedList<String>(); } // to do
    public static Num getVerseCount(String songTitle)      { return new BigNum(getVerses(songTitle).size()); }
    public static Num getAvgVerseLength(String songTitle)  { return null; } // to do
    public static String getRefrain(String songTitle) {
        StringBuilder sb = new StringBuilder("\n[Bridge]\n");
        switch(songTitle) {
            case ("Turn! Turn! Turn!"): // Written by Pete Seeger in the late 1950s; see also Ecclesiastes 3:1-8.
                sb.append("\nTo Everything (Turn, Turn, Turn)");
                sb.append("\nThere is a season (Turn, Turn, Turn)");
                sb.append("\nAnd a time to every purpose, under Heaven");
                break;
            case ("One"): // By Harry Nilsson; made famous by Three Dog Night.
                for (int i=3; i-- > 0; sb.append("\nOne is the loneliest number"));
                sb.append(" that you'll ever do");
                break;
            case ("Who Knows One?"): // Also known as “Ehad Mi Yode’a”; author unknown.
            default:
                throw new UnsupportedOperationException("Sorry, I don't know the lyrics for " + songTitle + ".");
        }
        return sb.toString();
    }
}

class Song {
    private final String title;
    public Song(String title)          { this.title = title; }
    public final String title()        { return this.title; }
    public final String refrain()      { return Songs.getRefrain(title); }
    public final List<String> verses() { return Songs.getVerses(this.title); }
    public final Num verseCount()      { return Songs.getVerseCount(this.title); }
    public final Num avgVerseLength()  { return Songs.getAvgVerseLength(this.title); }
    public final Num refrainLength()   { return new BigNum(this.refrain().length()); }
    public String toString()           { return title + "\n" + lyrics(); }
    public final String lyrics() {
        String r = refrain(); StringBuilder sb = new StringBuilder(r);
        for (String verse : Songs.getVerses(title)) sb.append("\n\n" + verse + "\n\n" + r);
        return sb.toString();
    }
    /** @see Donald E. Knuth, &ldquo;The Complexity of Songs,&rdquo; <i>Communications of the ACM</i> <b>27</b>
     *       (April 1984) 344-346; errata (June 1984), 593.
     */
    public final Num sizeComplexity() {
        final Num m = this.verseCount(), V = this.avgVerseLength(), R = this.refrainLength();
        return R.add(V.multiply(m));
    }
}