Pages

Them Apples

import java.lang.Comparable;
import java.util.Comparator;
import java.util.Collection;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map;
import java.util.TreeMap;

public class ThemApples
{
    final static Collection<PackingStats> results = new ArrayList<PackingStats>();

    public static void compete(String[] args) {
        final int numCartons = args.length > 0 ? Integer.parseInt(args[0]) : 5;
        final double low = args.length > 1 ? Double.parseDouble(args[1]) : Math.random();
        final double high = args.length > 2 ? Double.parseDouble(args[2]) : Math.random();
        final PackingContest contest = new PackingContest(numCartons, low, high);
        int rounds = args.length > 3 ? Integer.parseInt(args[3]) : 1;
        while (rounds-- > 0) {
            for (ApplePackerFranchise packer : Knuth.shuffle(Northwest.applePackers)) {
                packer.initialize();
                ThemApples.results.add(contest.testPacker(packer));
            }
        }
    }
    public static void main(String[] args) {
        final String[] highMixLowVol = {"10", "0.33", "0.66", "6"};
        final String[] highMixHighVol = {"100", "0.33", "0.66", "6"};
        final String[] lowMixHighVol1 = {"100", "0.0", "1.0", "4"};
        final String[] lowMixHighVol2 = {"100", "0.0", "0.0", "4"};
        final String[] lowMixHighVol3 = {"100", "0.0", "1.0", "4"};

        if (args.length > 0) compete(args);
        else { compete(highMixLowVol);
               compete(highMixHighVol);
               compete(lowMixHighVol1);
               compete(lowMixHighVol2);
               compete(lowMixHighVol3);
        }
        OrderedStats[] rankings = Judge.evaluate(ThemApples.results);
        Announcer.printOverallRanking(Judge.combine(rankings));
        Announcer.printRankings(rankings, 10);
        Announcer.printResults(ThemApples.results);
    }
}

/**************/
/* Interfaces */
/**************/

interface Stuff
{
    public abstract double volume();
    public abstract double weight();
}
interface UnitPriced extends Stuff
{
    public abstract double unitPrice();
}
interface Ageable
{
    public abstract long age();
    public abstract long ageAsOf(long time);
}
interface Valuable
{
    public abstract double value();
}
interface Fillable<T extends Stuff>
{
    public boolean add(T stuff);
    public boolean remove(T stuff);
    public double remainingSpace();
    public boolean isFull();
}
interface Closeable
{
    public abstract boolean close();
    public abstract boolean isOpen();
    public abstract boolean isClosed();
}
interface AppleCustomer
{
    public abstract boolean receive(Collection<BagOfApples> bags);
}
interface ApplePackerFranchise extends AppleCustomer
{
    public abstract String parentCompany();
    public abstract String businessName();
    public abstract void initialize();
    public abstract Collection<AppleCarton> pack(Collection<AppleCarton> cartons);
}

/***************************************/
/* Functions and Functional Interfaces */
/***************************************/

interface Test {
    public abstract boolean pass(int result);
}
interface FoodAttr<V extends Comparable<V>> {
    public abstract V get(Foodstuff item); 
}
interface FoodAttrD {
    public abstract Double get(Foodstuff item); 
}

class Lamb { // Lambda Functions
    public static final Test lt = (compareToResult) -> compareToResult < 0;
    public static final Test gt = (compareToResult) -> compareToResult > 0;
    public static final FoodAttr<Long>   age = (item) -> item.age();
    public static final FoodAttr<Double> wgt = (item) -> item.weight();
    public static final FoodAttr<Double> vol = (item) -> item.volume();
    public static final FoodAttr<Double> val = (item) -> item.value();
    public static final FoodAttrD ageD = (item) -> ((Long) item.age()).doubleValue();
    public static final FoodAttrD wgtD = (item) -> item.weight();
    public static final FoodAttrD volD = (item) -> item.volume();
    public static final FoodAttrD valD = (item) -> item.value();
}

