Decorator: Coffee with Extras
Problem
Each input line starts with a base drink (espresso costs 300, tea costs 200) followed by zero or more extras (milk 50, sugar 10, whip 70). The same extra can appear more than once. Print the names joined with + and the total price, for example espresso+milk 350. Build the price with the Decorator pattern: each extra wraps the drink before it.
Constraints
- 1 <= lines <= 10^4
- 0 <= extras <= 20
Examples
Input
espresso milk sugar
Output
espresso+milk+sugar 360
Solutions
A decorator has the same interface as the thing it wraps, so extras can be stacked in any order and any number of times without new subclasses for each combination.
/*
* Problem : Process lines.
* Input : Several lines.
* Output : Description and price.
* Approach: Wrap drinks.
* Time : O(total words)
* Space : O(n)
* Java : 8 or newer.
*/
import java.util.Scanner;
public class Main {
// Step 1: A Drink has a description and a price. Every extra wraps another Drink.
interface Drink {
String description();
long price();
}
static class Plain implements Drink {
private final String name;
private final long cost;
Plain(String name, long cost) {
this.name = name;
this.cost = cost;
}
public String description() {
return name;
}
public long price() {
return cost;
}
}
// Step 2: A decorator holds a drink inside. It adds its own name and price to the inner drink.
static class Extra implements Drink {
private final Drink inner;
private final String name;
private final long cost;
Extra(Drink inner, String name, long cost) {
this.inner = inner;
this.name = name;
this.cost = cost;
}
public String description() {
return inner.description() + "+" + name;
}
public long price() {
return inner.price() + cost;
}
}
// This method solves the task. It receives the input and returns the answer.
static String[] solve(String[] lines) {
java.util.Map<String, Long> extras = new java.util.HashMap<>();
extras.put("milk", 50L);
extras.put("sugar", 10L);
extras.put("whip", 70L);
String[] output = new String[lines.length];
for (int i = 0; i < lines.length; i++) {
String[] parts = lines[i].split(" ");
// Step 3: Start with the base drink.
Drink drink = new Plain(parts[0], parts[0].equals("espresso") ? 300 : 200);
// Step 4: Wrap the drink once for each extra. The last wrapper is the outer one.
for (int j = 1; j < parts.length; j++) {
drink = new Extra(drink, parts[j], extras.get(parts[j]));
}
output[i] = drink.description() + " " + drink.price();
}
return output;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
java.util.List<String> all = new java.util.ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (!line.isEmpty()) {
all.add(line);
}
}
String[] lines = all.toArray(new String[0]);
System.out.println(String.join("\n", solve(lines)));
}
}
Hints (1)
- Why is a decorator better than one subclass for each combination?