37#include "absl/container/flat_hash_set.h"
38#include "absl/status/status.h"
39#include "absl/status/statusor.h"
40#include "absl/strings/str_join.h"
41#include "absl/strings/str_replace.h"
42#include "absl/strings/string_view.h"
51 "One of \"text\", \"latex\", or \"analysis\".");
53 "How many ingredients to buy (ignored in analysis mode).");
54ABSL_FLAG(std::vector<std::string>, existing_ingredients, {},
55 "Ingredients you already have (ignored in analysis mode).");
56ABSL_FLAG(std::vector<std::string>, unavailable_ingredients, {},
57 "Ingredients you cannot get (ignored in analysis mode).");
58ABSL_FLAG(std::vector<std::string>, required_cocktails, {},
59 "Cocktails you must be able to make (ignored in analysis mode).");
60ABSL_FLAG(std::vector<std::string>, blocked_cocktails, {},
61 "Cocktails to exclude from the menu (ignored in analysis mode).");
65namespace math_opt = ::operations_research::math_opt;
67constexpr absl::string_view kIngredients[] = {
"Amaro Nonino",
94 "Orange Flower Water Extract",
111constexpr std::size_t kIngredientsSize =
112 sizeof(kIngredients) /
sizeof(kIngredients[0]);
116 std::vector<std::string> ingredients;
119std::vector<Cocktail> AllCocktails() {
122 {.name =
"Prosecco glass", .ingredients = {
"Prosecco"}},
123 {.name =
"Aperol Spritz", .ingredients = {
"Prosecco",
"Aperol"}},
124 {.name =
"Chambord Spritz", .ingredients = {
"Prosecco",
"Chambord"}},
125 {.name =
"Improved French 75",
126 .ingredients = {
"Prosecco",
"Vodka",
"Lemon",
"Simple Syrup"}},
128 {.name =
"Gin and Tonic", .ingredients = {
"Gin",
"Tonic Water",
"Lime"}},
129 {.name =
"Rum and Coke", .ingredients = {
"Rum",
"Coke"}},
130 {.name =
"Improved Manhattan",
131 .ingredients = {
"Bourbon",
"Sweet Vermouth",
"Bitters"}},
135 {.name =
"Lemon Drop",
136 .ingredients = {
"Vodka",
"Cointreau",
"Lemon",
"Simple Syrup"}},
138 {.name =
"Big Crush",
139 .ingredients = {
"Raspberry Vodka",
"Cointreau",
"Lemon",
"Chambord",
141 {.name =
"Cosmopolitan",
142 .ingredients = {
"Vodka",
"Cranberry",
"Cointreau",
"Lime"}},
144 {.name =
"Vodka/Pickle", .ingredients = {
"Vodka",
"Pickle"}},
147 {.name =
"Last Word",
148 .ingredients = {
"Gin",
"Green Chartreuse",
"Luxardo",
"Lime"}},
149 {.name =
"Corpse Reviver #2 (Lite)",
150 .ingredients = {
"Gin",
"Cointreau",
"Lillet Blanc",
"Lemon"}},
151 {.name =
"Negroni", .ingredients = {
"Gin",
"Sweet Vermouth",
"Campari"}},
154 .ingredients = {
"Gin",
"Luxardo",
"Lemon",
"Creme de Violette"}},
157 {.name =
"Paper Plane",
158 .ingredients = {
"Bourbon",
"Aperol",
"Amaro Nonino",
"Lemon"}},
160 .ingredients = {
"Bourbon",
"Sweet Vermouth",
"Lime",
"Cointreau"}},
163 {.name =
"Old Fashioned",
164 .ingredients = {
"Bourbon",
"Sugar",
"Bitters",
"Orange",
"Cherry"}},
165 {.name =
"Boulevardier",
166 .ingredients = {
"Bourbon",
"Sweet Vermouth",
"Campari"}},
169 {.name =
"Margarita", .ingredients = {
"Tequila",
"Cointreau",
"Lime"}},
171 {.name =
"Midnight Cruiser",
172 .ingredients = {
"Tequila",
"Aperol",
"Lime",
"Pineapple Juice",
173 "Cucumber",
"Simple Syrup"}},
175 {.name =
"Tequila shot", .ingredients = {
"Tequila"}},
179 {.name =
"Pineapple Mai Tai",
180 .ingredients = {
"Rum",
"Lime",
"Orgeat",
"Cointreau",
182 {.name =
"Daiquiri", .ingredients = {
"Rum",
"Lime",
"Simple Syrup"}},
184 .ingredients = {
"Rum",
"Lime",
"Simple Syrup",
"Mint",
"Seltzer"}},
188 .ingredients = {
"Rum",
"All Spice Dram",
"Bitters",
"Lime",
189 "Simple Syrup",
"Cloves",
"Green Chartreuse"}},
193 {.name =
"Pisco Sour",
194 .ingredients = {
"Pisco",
"Lime",
"Simple Syrup",
"Egg",
"Bitters"}},
196 .ingredients = {
"Ruby Port",
"Brandy",
"Creme de Cacao",
"Sugar",
"Egg",
200 {.name =
"Ramos gin fizz",
201 .ingredients = {
"Gin",
"Seltzer",
"Heavy Cream",
202 "Orange Flower Water Extract",
"Egg",
"Lemon",
"Lime",
207 std::vector<std::string> ingredients;
208 std::vector<Cocktail> cocktails;
211absl::StatusOr<Menu> SolveForMenu(
212 const int max_new_ingredients,
const bool enable_solver_output,
213 const absl::flat_hash_set<std::string>& existing_ingredients,
214 const absl::flat_hash_set<std::string>& unavailable_ingredients,
215 const absl::flat_hash_set<std::string>& required_cocktails,
216 const absl::flat_hash_set<std::string>& blocked_cocktails) {
217 const std::vector<Cocktail> all_cocktails = AllCocktails();
219 absl::flat_hash_map<std::string, math_opt::Variable> ingredient_vars;
220 for (
const absl::string_view ingredient : kIngredients) {
221 const double lb = existing_ingredients.contains(ingredient) ? 1.0 : 0.0;
222 const double ub = unavailable_ingredients.contains(ingredient) ? 0.0 : 1.0;
227 for (
const auto& [
name, ingredient_var] : ingredient_vars) {
228 ingredients_used += ingredient_var;
230 model.AddLinearConstraint(ingredients_used <=
231 max_new_ingredients + existing_ingredients.size());
233 absl::flat_hash_map<std::string, math_opt::Variable> cocktail_vars;
234 for (
const Cocktail& cocktail : all_cocktails) {
235 const double lb = required_cocktails.contains(cocktail.name) ? 1.0 : 0.0;
236 const double ub = blocked_cocktails.contains(cocktail.name) ? 0.0 : 1.0;
238 model.AddIntegerVariable(lb, ub, cocktail.name);
239 for (
const std::string& ingredient : cocktail.ingredients) {
240 model.AddLinearConstraint(v <=
246 for (
const auto& [
name, cocktail_var] : cocktail_vars) {
247 cocktails_made += cocktail_var;
249 model.Maximize(cocktails_made);
256 case math_opt::TerminationReason::kOptimal:
257 case math_opt::TerminationReason::kFeasible:
261 <<
"failed to find a solution: " << result.
termination;
264 for (
const absl::string_view ingredient : kIngredients) {
265 if (result.
variable_values().at(ingredient_vars.at(ingredient)) > 0.5) {
266 menu.ingredients.push_back(std::string(ingredient));
269 for (
const Cocktail& cocktail : all_cocktails) {
270 if (result.
variable_values().at(cocktail_vars.at(cocktail.name)) > 0.5) {
271 menu.cocktails.push_back(cocktail);
277absl::flat_hash_set<std::string> SetFromVec(
278 const std::vector<std::string>& vec) {
279 return {vec.begin(), vec.end()};
282absl::Status AnalysisMode() {
283 std::cout <<
"Considering " << AllCocktails().size() <<
" cocktails and "
284 << kIngredientsSize <<
" ingredients." << std::endl;
285 std::cout <<
"Solving for number of cocktails that can be made as a function "
286 "of number of ingredients"
289 std::cout <<
"ingredients | cocktails" << std::endl;
290 for (
int i = 1; i <= kIngredientsSize; ++i) {
291 const absl::StatusOr<Menu> menu = SolveForMenu(
295 <<
"Failure when solving for " << i <<
" ingredients";
296 std::cout << i <<
" | " << menu->cocktails.size() << std::endl;
298 return absl::OkStatus();
301std::string ExportToLaTeX(
const std::vector<Cocktail>& cocktails,
302 const std::string& title =
"Cocktail Hour") {
303 std::vector<std::string> lines;
304 lines.push_back(
"\\documentclass{article}");
305 lines.push_back(
"\\usepackage{fullpage}");
306 lines.push_back(
"\\linespread{2}");
307 lines.push_back(
"\\begin{document}");
308 lines.push_back(
"\\begin{center}");
309 lines.push_back(absl::StrCat(
"\\begin{Huge}", title,
"\\end{Huge}"));
311 for (
const Cocktail& cocktail : cocktails) {
312 lines.push_back(absl::StrCat(cocktail.name,
"---{\\em ",
313 absl::StrJoin(cocktail.ingredients,
", "),
317 lines.push_back(
"\\end{center}");
318 lines.push_back(
"\\end{document}");
320 return absl::StrReplaceAll(absl::StrJoin(lines,
"\n"), {{
"#",
"\\#"}});
324 const std::string mode = absl::GetFlag(FLAGS_mode);
325 CHECK(absl::flat_hash_set<std::string>({
"text",
"latex",
"analysis"})
327 <<
"Unexpected mode: " << mode;
330 if (mode ==
"analysis") {
331 return AnalysisMode();
336 SolveForMenu(absl::GetFlag(FLAGS_num_ingredients), mode ==
"text",
337 SetFromVec(absl::GetFlag(FLAGS_existing_ingredients)),
338 SetFromVec(absl::GetFlag(FLAGS_unavailable_ingredients)),
339 SetFromVec(absl::GetFlag(FLAGS_required_cocktails)),
340 SetFromVec(absl::GetFlag(FLAGS_blocked_cocktails))),
341 _ <<
"error when solving for optimal set of ingredients");
344 if (mode ==
"latex") {
345 std::cout << ExportToLaTeX(menu.cocktails) << std::endl;
346 return absl::OkStatus();
350 std::cout <<
"Considered " << AllCocktails().size() <<
" cocktails and "
351 << kIngredientsSize <<
" ingredients." << std::endl;
352 std::cout <<
"Solution has " << menu.ingredients.size()
353 <<
" ingredients to make " << menu.cocktails.size() <<
" cocktails."
357 std::cout <<
"Ingredients:" << std::endl;
358 for (
const std::string& ingredient : menu.ingredients) {
359 std::cout <<
" " << ingredient << std::endl;
361 std::cout <<
"Cocktails:" << std::endl;
362 for (
const Cocktail& cocktail : menu.cocktails) {
363 std::cout <<
" " << cocktail.name << std::endl;
365 return absl::OkStatus();
370int main(
int argc,
char** argv) {
372 const absl::Status
status = Main();
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
int main(int argc, char **argv)
ABSL_FLAG(std::string, mode, "text", "One of \"text\", \"latex\", or \"analysis\".")
void InitGoogle(const char *usage, int *argc, char ***argv, bool deprecated)
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
absl::StatusOr< SolveResult > Solve(const Model &model, const SolverType solver_type, const SolveArguments &solve_args, const SolverInitArguments &init_args)
StatusBuilder InternalErrorBuilder()
SolveParameters parameters
const VariableMap< double > & variable_values() const
#define OR_ASSIGN_OR_RETURN3(lhs, rexpr, error_expression)