class Knuth {
    public static <T> T[] shuffle(T[] array) {
        final int n = array.length, k = n - 1;
        for (int i = 0; i < k; i++) {
            final Double d = Math.random() * (n - i);
            final int j = i + d.intValue();
            final T tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
        return array;
    }
}

class Doubles {
    public static int compare(Double lhs, Double rhs, int nanVal) {
        Boolean lhsIsNaN = lhs.isNaN(), rhsIsNaN = rhs.isNaN();
        if (lhsIsNaN && rhsIsNaN) return 0;
        else if (lhsIsNaN) return nanVal;
        else if (rhsIsNaN) return -1 * nanVal;
        else return lhs.compareTo(rhs);
    }  
}

class Get // Utility Functions
{
    public static <F extends Foodstuff, V extends Comparable<V>>
    double sum(Collection<F> items, FoodAttrD attribute)
    {
        double total = 0;
        for (F item : items) total += attribute.get(item);
        return total;
    }
    public static <F extends Foodstuff, V extends Comparable<V>>
    double avg(Collection<F> items, FoodAttrD attribute)
    {
        return Get.sum(items, attribute) / items.size();
    }
    public static <F extends Foodstuff, V extends Comparable<V>>
    F most(Collection<F> items, FoodAttr<V> attribute, Test test)
    {
        F selectedItem = null;
        V selectedValue = null;
        for (F item : items)
        {
            V value = attribute.get(item);
            if (selectedValue == null || test.pass(value.compareTo(selectedValue)))
            {
                selectedValue = value;
                selectedItem = item;
            }
        }
        return selectedItem;
    }
    public static <F extends Foodstuff, V extends Comparable<V>>
    Collection<F> all(Collection<F> items, FoodAttrD attribute, Test test, Double val)
    {
        Collection<F> selectedEdibleItems = new ArrayList<F>();
        for (F item : items)
            if (test.pass(attribute.get(item).compareTo(val)))
                selectedEdibleItems.add(item);
        return selectedEdibleItems;
    }
    public static <F extends Foodstuff, V extends Comparable<V>>
    F one(Collection<F> items, FoodAttrD attribute, Test test, Double val)
    {
        Collection<F> selectedEdibleItems = new ArrayList<F>();
        for (F item : items)
            if (test.pass(attribute.get(item).compareTo(val)))
                return item;
        return null;
    }
}

/********************/
/* Food For Thought */
/********************/

abstract class Foodstuff implements Ageable, Valuable, UnitPriced
{
    private final long createTime = System.nanoTime();
    private final double weight, volume;

    protected Foodstuff(double weight, double volume)
    {
        this.weight = weight;
        this.volume = volume;
    }
    public long ageAsOf(long time) { return time - this.createTime; }
    public long age()              { return ageAsOf(System.nanoTime()); }
    public double weight()         { return this.weight; }
    public double volume()         { return this.volume; }
    public double value()          { return this.weight * this.unitPrice(); }
    
    public abstract double unitPrice();
}
class BagOfApples extends Foodstuff
{
    private final Variety variety;
    private final Grower grower;
    private final int lot;
    
    public BagOfApples(Grower grower, Variety variety, double wgt, int lot)
    {
        super(wgt, wgt / AppleAttributes.bulkDensity(variety));
        this.variety = variety;
        this.grower = grower;
        this.lot = lot;
    }
    public double unitPrice() { return Market.price(variety); }
    public Variety variety()  { return this.variety; }
    public Grower grower()    { return this.grower; }
    public int lot()          { return this.lot; }
    public String toString()
    {
        return "grower: " + this.grower().toString() +
            "\nvariety: " + this.variety().toString() +
            "\nlot: " + this.lot() +
            "\nweight: " + this.weight() +
            "\nvolume: " + this.volume() +
            "\nvalue: " + this.value() +
            "\nage: " + this.age() + "\n";
    }
}

/********************/
/* A Lot To Chew On */
/********************/

class EdibleItems<F extends Foodstuff>
{
    private final Collection<F> items = new ArrayList<F>();
    
    public int count()            { return items.size(); }
    public boolean add(F item)    { return items.add(item); }
    public boolean remove(F item) { return items.remove(item); }
    public double sumAge()        { return Get.sum(items, Lamb.ageD); }
    public double sumWgt()        { return Get.sum(items, Lamb.wgtD); }
    public double sumVol()        { return Get.sum(items, Lamb.volD); }
    public double sumVal()        { return Get.sum(items, Lamb.valD); }
    public double avgAge()        { return Get.avg(items, Lamb.ageD); }
    public double avgWgt()        { return Get.avg(items, Lamb.wgtD); }
    public double avgVol()        { return Get.avg(items, Lamb.volD); }
    public double avgVal()        { return Get.avg(items, Lamb.valD); }
    public F oneMinAge()          { return Get.most(items, Lamb.age, Lamb.lt); }
    public F oneMaxAge()          { return Get.most(items, Lamb.age, Lamb.gt); }
    public F oneMinWgt()          { return Get.most(items, Lamb.wgt, Lamb.lt); }
    public F oneMaxWgt()          { return Get.most(items, Lamb.wgt, Lamb.gt); }
    public F oneMinVol()          { return Get.most(items, Lamb.vol, Lamb.lt); }
    public F oneMaxVol()          { return Get.most(items, Lamb.vol, Lamb.gt); }
    public F oneMinVal()          { return Get.most(items, Lamb.val, Lamb.lt); }
    public F oneMaxVal()          { return Get.most(items, Lamb.val, Lamb.gt); }
    public F oneAgeLt(Double d)   { return Get.one(items, Lamb.ageD, Lamb.lt, d); }
    public F oneAgeGt(Double d)   { return Get.one(items, Lamb.ageD, Lamb.gt, d); }
    public F oneAgtLt(Double d)   { return Get.one(items, Lamb.wgtD, Lamb.lt, d); }
    public F oneWgtGt(Double d)   { return Get.one(items, Lamb.wgtD, Lamb.gt, d); }
    public F oneVolLt(Double d)   { return Get.one(items, Lamb.volD, Lamb.lt, d); }
    public F oneVolGt(Double d)   { return Get.one(items, Lamb.volD, Lamb.gt, d); }
    public F oneValLt(Double d)   { return Get.one(items, Lamb.valD, Lamb.lt, d); }
    public F oneValGt(Double d)   { return Get.one(items, Lamb.valD, Lamb.gt, d); }
    public Collection<F> allAgeLt(Double d) { return Get.all(items, Lamb.ageD, Lamb.lt, d); }
    public Collection<F> allAgeGt(Double d) { return Get.all(items, Lamb.ageD, Lamb.gt, d); }
    public Collection<F> allWgtLt(Double d) { return Get.all(items, Lamb.wgtD, Lamb.lt, d); }
    public Collection<F> allWgtGt(Double d) { return Get.all(items, Lamb.wgtD, Lamb.gt, d); }
    public Collection<F> allVolLt(Double d) { return Get.all(items, Lamb.volD, Lamb.lt, d); }
    public Collection<F> allVolGt(Double d) { return Get.all(items, Lamb.volD, Lamb.gt, d); }
    public Collection<F> allValLt(Double d) { return Get.all(items, Lamb.valD, Lamb.lt, d); }
    public Collection<F> allValGt(Double d) { return Get.all(items, Lamb.valD, Lamb.gt, d); }
    public Collection<F> all() { return Get.all(items, Lamb.ageD, Lamb.gt, 0.0); }
}

/****************************/
/* Contained Pieces of Food */
/****************************/

class ContainedEdibleItems<F extends Foodstuff>
extends EdibleItems<F> implements Fillable<F>
{
    private final double size;
    private double spaceRemaining;
    
    public ContainedEdibleItems(double spaceLimit)
    {
        this.size = this.spaceRemaining = spaceLimit;
    }
    public boolean add(F item)
    {
        double itemVolume = item.volume();
        if (this.spaceRemaining > itemVolume)
        {
            if (super.add(item))
            {
                this.spaceRemaining -= itemVolume;
                return true;
            }
        }
        return false;
    }
    public boolean remove(F item)
    {
        if (super.remove(item))
        {
            this.spaceRemaining += item.volume();
            return true;
        }
        return false;
    }
    public double size() { return this.size; }
    public double remainingSpace() { return this.spaceRemaining; }
    public boolean isFull() { return this.spaceRemaining <= 0; }
}
class AppleInventory extends ContainedEdibleItems<BagOfApples>
{
    public AppleInventory(double storageSpace) { super(storageSpace); }
    public void print() { for (BagOfApples bag : this.all()) System.out.println(bag); }
}

/****************/
/* Food Cartons */
/****************/

class FoodCarton<F extends Foodstuff>
extends ContainedEdibleItems<F> implements Closeable
{
    private double length, width, height;
    private boolean opened = true;
    
    public FoodCarton(double l, double w, double h)
    {
        super(l * w * h);
        this.length = l;
        this.width = w;
        this.height = h;
    }
    public boolean close()        { return !(this.opened = false); }
    public boolean isOpen()       { return this.opened; }
    public boolean isClosed()     { return !this.opened; }
    public boolean add(F item)    { return this.opened ? super.add(item) : false; }
    public boolean remove(F item) { return this.opened ? super.remove(item) : false; }
    public final double length()  { return this.length; }
    public final double width()   { return this.width; }
    public final double height()  { return this.height; }
}
class AppleCarton extends FoodCarton<BagOfApples>
{
    public AppleCarton(int l, int w, int h) { super(l, w, h); }
    public void printContents()
    {
        for (BagOfApples bag : this.all()) System.out.println(bag);
    }
    public String toString()
    {
        if (this.count() == 0) return "Empty carton.";
        return "\nOldest Bag: " + this.oneMaxAge().age() +
               "\n Total Wgt: " + this.sumWgt() +
               "\n Total Val: " + this.sumVal() +
               "\n Total Age: " + this.sumAge() + "\n";
    }
}
class SmallFootprintAppleCarton extends AppleCarton {
    public SmallFootprintAppleCarton() { super(4, 2, 3); }
}
class MediumFootprintAppleCarton extends AppleCarton {
    public MediumFootprintAppleCarton() { super(3, 3, 3); }
}
class LargeFootprintAppleCarton extends AppleCarton {
    public LargeFootprintAppleCarton() { super(4, 3, 3); }
}

/*****************/
/* Apple Science */
/*****************/

enum Variety
{
    GALA("Gala"),
    RED_DELICIOUS("Red Delicious"),
    HONEY_CRISP("Honey Crisp");
    
    private String name;
    Variety(String name) { this.name = name; }
    public String toString()  { return this.name; }
}
class AppleAttributes
{
    private static final double PACKING_EFFICIENCY = 0.64;
    private static final double density(Variety variety)
    {
        switch(variety)
        {
            case GALA: return 0.92;
            case RED_DELICIOUS: return 0.94;
            case HONEY_CRISP: return 0.95;
            default: return 0.93;
        }
    }
    public static double bulkDensity(Variety variety)
    {
        return density(variety) * PACKING_EFFICIENCY;
    }
}

/*******************/
/* Apple Economics */
/*******************/

class Market
{
    public static double price(Variety variety)
    {
        switch(variety)
        {
            case GALA: return 1.2;
            case RED_DELICIOUS: return 0.9;
            case HONEY_CRISP: return 1.3;
            default: return 0.8;
        }
    }
}

/*****************/
/* Apple Growers */
/*****************/

class GrowersAssociation
{
    private static int[][] lotIds =
        new int[Grower.values().length][Variety.values().length];
    
    private static Collection<LotOfApples> lots = new ArrayList<LotOfApples>();
    private static LotOfApples currentLot;
    
    public static int nextLot(Grower grower, Variety variety)
    {
        int lotId = ++lotIds[grower.ordinal()][variety.ordinal()];
        currentLot = new LotOfApples(grower, variety, lotId);
        return lotId;
    }
    public static boolean register(BagOfApples bag)
    {
        return currentLot.id() == bag.lot() &&
               currentLot.grower() == bag.grower() &&
               currentLot.variety() == bag.variety() ? currentLot.add(bag) : false;
    }
}
class LotOfApples
{
    private final int lotId;
    private final Grower grower;
    private final Variety variety; 
    private final Collection<BagOfApples> bags = new ArrayList<BagOfApples>();
    
    public LotOfApples(Grower grower, Variety variety, int id)
    {
        this.grower = grower;
        this.variety = variety;
        this.lotId = id;
    }
    public boolean add(BagOfApples bag)      { return this.bags.add(bag); }
    public boolean contains(BagOfApples bag) { return this.bags.contains(bag); }
    public Grower grower()                   { return this.grower; }
    public Variety variety()                 { return this.variety; }
    public int id()                          { return this.lotId; }
}

enum Grower
{
    APPLE_BEES ("Apple Bees", 4.0, 8.0,
        Variety.HONEY_CRISP, Variety.GALA, 500L, 10000.0),
    RANDOM_REDS ("Random Reds", Math.E, Math.PI,
        Variety.RED_DELICIOUS, Variety.RED_DELICIOUS, 100L, 20000.0),
    FRIENDLY_FRUITS ("Friendly Fruits", 2.0, 6.0,
        Variety.GALA, Variety.RED_DELICIOUS, 300L, 15000.0);

    private String name;
    private long leadTime;
    private double minBagWgt, bagWgtRange, wgtLimit;
    private Variety var1, var2;
    
