Skip to content

Instantly share code, notes, and snippets.

@lvngd
Last active June 13, 2020 15:29
Show Gist options
  • Save lvngd/3fe7ab41df930aea127457178518b811 to your computer and use it in GitHub Desktop.
Save lvngd/3fe7ab41df930aea127457178518b811 to your computer and use it in GitHub Desktop.
class CocktailSuggester:
def __init__(self, ingredients, ratio=0):
self.ratio = ratio
self.ingredients = self.format_ingredients(ingredients)
#lookup tables and graph
self.cocktails_to_ingredients, self.ingredients_to_cocktails, self.compatible_ingredients = self.get_cocktail_lookups()
self.cocktail_matches = set()
self.partial_cocktails = {}
self.make_cocktail()
def format_ingredients(self, ingredients):
"""format and normalize ingredients for better matching"""
to_replace = {'cointreau': 'triple sec', '&': 'and'}
updated_ingredients = []
for i in ingredients:
i = i.lower().strip()
if i in to_replace:
ingredient = to_replace[i]
else:
ingredient = i
updated_ingredients.append(ingredient)
return updated_ingredients
def get_cocktail_lookups(self):
with open('cocktails.txt','r') as read:
data = read.read().splitlines()
cocktails_ingredients = {}
ingredients_to_cocktails = {}
#compatible ingredients are any ingredients that are found in a recipe together
compatible_ingredients = {}
for row in data:
cocktail_recipe = row.split(',')
cocktail_name = cocktail_recipe[0]
ingredients = cocktail_recipe[1:]
cocktails_ingredients[cocktail_name] = set(ingredients)
for count,ingredient in enumerate(ingredients):
if ingredient not in ingredients_to_cocktails:
ingredients_to_cocktails[ingredient] = set([cocktail_name])
else:
ingredients_to_cocktails[ingredient].add(cocktail_name)
if ingredient not in compatible_ingredients:
compatible_ingredients[ingredient] = set([i for i in ingredients if i != ingredient])
else:
compatible_ingredients[ingredient].update([i for i in ingredients if i != ingredient])
return cocktails_ingredients, ingredients_to_cocktails, compatible_ingredients
def ingredients_pointing_to(self,ingredient):
"""get ingredients pointing to this ingredient and return sorted list of ingredients in order of which has the most other ingredients pointing to it"""
top_sorted = sorted(self.compatible_ingredients[ingredient],key=lambda x:len(self.compatible_ingredients[x]),reverse=True)
return top_sorted
def valid_cocktail(self,cocktail_ingredients):
"""
return True if the ingredients can form a valid cocktail
"""
if len(cocktail_ingredients) > 1:
first_ingredient = None
for ingredient in cocktail_ingredients:
if not first_ingredient:
first_ingredient = ingredient
else:
cocktail_list = self.ingredients_to_cocktails[first_ingredient]
next_ingredient = ingredient
next_cocktails = self.ingredients_to_cocktails[next_ingredient]
#find common cocktails for each cocktail ingredient(set intersection)
result_set = cocktail_list.intersection(next_cocktails)
if not result_set:
return False
return True
def matching_cocktails(self,ingredients):
"""returns True if there is a cocktail match and False if not"""
for cocktail,recipe_ingredients in self.cocktails_to_ingredients.items():
if cocktail in self.partial_cocktails and cocktail in self.cocktail_matches:
#remove full cocktail matches from partial dictionary
del self.partial_cocktails[cocktail]
if ingredients == recipe_ingredients:
self.cocktail_matches.add(cocktail)
return True
else:
#check if ingredients make up part of a cocktail
missing_ingredients = recipe_ingredients - ingredients
ratio = len(missing_ingredients) / len(recipe_ingredients)
if ratio > 0 and ratio < self.ratio:
if cocktail not in self.partial_cocktails:
self.partial_cocktails[cocktail] = missing_ingredients
else:
self.partial_cocktails[cocktail].update(missing_ingredients)
return False
def make_cocktail(self):
"""depth first search from each input ingredient"""
for ingredient in self.ingredients:
if ingredient not in self.compatible_ingredients:
print("can't find {} in any recipe - skipping".format(ingredient))
else:
ingredients_tried = []
#initialize dfs stack with current input ingredient
ingredients_to_try = [ingredient]
#set of ingredients to check for valid ingredient combinations
cocktail_recipe = set()
while ingredients_to_try:
current = ingredients_to_try.pop()
cocktail_recipe.add(current)
ingredients_tried.append(current)
#get neighbors
potential_ingredients = self.ingredients_pointing_to(current)
for ing in potential_ingredients:
#make sure it's an ingredient we have
if ing in self.ingredients:
cocktail_recipe.add(ing)
if self.valid_cocktail(cocktail_recipe):
#check to see if it's a recipe match
found_match = self.matching_cocktails(cocktail_recipe)
#add to stack
if ing not in ingredients_tried and ing not in ingredients_to_try:
ingredients_to_try.append(ing)
cocktail_recipe.remove(ing)
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment