「酒飲み父ちゃんのアルコール量チェック – 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コメント:
コメントを投稿