    Grower(String name, Double min, Double max,
        Variety primary, Variety secondary, long leadTime, double wgtLimit)
    {
        this.name = name;
        this.minBagWgt = min;
        this.bagWgtRange = Math.abs(max - min);
        this.var1 = primary;
        this.var2 = secondary;
        this.leadTime = leadTime;
        this.wgtLimit = wgtLimit;
    }
    private Variety whichVariety() { return Math.random() < 0.7 ? var1 : var2; }
    public boolean fillOrder(AppleCustomer customer, double requestedWgt)
    {
        final Long whenRequested = System.nanoTime();
        final Grower grower = this;
        final Variety variety = this.whichVariety();
        final int lotId = GrowersAssociation.nextLot(this, variety);
        final Collection<BagOfApples> themApples = new ArrayList<BagOfApples>();
        double totalWgt = 0.0;
        while (totalWgt < requestedWgt && totalWgt < wgtLimit)
        {
            Double bagWgt = this.minBagWgt + (Math.random() * this.bagWgtRange);
            BagOfApples bag = new BagOfApples(grower, variety, bagWgt, lotId);
            if (!GrowersAssociation.register(bag)) return false;
            if (!themApples.add(bag)) return false;
            totalWgt += bagWgt;
        }
        while (System.nanoTime() - whenRequested < this.leadTime);
        return customer.receive(themApples); // How do you like themApples?
    }
    public double wgtLimit() { return this.wgtLimit; }
    public String toString() { return this.name; }
}

/*******************/
/* Packing Contest */
/*******************/

class PackingContest
{
    private int numCartons;
    private double low, high;

    public PackingContest(int numCartons, double low, double high)
    {
        this.numCartons = numCartons;
        this.low = low;
        this.high = high;
    }
    public PackingStats testPacker(ApplePackerFranchise contestent)
    {
        return new PackingStats(contestent, getCartons());
    }
    private Collection<AppleCarton> getCartons()
    {
        Collection<AppleCarton> cartons = new ArrayList<AppleCarton>();
        for (int i = 0; i < numCartons; i++)
        {
            AppleCarton carton;            
            double d = Math.random();
            if (d < low) carton = new SmallFootprintAppleCarton();
            else if (d < high) carton = new MediumFootprintAppleCarton();
            else carton = new LargeFootprintAppleCarton();
            cartons.add(carton);
        }
        return cartons;
    }
}

class PackingStats
{
    private final ApplePackerFranchise franchise;
    private final long packTime;
    private int cartonCount, bagCount;
    private double sumWgt, sumAge, sumVal, sumVol, sumSize, maxAge;    
    public PackingStats(PackingStats stats)
    {
        this.franchise = stats.franchise;
        this.packTime = stats.packTime;
        this.cartonCount = stats.cartonCount;
        this.bagCount = stats.bagCount;
        this.sumWgt = stats.sumWgt;
        this.sumAge = stats.sumAge;
        this.sumVal = stats.sumVal;
        this.sumVol = stats.sumVol;
        this.sumSize = stats.sumSize;
        this.maxAge = stats.maxAge;
    }
    public PackingStats(ApplePackerFranchise p, Collection<AppleCarton> cartons)
    {
        this.franchise = p;
        this.cartonCount = cartons.size();
        long beginTime = System.nanoTime();
        p.pack(cartons);
        this.packTime = System.nanoTime() - beginTime;
        for (AppleCarton c : cartons)
        {
            if (c.count() == 0) continue;
            double max = c.oneMaxAge().age();
            if (max > this.maxAge) this.maxAge = max;
            this.sumWgt += c.sumWgt();
            this.sumAge += c.sumAge();
            this.sumVal += c.sumVal();
            this.sumVol += c.sumVol();
            this.sumSize += c.size();
            this.bagCount += c.count();
        }
    }
    public String parentCompany() { return this.franchise.parentCompany(); }
    public String businessName()  { return this.franchise.businessName(); }
    public Double packTimeSec()   { return this.packTime / 1E9; }
    public Double maxAgeSec()     { return this.maxAge / 1E9; }
    public Double avgAgeSec()     { return this.sumAge / 1E9 / this.bagCount; }
    public Double wgtVolRatio()   { return this.sumWgt / this.sumVol; }
    public Double wgtEfficiency() { return this.wgtVolRatio() / this.volEfficiency(); }
    public Double volEfficiency() { return this.sumVol / this.sumSize; }
    public Double valEfficiency() { return this.sumVal / this.sumSize; }
    public Integer cartonCount()  { return this.cartonCount; }
    public Integer bagCount()     { return this.bagCount; }
}

class OrderedStats
{
    private final String description;
    private final Set<PackingStats> rankedStats = new TreeSet<PackingStats>();

