Skip to content

Instantly share code, notes, and snippets.

@ara-ta3

ara-ta3/Makefile Secret

Last active December 11, 2016 10:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ara-ta3/58bec44c58720a8228b6ae67d796f687 to your computer and use it in GitHub Desktop.
Save ara-ta3/58bec44c58720a8228b6ae67d796f687 to your computer and use it in GitHub Desktop.
CTO Challenge 2016 2nd LV3
from typing import List, Dict
from functools import reduce
from collections import Counter
from menu import Menu
Coupon = int
class CouponSelector():
def __init__(self,
limitation_of_coupons: Dict[Coupon, int],
coupons_available_for_pizza: List[Coupon],
lowest_amount_for_using_coupon: int
) -> None:
self.limitation_of_coupons = limitation_of_coupons
self.coupons_available_for_pizza = coupons_available_for_pizza
self.lowest_amount_for_using_coupon = lowest_amount_for_using_coupon
def select_optimum_combination_by_menus(
self,
menus: List[Menu],
coupons: List[Coupon]):
amount = reduce(lambda amount, m: amount + m.price, menus, 0)
available_coupons = _filter_limited_coupons(
coupons,
self.limitation_of_coupons
)
if not _pizza_exists(menus):
available_coupons = [c for c in available_coupons
if c not in self.coupons_available_for_pizza]
return self.select_optimum_combination(amount, available_coupons)
def select_optimum_combination(self, amount: int, coupons: List[Coupon]):
if amount <= self.lowest_amount_for_using_coupon:
return []
sorted_coupons = sorted(coupons, reverse=True)
rest = amount
used_coupons = []
for c in sorted_coupons:
if rest < c:
break
rest -= c
used_coupons.append(c)
return used_coupons
def _pizza_exists(menus: List[Menu]):
return any(m.is_pizza() for m in menus)
def _filter_limited_coupons(
coupons: List[Coupon],
coupon2limit: Dict[Coupon, int]):
counter = Counter(coupons)
for c, n in counter.items():
counter[c] = min(coupon2limit[c], n)
return list(counter.elements())
from coupon_selector import CouponSelector
coupon_selector = CouponSelector({}, [], 1000)
def test_select_optimum_combination_case1():
selected = coupon_selector.select_optimum_combination(
1000,
[500, 500, 200, 100, 100, 100]
)
assert selected == []
def test_select_optimum_combination_case2():
selected = coupon_selector.select_optimum_combination(1210, [])
assert selected == []
def test_select_optimum_combination_case3():
selected = coupon_selector.select_optimum_combination(
1210,
[500, 500, 200, 100, 100, 100]
)
assert selected == [500, 500, 200]
def test_select_optimum_combination_case4():
selected = coupon_selector.select_optimum_combination(
1530,
[500, 500, 200, 100, 100, 100]
)
assert selected == [500, 500, 200, 100, 100, 100]
def test_discounted_in_order_when_coupons_are_not_sorted():
selected = coupon_selector.select_optimum_combination(
1530,
[500, 200, 100, 500, 100, 100]
)
assert selected == [500, 500, 200, 100, 100, 100]
from coupon_selector import CouponSelector
from menu import PizzaMenu, SideMenu
coupon_selector = CouponSelector(
{500: 2, 200: 2, 100: 3, 400: 1},
[400],
1000
)
def test_select_optimum_combination_by_menu_case1_on_lv2():
menus = [PizzaMenu("ジェノベーゼ", "M", 1000)]
selected = coupon_selector.select_optimum_combination_by_menus(
menus,
[500, 200, 100, 400]
)
assert selected == []
def test_select_optimum_combination_by_menu_case2_on_lv2():
menus = [PizzaMenu("マルゲリータ", "M", 1200)]
selected = coupon_selector.select_optimum_combination_by_menus(
menus,
[]
)
assert selected == []
def test_select_optimum_combination_by_menu_case3_on_lv2():
menus = [
SideMenu("ポテトフライ", 400),
SideMenu("ポテトフライ", 400),
SideMenu("シーザーサラダ", 600)
]
selected = coupon_selector.select_optimum_combination_by_menus(
menus,
[500, 500, 200, 100, 100, 400]
)
assert selected == [500, 500, 200, 100, 100]
def test_select_optimum_combination_by_menu_case4_on_lv2():
menus = [PizzaMenu("ジェノベーゼ", "L", 1400)]
selected = coupon_selector.select_optimum_combination_by_menus(
menus,
[500, 500, 200, 100, 100, 400]
)
assert selected == [500, 500, 400]
def test_select_optimum_combination_by_menu_case5_on_lv2():
menus = [PizzaMenu("ジェノベーゼ", "M", 1000), PizzaMenu("マルゲリータ", "M", 1200)]
selected = coupon_selector.select_optimum_combination_by_menus(
menus,
[500, 500, 500, 200, 200, 200, 100, 100, 100, 100, 400, 400]
)
assert selected == [500, 500, 400, 200, 200, 100, 100, 100]
from itertools import combinations, product
from datetime import datetime
from setmenu_discounter import SetMenuDiscount, SetMenuDiscounter
from menu import PizzaMenu, SideMenu
all_pizza = [
PizzaMenu("ジェノベーゼ", "M", 1000),
PizzaMenu("マルゲリータ", "M", 1200),
PizzaMenu("ジェノベーゼ", "L", 1400),
PizzaMenu("マルゲリータ", "L", 1800),
]
l_pizza = [
PizzaMenu("ジェノベーゼ", "L", 1400),
PizzaMenu("マルゲリータ", "L", 1800),
]
side_menus = [
SideMenu("ポテトフライ", 400),
SideMenu("グリーンサラダ", 500),
SideMenu("シーザーサラダ", 600)
]
def is_weekday_and_from_11_to_14(d):
return 0 <= d.weekday() and \
d.weekday() < 5 and \
11 <= d.hour and \
d.hour <= 14
service = SetMenuDiscounter([
SetMenuDiscount(
"ピザ2セット",
list(map(lambda x: list(x), combinations(all_pizza, 2))),
None,
[SideMenu("ポテトフライ", 400)],
None
),
SetMenuDiscount(
"ピザL2セット",
list(map(lambda x: list(x), combinations(l_pizza, 2))),
None,
[
SideMenu("ポテトフライ", 400),
SideMenu("グリーンサラダ", 500),
SideMenu("シーザーサラダ", 600)
],
None
),
SetMenuDiscount(
"平日ランチセット",
list(map(lambda x: list(x), product(all_pizza, side_menus))),
[is_weekday_and_from_11_to_14],
[],
400
)
])
def test_pizza2_set():
orders = [
PizzaMenu("ジェノベーゼ", "M", 1000),
PizzaMenu("マルゲリータ", "M", 1200),
SideMenu("ポテトフライ", 400),
]
(actual, discount) = service.decide_discount(orders, datetime.now())
assert type(actual) == SetMenuDiscount
assert actual.name == "ピザ2セット"
assert discount == 400
def test_pizzaL2_set():
orders = [
PizzaMenu("ジェノベーゼ", "L", 1400),
PizzaMenu("マルゲリータ", "L", 1800),
SideMenu("グリーンサラダ", 500),
]
(actual, discount) = service.decide_discount(orders, datetime.now())
assert type(actual) == SetMenuDiscount
assert actual.name == "ピザL2セット"
assert discount == 500
def test_weekday_lunch_set():
orders = [
PizzaMenu("ジェノベーゼ", "L", 1400),
SideMenu("グリーンサラダ", 500),
]
(actual, discount) = service.decide_discount(
orders,
datetime(2016, 10, 26, 11, 30, 0)
)
assert type(actual) == SetMenuDiscount
assert actual.name == "平日ランチセット"
assert discount == 400
pip=bin/pip
python=bin/python
pyflakes=bin/pyflakes
pytest=bin/pytest
run: $(python)
$< main.py -c $(cs) -m $(ms)
test: $(pytest)
$< -v ./lv1_test.py ./lv2_test.py ./lv3_test.py
install: $(pip)
$< install -r requirements.txt
$(pytest): $(pip)
$(MAKE) install
$(pyflakes): $(pip)
$(MAKE) install
$(python):
virtualenv . -p python3
$(pip):
virtualenv . -p python3
clean:
rm -rf ./bin
rm -rf ./lib
rm -rf ./include
from typing import Dict, List, Tuple
class Menu(object):
def __init__(self, name, price):
self.name = name
self.price = price
def __eq__(self, other):
return self.name == other.name
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(str(self))
def __str__(self):
return self.name
def is_pizza(self):
return type(self) == PizzaMenu
class PizzaMenu(Menu):
def __init__(self, name, size, price):
super(PizzaMenu, self).__init__(name, price)
self.size = size
def __str__(self):
return str(self.name + self.size)
class SideMenu(Menu):
def __init__(self, name, price):
super(SideMenu, self).__init__(name, price)
class MenuRepository():
def __init__(self, master: Dict[str, Menu] = {}) -> None:
self.master = master
def find_by_name(self, name: str) -> Menu:
return self.master.get(name, None)
class OrderService():
def __init__(self, menu_repository: MenuRepository) -> None:
self.menu_repository = menu_repository
def order(self, menu_names: List[str]) -> Tuple[List[Menu], List[str]]:
menus = []
not_found_menus = []
for name in menu_names:
menu = self.menu_repository.find_by_name(name)
if menu is not None:
menus.append(menu)
else:
not_found_menus.append(name)
return (menus, not_found_menus)
pytest
flake8
mypy-lang
from datetime import datetime
from typing import List, Tuple, Callable
from menu import Menu
class SetMenuDiscount(object):
def __init__(self, name: str,
required_menus_combinations: List[List[Menu]],
available_time_rules: List[Callable[[datetime], bool]],
discount_target_menus: List[Menu],
discount: int = None)->None:
self.name = name
self.required_menus_combinations = required_menus_combinations
self.available_time_rules = available_time_rules
self.discount_target_menus = discount_target_menus
self.discount = discount
def is_available(self, ordered_menus: List[Menu],
ordered_time: datetime)->bool:
return self.in_available_period(ordered_time) \
and self.is_matched(ordered_menus)
def is_matched(self, ordered_menus: List[Menu])->bool:
ordered_menu_set = set(ordered_menus)
for combination in self.required_menus_combinations:
c = set(combination)
if c.intersection(ordered_menu_set) == c:
return True
return False
def in_available_period(self, ordered_time: datetime)->bool:
if self.available_time_rules is None:
return True
return any(map(lambda fn: fn(ordered_time), self.available_time_rules))
def get_target_discount_menus(self, ordered_menus: List[Menu])->List[Menu]:
t = set(self.discount_target_menus).intersection(set(ordered_menus))
return list(t)
def has_amount_discounts(self)->bool:
return self.discount is not None
def has_menu_discounts(self)->bool:
return len(self.discount_target_menus) > 0
def calculate_discount(self, ordered_menus: List[Menu])->int:
price = 0
if self.has_menu_discounts():
menus = self.get_target_discount_menus(ordered_menus)
prices = map(lambda m: m.price, menus)
m = max(prices, default=0)
price = max(price, m)
if self.has_amount_discounts():
price = max(price, self.discount)
return price
class SetMenuDiscounter():
def __init__(self, discounts: List[SetMenuDiscount])->None:
self.discounts = discounts
def decide_discount(self,
ordered: List[Menu],
current_time: datetime
)->Tuple[SetMenuDiscount, int]:
availables = [d for d in self.discounts
if d.is_available(ordered, current_time)]
if not availables:
return (None, 0)
best, *rest = availables
best_discount = best.calculate_discount(ordered)
for d in rest:
p = d.calculate_discount(ordered)
if best_discount < p:
best = d
best_discount = p
return (best, best_discount)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment