![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmLwUTO3ADOxMDOtMzMwQjM3MDNxgDMyETOxAjMtkTNzgDM58CXyETOxAjMvwVO1MDOwkzLcd2bsJ2Lc12bj5ycn9Gbi52YugTMwIzZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
不知道有多少人是被這個頭圖騙進來的:)
事情的起因是這樣的,上周有同學問小編,看着小編的示例代碼敲代碼,感覺自己也會寫了,如果不看的話,七七八八可能也寫的出來,但是一旦自己獨立寫一段程式,感覺到無從下手。
其實這個很正常,剛開始學習寫代碼,都是跟着别人的套路往下寫,看的套路少,很難形成自己的套路,這就和做數學題是一樣的,做一道題就想會所有的題目,這個可能性微乎其微,都是通過大量的練習來摸索到自己的套路。
正好快過年了,各個公司都會搞一些抽獎活動,小編今天就來聊一下,如果要寫一個簡單的抽獎程式,小編是怎麼寫的。
分析需求
我們先整理下思路,目标是什麼?
目标是要寫一個抽獎程式,那麼抽獎程式的核心是什麼?
當然是如何判斷一個人中獎了。那麼如何判斷一個人中獎呢?
是不是可以通過随機函數來操作呢?
中獎方法
一步一步來,我們先通過随機函數來判斷是否中獎。代碼是不是可以先寫成下面這樣:
import random
# 判斷中獎函數
def lottery():
flag = random.randint(0, 9)
if flag < 2:
return True
else:
return False
首先,我們擷取 0 ~ 9 之間的随機正整數(這裡不讨論 random 是不是真随機,從狹義上來講我們可以認為它是随機的),如果中獎率為 20% 的話,我們可以認為小于 2 的數字為中獎,其餘的為沒有中獎。然後中獎後傳回 True ,沒有中獎傳回 False 。
我們加一個入口測試函數,測試一下上面的代碼是否能正常運作,并且中獎率是否能維持在大約 20 % 左右。
if __name__ == '__main__':
# 中獎次數
a = 0
# 沒有中獎次數
b = 0
for i in range(1000000) :
if (lottery()):
a += 1
else:
b += 1
print('共計中獎:', a, ',未中獎:', b)
執行結果:
共計中獎: 200145 ,未中獎: 799855
上面的測試總共循環了 1 百萬次,大約執行需要 2 ~ 3 秒左右,速度還是蠻快的。可以看到,中獎結果确實接近 20% 左右。
動态中獎率
難道到這裡就結束了麼?當然不可能,這裡隻是剛剛開了個頭。
如果這時老闆說,你這個機率不能調整啊,需要讓中獎率可以動态調整的,活動剛開始的時候中獎率要高,随着時間的推移,中獎率要降下來。
這時候咋整,傻眼了吧。
既然中獎率要可調整,那麼我們中獎率就不能定死在程式中了,這個中獎率需要有一個地方去做存儲,在每次做随機的時候将這個中獎率取出來。
簡單易行的方法就是将這個中獎率放在資料庫中或者緩存服務中,這個根據實際業務場景來定。一般是根據預估通路壓力的大小來進行技術選型,如果壓力不是特别大,那麼放在資料庫中也是可以的,如果并發會比較高的話,建議還是放在緩存中。
我們來寫一個從資料庫擷取中獎機率的方法(為了展示直覺,小編這裡直接使用 Mysql 資料庫用作資料存儲),先看下資料庫的資料:
很簡單的設計了一張表,裡面有意義的字段有兩個,一個用作中獎率的分子部分,一個用作中獎率的分母部分。分母部分最好要設定成 100 、 1000 、 10000 這種,這樣計算中獎率會比較好計算。
def get_lottery_rate():
conn = pymysql.connect(host='localhost', user='root', password='password', database='test', charset='utf8mb4')
try:
sql = 'SELECT fenzi, fenmu FROM rate'
cursor = conn.cursor()
cursor.execute(sql)
result = cursor.fetchone()
return result
except Exception as ex:
print(ex)
finally:
conn.close()
運作這個方法測試結果如下:
(10, 100)
可以看到,我們獲得了一個元組,裡面的内容就是我們從資料庫取出來的分子和分母。
我們将前面的抽獎的那個方法改一下,改成從資料庫擷取中獎比例。修改後的代碼如下:
def lottery():
rate = get_lottery_rate()
flag = random.randint(1, rate[1])
if flag < rate[0]:
return True
else:
return False
還是運作上面的測試方法,這裡要注意下,因為我們現在是從資料庫擷取資料,每次方法執行都要加上資料庫連結的建立與銷毀,建議将循環次數修改為 1000 以内,不然執行的時間就有點太長了。
小編這裡将循環次數修改為 1000 次後,執行結果如下:
共計中獎: 92 ,未中獎: 908
那麼到這裡,我們就可以通過修改資料庫中資料實時的操作中獎率了。當然上面的慢的問題我們可以使用資料庫連接配接池等技術進行優化。
增加獎項
那麼是否就結束了呢?no no no,我們接着加需求。
現在,我們隻能知道每次到底中不中獎,隻有一個獎項,但是現在想變成 3 個獎項,如:一等獎、二等獎、三等獎那該怎麼辦?
這個對之前的抽獎方法改動就有點大了,首先我們先在資料庫增加出來另外兩個獎項的配置:
配置這裡三個獎項的分母最好保持一緻,否則後續計算會徒增複雜度。
修改我們擷取配置的那個方法:
def get_lottery_rate():
conn = pymysql.connect(host='localhost', port = 3306, user='root', password='password', database='test', charset='utf8mb4')
try:
sql = 'SELECT * FROM rate order by id asc '
cursor = conn.cursor()
cursor.execute(sql)
result = cursor.fetchall()
return result
except Exception as ex:
print(ex)
finally:
conn.close()
測試調用後結果如下:
((1, 10, 100), (2, 5, 100), (3, 1, 100))
先在我們要做的是要将這個配置融入進我們之前的中獎的那個方法中,不多說,直接上代碼:
# 判斷中獎函數
def lottery():
config = get_lottery_rate()
flag = random.randint(1, config[0][2])
if flag <= config[0][1]:
return 1
elif flag > config[0][1] and flag <= (config[1][1] + config[0][1]):
return 2
elif flag > (config[1][1] + config[0][1]) and flag <= (config[2][1] + config[1][1]):
return 3
else:
return 0
接着修改我們的做測試的代碼:
def main():
# 一等獎中獎次數
a = 0
# 二等獎中獎次數
b = 0
# 三等獎中獎次數
c = 0
# 未中獎次數
d = 0
# 循環次數
e = 0
for i in range(1000):
e += 1
print('目前循環次數:', e)
result = lottery()
print('目前中獎結果:', result)
if (result == 1):
a += 1
elif (result == 2):
b += 1
elif (result == 3):
c += 1
else:
d += 1
print('一等獎中獎:', a, ',二等獎中獎次數:', b, ',三等獎中獎次數:', c, ',未中獎次數:', d)
調用我們的測試方法:
if __name__ == '__main__':
main()
小編這裡的運作結果如下:
增加會員判斷
到這裡我們還沒完,還能加需求,現在網站大多數都是會員制的,比如白銀會員,黃金會員,鑽石會員,如果不同的會員等級需要有不同的中獎率,這個是很正常的一件事兒,小編現在還清晰的記得當年某家大型網際網路公司代碼中的注釋 “窮逼 VIP(活動送的那種)” 。
我們假設鑽石會員的中獎率為整體中獎率的 100% ,黃金會員的中獎率為整體中獎率的 50% ,白銀會員的中獎率為整體中獎率的 20% 。
最簡單的實作方式是直接在最外層套一層會員中獎率的判斷,不知道各位同學怎麼想。
小編這裡給出自己的解決方案:
# 判斷會員等級中獎率過濾
# 會員等級 1.白銀會員 2.黃金會員 3. 鑽石會員
def vip_lottery(level):
rate = random.randint(1, 10)
# 如果是鑽石會員,直接進入抽獎函數
if level == 3:
return lottery()
# 如果是黃金會員, 50% 機率進入抽獎函數
elif level == 2:
if rate <= 5:
return lottery()
else:
return 0
# 如果是白銀會員, 20% 機率進入抽獎函數
elif level == 1:
if rate <= 2:
return lottery()
else:
return 0
# 如果是其他,直接傳回未中獎
else:
return 0
我們新增一個測試增加會員過濾的測試方法:
# 會員制中獎測試方法
def test_vip():
print('請輸入您目前的會員等級:1.白銀會員 2.黃金會員 3. 鑽石會員')
level = input()
result = vip_lottery(int(level))
if (result == 1):
print('恭喜您中了一等獎')
elif (result == 2):
print('恭喜您中了二等獎')
elif (result == 3):
print('恭喜您中了三等獎')
else:
print('未中獎,謝謝惠顧')
在我們的入口函數中調用這個方法:
if __name__ == '__main__':
test_vip()
最終測試結果如下:
小編的人品還可以嘛,直接就能中三等獎。
那麼,到這裡,是不是一個簡易的抽獎程式就算完成了呢?其實還能接着加,如果每個獎項都有數量限制,并且限制的數量是可以随時調整的等等等等,小編這裡就不一一列舉了。
整體代碼寫的稍微有些長了,小編就不貼出來了,上傳到代碼倉庫各位感興趣的同學自己通路吧。
注意: 本篇文章所使用代碼,僅供示範講解使用,不可用于生産環境,在通路量過大的情況下會産生嚴重的性能問題。
示例代碼
示例代碼-Github
示例代碼-Gitee
掃描二維碼關注「極客挖掘機」公衆号!作者:極客挖掘機
定期發表作者的思考:技術、産品、營運、自我提升等。
本文版權歸作者極客挖掘機和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。
如果您覺得作者的文章對您有幫助,就來作者個人小站逛逛吧:
極客挖掘機