    public OrderedStats(String desc) {
        this.description = desc;
    }
    public boolean add(ComparablePackingStats stats) {
        return rankedStats.add(stats);
    }
    public Collection<Map.Entry<String, Integer>> parentCompanyRanking() {
        Map<String, Integer> parentScores = new TreeMap<String, Integer>();
        int place = 0;
        for (PackingStats stats : this.rankedStats){
            place++;
            String parentCompany = stats.parentCompany();
            Integer newScore = parentScores.containsKey(parentCompany) ?
                parentScores.get(parentCompany) + place : place;
            parentScores.put(parentCompany, newScore);
        }
        ArrayList<Map.Entry<String, Integer>> list =
            new ArrayList<Map.Entry<String, Integer>>(); 
        for (Map.Entry<String, Integer> entry : parentScores.entrySet())
            list.add(entry);
        Collections.sort(list, EntryComparator.THE_COMPARATOR);
        return list;
    }
    public String parentCompanyRankingAsString() {
        String s = this.description + "\n\nParent Co. Ranking\n\n";
        int i = 0;
        for (Map.Entry<String, Integer> entry : this.parentCompanyRanking())
            s += ++i + ". " + entry.getKey() + " (" + entry.getValue() + ")\n";
        return s;
    }
    public String franchiseRankingAsString(int topN) {
        String s = "Top " + topN + " Individual Franchise Scores\n\n";
        int i = 0;
        for (PackingStats stats : this.rankedStats) {
            s += (++i + ". " + stats.toString());
            if (topN > 0 && i >= topN) break;
        }
        return s;
    }
}

class EntryComparator implements Comparator<Map.Entry<String, Integer>> {
    public static final EntryComparator THE_COMPARATOR = new EntryComparator();
    protected EntryComparator() {}
    public int compare(Map.Entry<String, Integer> a, Map.Entry<String, Integer> b) {
        return a.getValue().compareTo(b.getValue());
    }
}
abstract class ComparablePackingStats
extends PackingStats implements Comparable<PackingStats> {
    public ComparablePackingStats(PackingStats stats) { super(stats); }
    public String toString() {
        return this.parentCompany() + ": " +
               this.businessName() + " (" + this.keyValue() + ")\n";
    }
    public abstract String keyValue();
}
class StatsComparableByTime extends ComparablePackingStats {
    public StatsComparableByTime(PackingStats stats) { super(stats); }
    public int compareTo(PackingStats other) {
        return this.packTimeSec().compareTo(other.packTimeSec());
    }
    public String keyValue() { return this.packTimeSec().toString(); }
}
class StatsComparableByAge extends ComparablePackingStats {
    public StatsComparableByAge(PackingStats stats) { super(stats); }
    public int compareTo(PackingStats other) {
        return Doubles.compare(this.avgAgeSec(), other.avgAgeSec(), 1);
    }
    public String keyValue() { return this.avgAgeSec().toString(); }
}
class StatsComparableByWgt extends ComparablePackingStats {
    public StatsComparableByWgt(PackingStats stats) { super(stats); }
    public int compareTo(PackingStats other) {
        return Doubles.compare(this.wgtEfficiency(), other.wgtEfficiency(), 1);
    }
    public String keyValue() { return this.wgtEfficiency().toString(); }
}
class StatsComparableByVal extends ComparablePackingStats {
    public StatsComparableByVal(PackingStats stats) { super(stats); }
    public int compareTo(PackingStats other) {
        return -1 * Doubles.compare(this.valEfficiency(), other.valEfficiency(), -1);
    }
    public String keyValue() { return this.valEfficiency().toString(); }
}
class StatsComparableByVol extends ComparablePackingStats {
    public StatsComparableByVol(PackingStats stats) { super(stats); }
    public int compareTo(PackingStats other) {
        return -1 * Doubles.compare(this.volEfficiency(), other.volEfficiency(), -1);
    }
    public String keyValue() { return this.volEfficiency().toString(); }
}

class Judge
{
    private static OrderedStats
        statsSortedByTime = new OrderedStats("PACK TIME COMPETITION"),
        statsSortedByAge = new OrderedStats("AVG AGE COMPETITION"),
        statsSortedByWgt = new OrderedStats("WGT EFFICIENCY COMPETITION"),
        statsSortedByVal = new OrderedStats("VAL EFFICIENCY COMPETITION"),
        statsSortedByVol = new OrderedStats("VOL EFFICIENCY COMPETITION");

    public static OrderedStats[] evaluate(Collection<PackingStats> stats)
    {
        for (PackingStats s : stats) {
            statsSortedByTime.add(new StatsComparableByTime(s));
            statsSortedByAge.add(new StatsComparableByAge(s));
            statsSortedByWgt.add(new StatsComparableByWgt(s));
            statsSortedByVal.add(new StatsComparableByVal(s));
            statsSortedByVol.add(new StatsComparableByVol(s));
        }
        OrderedStats[] results = { statsSortedByTime, statsSortedByAge,
            statsSortedByWgt, statsSortedByVal, statsSortedByVol
        };
        return results;
    }
    public static Collection<Map.Entry<String, Integer>> combine(OrderedStats[] rankings)
    {
        Collection<Map.Entry<String, Integer>>
            overallRanking = null, currentRanking;
        for (OrderedStats ranking : rankings) {
            currentRanking = ranking.parentCompanyRanking();
            if (overallRanking == null) {
                overallRanking = currentRanking;
                continue;
            }
            for (Map.Entry<String, Integer> entry : currentRanking) {
                String key = entry.getKey();
                Integer val = entry.getValue();
                for (Map.Entry<String, Integer> e : overallRanking) {
                    if (e.getKey().equals(key))
                        e.setValue(e.getValue() + val);
                }
            }
        }
        ArrayList<Map.Entry<String, Integer>> list =
            new ArrayList<Map.Entry<String, Integer>>(); 
        for (Map.Entry<String, Integer> entry : overallRanking)
            list.add(entry);
        Collections.sort(list, EntryComparator.THE_COMPARATOR);
        return list;
    }
}

class Announcer
{
    public static void printResults(Collection<PackingStats> results)
    {
        System.out.println("PACKING STATISTICS\n");
        for (PackingStats stats : results)
            System.out.println("  Business Name: " + stats.businessName() + 
                "\n   Carton Count: " + stats.cartonCount() +
                "\n      Bag Count: " + stats.bagCount() +
                "\n      Pack Time: " + stats.packTimeSec() + " sec" +
                "\n Avg Age of Bag: " + stats.avgAgeSec() + " sec" +
                "\n Max Age of Bag: " + stats.maxAgeSec() + " sec" +
                "\n Vol Efficiency: " + stats.volEfficiency() * 100 + " %" +
                "\n Val Efficiency: " + stats.valEfficiency() * 100 + " cents / unit vol" +
                "\n Wgt Efficiency: " + stats.wgtEfficiency() + " wgt units / unit vol\n");
    }
    public static void printRankings(OrderedStats[] rankings, int topN)
    {
        for (OrderedStats ranking : rankings)
        {
            System.out.println(ranking.parentCompanyRankingAsString());
            System.out.println(ranking.franchiseRankingAsString(topN));
        }
    }
    public static void printOverallRanking(Collection<Map.Entry<String, Integer>> entries)
    {
        System.out.println("OVERALL COMPETITION\n");
        int i = 0;
        for (Map.Entry<String, Integer> entry : entries)
            System.out.println(++i + ". " + entry.getKey() + " (" + entry.getValue() + ")");
        System.out.println();
    }
}

/****************************/
/* Authorized Apple Packers */
/****************************/

abstract class ApplePacker implements ApplePackerFranchise
{
    private static double MAX_STORAGE_SPACE = 1000.0;

    private final String parentCompany;
    private final String businessName;
    protected final AppleInventory inventory;
    protected final Collection<Grower> preferredSuppliers;

    public ApplePacker(String parentCompany, String businessName, double storageSpace)
    {
        this.parentCompany = parentCompany;
        this.businessName = businessName;
        if (storageSpace > ApplePacker.MAX_STORAGE_SPACE)
            storageSpace = ApplePacker.MAX_STORAGE_SPACE;
        this.inventory = new AppleInventory(storageSpace);
        this.preferredSuppliers = new ArrayList<Grower>();
    }
    public String parentCompany()
    {
        return this.parentCompany;
    }
    public String businessName()
    {
        return this.businessName;
    }
    public boolean receive(Collection<BagOfApples> bags)
    {
        for (BagOfApples bag : bags)
            if (!this.inventory.add(bag))
                return false;

        return true;
    }
    public abstract void initialize();
    public abstract Collection<AppleCarton> pack(Collection<AppleCarton> cartons);
    protected void restock()
    {
        while (inventory.remainingSpace() > 0)
            for (Grower grower : this.preferredSuppliers)
                if (!grower.fillOrder(this, grower.wgtLimit()) ||
                    inventory.remainingSpace() <= 0)
                    return;
    }
}

/********************/
/* The Model Packer */
/********************/

class ApplePacker0 extends ApplePacker
{
    public ApplePacker0(String parentCo)
    {
        this(parentCo, "Johnny Appleseed");
    }
    public ApplePacker0(String parentCo, String businessName)
    {
        this(parentCo, businessName, 25);
    }
    public ApplePacker0(String parentCo, String businessName, int storageSpace)
    {
        super(parentCo, businessName, storageSpace);
    }
    public void initialize()
    {
        this.preferredSuppliers.add(Grower.RANDOM_REDS);
        this.restock();
    }
    public Collection<AppleCarton> pack(Collection<AppleCarton> cartons)
    {
        for (AppleCarton carton : cartons)
        {
            BagOfApples bag = inventory.oneVolLt(carton.remainingSpace());
            while (bag != null)
            {
                if (!inventory.remove(bag)) return null;
                if (!carton.add(bag)) return null;
                bag = inventory.oneVolLt(carton.remainingSpace());
            }
            if (!carton.close()) return null;
        }
        return cartons;
    }
}

/**************************/
/* Regional Apple Packers */
/**************************/

class Northwest
{
    public static ApplePackerFranchise[] applePackers =
    {
        new ApplePacker0("Big Business", "Johnny In-The-Box, LLC", 500),
        new ApplePacker0("Big Business", "Johnny In-The-Box, LLC", 500),
        new ApplePacker0("Big Business", "Johnny In-The-Box, LLC", 500),
        new ApplePacker0("Big Business", "Johnny In-The-Box, LLC", 500),
        new ApplePacker0("Big Business", "Johnny In-The-Box, LLC", 500),
        new ApplePacker0("Small Business", "The Original Johnny Appleseed"),
        new ApplePacker0("Small Business", "Johnny Appleseed & Son Pack-4-U", 50),
        new ApplePacker0("Small Business", "Johnny Appleseed & Son Pack-4-U", 50),
        new ApplePacker0("Small Business", "Johnny Appleseed & Son Pack-4-U", 50),
        new ApplePacker0("Small Business", "Johnny Appleseed & Son Pack-4-U", 50),
        new ApplePacker99(1),
        new ApplePacker99(2),
        new ApplePacker99(3),
        new ApplePacker99(4),
        new ApplePacker99(5)
    };
}

/*****************/
/* ApplePacker99 */
/*****************/

class ApplePacker99 implements ApplePackerFranchise
{
    private final String parentCo = "ApplePacker99";
    private ApplePacker packer;
    public ApplePacker99(int version) {
        packer = new ApplePacker99a(this.parentCo, 300);
    }
    public String parentCompany() { return packer.parentCompany(); }
    public String businessName()  { return packer.businessName(); }
    public void initialize()      { packer.initialize(); }
    public boolean receive(Collection<BagOfApples> bags) { return packer.receive(bags); }
    public Collection<AppleCarton> pack(Collection<AppleCarton> cartons) {
        return packer.pack(cartons);
    }
    
    private class ApplePacker99a extends ApplePacker
    {
        public ApplePacker99a(String parentCo, int inventorySize) {
            super(parentCo, "ApplePacker99a", inventorySize);
            this.preferredSuppliers.add(Grower.RANDOM_REDS);
        }
        public void initialize() { /* do nothing */ }
        public Collection<AppleCarton> pack(Collection<AppleCarton> cartons) {
            for (AppleCarton carton : cartons)
            {
                if (this.inventory.count() < 10) this.restock();
                BagOfApples bag = inventory.oneVolLt(carton.remainingSpace());
                while (bag != null)
                {
                    if (!inventory.remove(bag)) return null;
                    if (!carton.add(bag)) return null;
                    bag = inventory.oneVolLt(carton.remainingSpace());
                }
                if (!carton.close()) return null;
            }
            return cartons;
        }
    }
}