Create a gist now

Instantly share code, notes, and snippets.

@ara-ta3 /Makefile Secret
Last active Dec 11, 2016

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