// Cut a list off at a certain point in order to stay within a
// defined "budget" (according to a user-defined function giving
// a price for each element)
sclass CutListToBudget {
settable double maxPrice;
settable Iterable inputList;
gettable L outputList;
gettable double finalPrice;
// cut up last element to fill remaining budget?
settable bool allowPartial;
// last element of outputList before reduction
// (if there was a reduction)
gettable A fullLastElement;
swappable double getPrice(A element) { throw unimplemented(); }
// reduce an element to fit a budget
// can return null if no reduced element is possible
swappable A reduceElement(A element, double budget) { null; }
*(IF1 *getPrice) {}
*(IF1 *getPrice, double *maxPrice, Iterable *inputList) {}
run {
outputList = new L;
finalPrice = 0;
fOr (element : inputList) {
double price = getPrice(element);
if (finalPrice + price > maxPrice) {
if (allowPartial) {
A partial = reduceElement(element, maxPrice-finalPrice);
if (partial != null) {
fullLastElement = element;
finalPrice += getPrice(partial);
outputList.add(partial);
if (finalPrice > maxPrice)
fail("reduceElement failure (over budget)");
}
}
break;
}
finalPrice += price;
outputList.add(element);
}
}
L get() {
if (outputList == null) run();
ret outputList;
}
selfType allowPartial(IF2 reduceElement) {
this.reduceElement = reduceElement;
ret allowPartial(true);
}
A lastElement() {
ret last(get());
}
Percent lastElementKeptPercentage() {
ret fullLastElement == null ?: Percent(getPrice(lastElement())/getPrice(fullLastElement)*100);
}
}