「酒飲み父ちゃんのアルコール量チェック – UML でクラス図の概略を作成」の続き。
具体例をシンプルに計算
前回、 クラス図の概略を作成した。はじめにこのクラス図は横に置いておき、具体例をシンプルに計算してみる。例えば、
今日、父ちゃんが「日本酒 : 1 合、焼酎 : 0.5 合、ウイスキー : 30 ml 」飲んだとする。それぞれのアルコール度数を「 15 度, 25 度, 40 度」とした場合、全部で日本酒で換算すると何合飲んだか?
ん~、暗算では無理。(+_+)
これを計算するには、すべての単位を ml に換算して、
# 日本酒 1 合のアルコール量 ALCOHOL_PER_SAKE_1GOU = 180 * 15 / 100 # 飲酒 sake = 1 * 180 * 15 / 100 # 日本酒 : 1 合 shochu = 0.5 * 180 * 25 / 100 # 焼酎 : 0.5 合 whiskey = 30 * 1 * 40 / 100 # ウイスキー : 30 ml # 飲酒量を日本酒で考えると print round((sake + shochu + whiskey) / ALCOHOL_PER_SAKE_1GOU, 2)
翌日に再度、同じように計算したい場合は、「飲酒」の変数に設定した計算式の中の値を変更。別の種類の酒を飲んだら、新しく変数を追加。日本酒換算ではなくて、焼酎換算にしたい場合は、最初の ALCOHOL_PER_SAKE_1GOU の式を変更。新しい単位が追加された場合は、単位と ml への換算の式は頭の中に記憶しておくか、または別の管理方法を考えなくてはいけない。 この方法は、あくまでも一例を計算するだけ。
ちょっとまとめる
翌日もう少し入力しやすいように、変更がありそうなところをまとめておく。 単位 ・ml への変換やアルコールの度数の情報を一箇所で管理、アルコール量の計算で毎回度数を 100 で割るという手間をなくしたり。とりあえず、クラスを使わないで書くなら、
# 酒 : アルコール度数
LIQUOR_LIST = {u'日本酒' : 15,
u'ウイスキー' : 40,
u'焼酎' : 25}
# 単位 : 各単位は ml へ変換するときに乗算する値
UNIT_LIST = {'ml' : 1,
'l' : 1000,
u'合' : 180}
def alcoholPer1Unit(liquor, unit):
u""" 指定された酒の1単位当たりのアルコール量を返す """
return UNIT_LIST[unit] * LIQUOR_LIST[liquor] / 100.0
def quantityOfAlcohol(liquor, val, unit):
u""" アルコールの量を単位 ml で返す """
return val * UNIT_LIST[unit] * LIQUOR_LIST[liquor] / 100.0
def totalQuantityOfAlcohol(drinksOfToday):
u""" 今日の飲酒量からトータルのアルコール量を返す """
return sum([quantityOfAlcohol(x[0], x[1], x[2]) for x in drinksOfToday])
def isOk(quantityOfAl, limit):
u""" 一日の許容アルコール量を超えていないかチェックする """
return u"!!! 飲みすぎ !!!" if quantityOfAl > limit else ""
def printResult(drinksOfToday):
today = totalQuantityOfAlcohol(drinksOfToday)
print u"%s に換算すると %.2f %s 飲んだ" % \
(LIQUOR, today / alcoholPer1Unit(LIQUOR, UNIT), UNIT)
print isOk(today, quantityOfAlcohol(LIQUOR, LIMIT, UNIT))
# 飲酒制限
LIQUOR = u'日本酒'; LIMIT = 2; UNIT = u'合'
# 今日の飲酒量
drinksOfToday = [(u'日本酒', 1, u'合'),
(u'焼酎' , 0.5, u'合'),
(u'ウイスキー', 30, u'ml')]
printResult(drinksOfToday)
あ~、もうこれでいいような気もしてきた ^^; UI を被せるにはまだ足りないけど、計算する分にはこれでいいか。
クラス図に基づいて
UML で概略書いたので、それに基づいて実装してみる。図とはちょっと違うけれど ^^;
import logging
from decimal import Decimal
class Liquor:
u""" 酒 """
def __init__(self, name, alcoholByPercentage):
self.name = name # 酒の名前
# アルコールの含有量
self.alcoholByPercentage = Decimal(str(alcoholByPercentage))
def __str__(self):
return name + " " + str(self.alcoholByPercentage)
def setDefaultUnit(self, unit):
u""" この酒で使われるデフォルトの単位を設定する。
Drink#__call__ の呼出しに先立ち、単位を設定しておく。
"""
self.unit = unit
return self
class Quantity:
u""" 量 """
def __init__(self, number, unit):
self.number = Decimal(str(number)) # 数値
self.unit = unit # 単位
def __cmp__(self, other):
# 単位 ml に変換してから量を比較する
return cmp(self.convertToMl(), other.convertToMl())
def __str__(self):
return "%.2f" % self.number + " " + self.unit.name + \
" (%.2fml)" % self.unit.convertToMl(self.number)
def convertToMl(self):
u""" 量を単位 ml に変換して返す """
return self.unit.convertToMl(self.number)
def getUnit(self):
u""" 単位を返す """
return self.unit
class Unit:
u""" 単位
基準となる単位を ml とした。すべての単位は ml へと変換するときに
乗算する値を持たなくてはいけない。
"""
def __init__(self, name, valOfConvMl):
self.name = name # 単位名
self.convertUnits = [] # 単位変換のリスト
# 単位 ml へ変換するために乗算する値
self.valOfConvMl = Decimal(str(valOfConvMl))
def convertToMl(self, number):
u""" 指定された数をml へ変換する。 """
return number * self.valOfConvMl
class Drink:
u""" 飲酒 """
def __init__(self, liquor, quantity=0):
self.liquor = liquor # 酒
self.quantity = quantity # 量
def __str__(self):
return u"%s, %-s" % \
(self.liquor.name, self.quantity)
def __call__(self, number):
u""" 数値を指定し予め生成ておいた飲酒オブジェクトを元に新たに Drink
オブジェクトを生成する。
Drink オブジェクトを簡単に作るためのメソッド。この呼出しに先立ち、
Liquor クラスだけが設定されていること。そのオブジェクトを使い、
数値を設定するだけで、Drink オブジェクトが生成される。
"""
assert self.liquor.unit is not None
return Drink(self.liquor, Quantity(number, self.liquor.unit))
def getAlcoholByMl(self):
u""" 単位 ml でアルコールの量を返す """
return self.getQuantityByMl() * self.getAlcoholPercentage()
def getQuantityByMl(self):
u""" 飲酒の量を単位 ml で返す """
return self.quantity.convertToMl()
def getAlcoholPercentage(self):
u""" アルコールの割合を返す """
return self.liquor.alcoholByPercentage / 100
def getUnit(self):
u""" 単位を返す """
return self.quantity.getUnit()
def getLiquorName(self):
u""" 酒の名前を返す """
return self.liquor.name
def getAlcoholPerUnitByMl(self):
u""" 1 単位当たりのアルコール量 を ml で返す """
return self.getAlcoholByMl() / self.quantity.number
class DrinkList:
u""" 飲酒のリスト """
def __init__(self):
self.drinks = [] # 飲酒のリスト
def __str__(self):
return u"--------- 飲酒リスト ---------\n" + \
"\n".join(["%s" % drink for drink in self.drinks]) + "\n" + \
"-"*30 + "\n" + \
u"アルコール総量:%.2f ml" % self.getAlcoholByMl()
def add(self, Drink):
u""" 飲酒を飲酒のリストに追加する """
self.drinks.append(Drink)
return self
def getAlcoholByMl(self):
u""" 飲酒におけるアルコールの総量を単位 ml で返す """
return sum([x.getAlcoholByMl() for x in self.drinks])
class AlcoholRestriction:
u""" 飲酒制限
ex. 「一日、日本酒で 2 合までなら飲んでいい」
"""
def __init__(self, drink):
self.drink = drink # 上限となる飲酒
def __str__(self):
result = u"制限: 一日に %s で %s までなら飲んいいよ!" % \
(self.drink.getLiquorName(), self.drink.quantity)
result += u"\n!!! 飲みすぎ!!!" if not self.permit() else ""
result += u"\n%s %s に相当" % \
(self.drink.getLiquorName(),
Quantity(self.drinkList.getAlcoholByMl() / \
self.drink.getAlcoholPerUnitByMl(),
self.drink.getUnit()))
return result
def setDrinkList(self, drinkList):
u""" 飲酒リストを設定 """
self.drinkList = drinkList
def getLimitOfAlcoholByMl(self):
u""" 設定された飲酒の上限であるアルコール量を返す """
return self.drink.getAlcoholByMl()
def permit(self):
u""" 指定された飲酒リストにおけるアルコールの量は許容されるか? """
assert self.drinkList is not None
if self.drinkList.getAlcoholByMl() <= self.getLimitOfAlcoholByMl():
return True
else: return False
# --- 酒-------------------------------------------------------
u""" 日本酒 """
LIQUOR_SAKE = Liquor(u"日本酒", 15)
u""" 焼酎 """
LIQUOR_SHOCHU = Liquor(u"焼酎", 25)
u""" ウイスキー """
LIQUOR_WHISKEY = Liquor(u"ウイスキー", 40)
# --- 単位 -----------------------------------------------------
# `ml' を量を比較する際の基準となる単位とする
u""" ml """
UNIT_ML = Unit(u"ml", 1)
u""" 合 """
UNIT_GOU = Unit(u"合", 180)
u""" 升 """
UNIT_SYOU = Unit(u"升", 1800)
# --- 量 -------------------------------------------------------
# ウイスキー・バーボンなどにおける特別な量の呼び方
u""" シングル """
QUANTITY_SINGLE = Quantity(30, UNIT_ML)
u""" ダブル """
QUANTITY_DOUBLE = Quantity(60, UNIT_ML)
u""" ジガー """
QUANTITY_JIGGER = Quantity(45, UNIT_ML)
# --- 飲酒 ------------------------------------------------------
u""" 飲酒オブジェクトを簡単に作成できるようにする。
ex. Liquor.DRINK_SAKE(2) : 「酒を 2 合飲んだ」
Liquor.DRINK_WHISKEY(100) : 「ウイスキーを 100 ml 飲んだ」
Drink#__call__() が呼出される。
"""
u""" 日本酒の飲酒を生成するためのオブジェクト
デフォルトの単位を「合」とする。
"""
DRINK_SAKE = Drink(LIQUOR_SAKE.setDefaultUnit(UNIT_GOU))
u""" 焼酎の飲酒を生成するためのオブジェクト
デフォルトの単位を「合」とする。
"""
DRINK_SHOCHU = Drink(LIQUOR_SHOCHU.setDefaultUnit(UNIT_GOU))
u""" ウイスキーの飲酒を生成するためのオブジェクト
デフォルトの単位を「ml」とした
"""
DRINK_WHISKEY = Drink(LIQUOR_WHISKEY.setDefaultUnit(UNIT_ML))
if __name__ == "__main__":
# 飲酒は「日本酒で2合まで」という制約
alcoholRestriction = AlcoholRestriction(DRINK_SAKE(2))
# 飲酒リスト
drinkList = DrinkList().add(
DRINK_SAKE(1)).add(
DRINK_SHOCHU(0.5)).add( # 焼酎 1 合
Drink(LIQUOR_WHISKEY, QUANTITY_SINGLE)) # ウイスキーのシングル
# 飲酒制限に飲酒リストを設定
alcoholRestriction.setDrinkList(drinkList)
# 飲酒リストを表示
print "%s" % drinkList
# 結果を表示
print "%s" % alcoholRestriction
ユニットテスト
やってること単純だけれど、テストしながらじゃないと実装できない ^^;
import unittest
import Liquor
import logging
from decimal import Decimal
class TestLiquor(unittest.TestCase):
def setUp(self):
# 単位
self.unitGou = Liquor.Unit(u"合", 180)
self.unitMl = Liquor.Unit(u"ml", 1)
# 酒
self.sake = Liquor.Liquor(u"焼酎", 25)
self.whiskey = Liquor.Liquor(u"ウイスキー", 45)
#--- 飲酒 ---
# 焼酎を 1 合飲む
self.d1 = Liquor.Drink(self.sake, Liquor.Quantity(1, self.unitGou))
# ウイスキーを 100 ml 飲む
self.d2 = Liquor.Drink(self.whiskey,
Liquor.Quantity(100, self.unitMl))
# 焼酎を 2 合飲む
self.d3 = Liquor.Drink(self.sake, Liquor.Quantity(2, self.unitGou))
# ウイスキーを 150 ml 飲む
self.d4 = Liquor.Drink(self.whiskey,
Liquor.Quantity(150, self.unitMl))
# 飲酒リスト
self.dl = Liquor.DrinkList()
self.dl.add(self.d1).add(self.d2).add(self.d3).add(self.d4)
self.dl2 = Liquor.DrinkList()
self.dl2.add(self.d1)
self.dl3 = Liquor.DrinkList()
self.dl3.add(self.d3)
# 飲酒制限
self.alcoholRestriction = Liquor.AlcoholRestriction(
Liquor.Drink(self.sake,
Liquor.Quantity(2, self.unitGou)))
def tearDown(self):
pass
def testAlcoholOfDrink(self):
u""" 飲酒におけるアルコール量のテスト """
# 焼酎を 1 合飲んだ場合、45 ml のアルコール
self.assertEqual(45, self.d1.getAlcoholByMl())
def testAlcoholOfDrinkList(self):
u""" 飲酒リストのアルコール量をテスト """
dl = Liquor.DrinkList()
dl.add(self.d1)
self.assertEqual(45, dl.getAlcoholByMl())
dl.add(self.d2)
self.assertEqual(90, dl.getAlcoholByMl())
dl.add(self.d3)
self.assertEqual(180, dl.getAlcoholByMl())
dl.add(self.d4)
self.assertEqual(Decimal("247.5"), dl.getAlcoholByMl())
# 合で
self.assertEqual(Decimal(str(1.375 * 180)), dl.getAlcoholByMl())
def testQuantityOfDrink(self):
u""" 1 合飲んだ焼酎の飲酒の量を ml で """
self.assertEqual(45, self.d1.getAlcoholByMl())
u""" 1 合飲んだ焼酎の飲酒の量を 合 で """
self.assertEqual(45, self.d1.getAlcoholByMl())
u""" 100 ml 飲んだウイスキーの飲酒の量を ml で """
self.assertEqual(45, self.d2.getAlcoholByMl())
def testAlcoholRestrictionSetting(self):
u""" Alcoholrestriction クラスの設定 """
self.assertEqual(90, self.alcoholRestriction.getLimitOfAlcoholByMl())
def testCmpQuantity(self):
# 単位が同じ場合
self.assertEqual(0,
cmp(Liquor.Quantity(10, self.unitGou),
Liquor.Quantity(10, self.unitGou)))
self.assertEqual(-1,
cmp(Liquor.Quantity(9, self.unitGou),
Liquor.Quantity(10, self.unitGou)))
self.assertEqual(1,
cmp(Liquor.Quantity(11, self.unitGou),
Liquor.Quantity(10, self.unitGou)))
self.assertEqual(True,
Liquor.Quantity(10, self.unitGou) ==
Liquor.Quantity(10, self.unitGou))
self.assertEqual(True,
Liquor.Quantity(10, self.unitGou) <=
Liquor.Quantity(10, self.unitGou))
self.assertEqual(True,
Liquor.Quantity(10, self.unitGou) >=
Liquor.Quantity(10, self.unitGou))
self.assertEqual(False,
Liquor.Quantity(10, self.unitGou) <
Liquor.Quantity(10, self.unitGou))
self.assertEqual(False,
Liquor.Quantity(10, self.unitGou) >
Liquor.Quantity(10, self.unitGou))
self.assertEqual(True,
Liquor.Quantity(9, self.unitGou) <
Liquor.Quantity(10, self.unitGou))
self.assertEqual(True,
Liquor.Quantity(11, self.unitGou) >
Liquor.Quantity(10, self.unitGou))
# 単位が異なる場合
self.assertEqual(0,
cmp(Liquor.Quantity(10, self.unitGou),
Liquor.Quantity(1800, self.unitMl)))
self.assertEqual(-1,
cmp(Liquor.Quantity(9, self.unitGou),
Liquor.Quantity(1800, self.unitMl)))
self.assertEqual(1,
cmp(Liquor.Quantity(11, self.unitGou),
Liquor.Quantity(1800, self.unitMl)))
self.assertEqual(True,
Liquor.Quantity(10, self.unitGou) ==
Liquor.Quantity(1800, self.unitMl))
self.assertEqual(True,
Liquor.Quantity(10, self.unitGou) <=
Liquor.Quantity(1800, self.unitMl))
self.assertEqual(True,
Liquor.Quantity(10, self.unitGou) >=
Liquor.Quantity(1800, self.unitMl))
self.assertEqual(True,
Liquor.Quantity(9, self.unitGou) <
Liquor.Quantity(1800, self.unitMl))
self.assertEqual(True,
Liquor.Quantity(11, self.unitGou) >
Liquor.Quantity(1800, self.unitMl))
self.assertEqual(False,
Liquor.Quantity(10, self.unitGou) <
Liquor.Quantity(1800, self.unitMl))
self.assertEqual(False,
Liquor.Quantity(10, self.unitGou) >
Liquor.Quantity(1800, self.unitMl))
def testAlcoholRestrictionPermit(self):
u""" 飲酒リストのテスト """
self.alcoholRestriction.setDrinkList(self.dl)
self.assertEqual(False,
self.alcoholRestriction.permit())
self.alcoholRestriction.setDrinkList(self.dl2)
self.assertEqual(True,
self.alcoholRestriction.permit())
# 2 合 ぎりぎり
self.alcoholRestriction.setDrinkList(self.dl3)
self.assertEqual(True,
self.alcoholRestriction.permit())
self.dl3.add(self.d1)
self.alcoholRestriction.setDrinkList(self.dl3)
self.assertEqual(False,
self.alcoholRestriction.permit())
def testAllcoholRestrictionPermit2(self):
u""" Liquor モジュールにおいて定義してある簡便な方法をテスト """
# 飲酒は「日本酒で2合まで」という制約決める
alcoholRestriction = Liquor.AlcoholRestriction(Liquor.DRINK_SAKE(2))
# 日本酒を 1 合飲んだ
drinkList = Liquor.DrinkList().add(Liquor.DRINK_SAKE(1))
# 飲酒制約を設定する
alcoholRestriction.setDrinkList(drinkList)
# 飲酒の総量は許容範囲か?
self.assertEqual(True, alcoholRestriction.permit())
# 焼酎を 0.7 合飲んだ
drinkList.add(Liquor.DRINK_SAKE(0.7))
self.assertEqual(True, alcoholRestriction.permit())
# ウイスキーを 30 ml 飲んだ
drinkList.add(Liquor.DRINK_WHISKEY(30))
self.assertEqual(False, alcoholRestriction.permit())
# 焼酎を 0.01 合飲んだ
drinkList.add(Liquor.DRINK_SAKE(0.01))
self.assertEqual(False, alcoholRestriction.permit())
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)s ' + \
'"%(pathname)s", line %(lineno)d, %(message)s')
logging.getLogger().setLevel(logging.DEBUG)
unittest.main()
0コメント:
コメントを投稿