這段時間工作巨多,直到今天才有時間寫點東西。可是要輸出點什麼比較好呢?這時候看到了隔壁桌面上放著一張機打的寫著一組一組數字的紙,有了!那要不抽個獎吧!
問題分析
抽獎,講究一個隨機,只要把隨機搖號解決了不就不管什麼規則都能抽了?所以我們需要一個可以產生隨機數的函式。不僅要隨機數,我們還需要的是一個閉區間隨機取整數的函式。所以
const random = (m, n) =>
m + Math.floor(Math.random() * (n - m))複製程式碼
沒吃過豬肉還見過豬跑,號碼肯定是抽一個少一個,所以根本不存在隨機數能相同的情況!所以這個方法是不能用的。那麼,拿來抽的方法肯定得滿足三個條件:
- 有範圍,是閉區間
- 起始值肯定大於1
- 隨機數不能有重複
問題解決
關於隨機不重複我最先想到的是利用sort
對陣列隨機打散的方法。這個時候可能會有其他聲音:
要不要這麼水?
sort
用來排序的不懂?
水不水不知道,看一下MDN。
arr.sort([compareFunction])
- 如果 compareFunction(a, b) 小於 0 ,那麼 a 會被排列到 b 之前;
- 如果 compareFunction(a, b) 等於 0 , a 和 b 的相對位置不變。備註: ECMAScript 標準並不保證這一行為,而且也不是所有瀏覽器都會遵守(例如 Mozilla 在 2003 年之前的版本);
- 如果 compareFunction(a, b) 大於 0 , b 會被排列到 a 之前。
- compareFunction(a, b) 必須總是對相同的輸入返回相同的比較結果,否則排序的結果將是不確定的。
so
// arr 陣列,num 取個數const random = (arr:number[], num: number) =>
arr.sort(() =>
Math.random() - 0.5).splice(0, num)複製程式碼
具體問題具體解決
首先,我們需要通過百度查一下業界知名抽獎活動典型的遊戲規則:
某抽獎遊戲分為紅色區和藍色區,紅色區由1-33共三十三個號碼組成,藍色區由1-16共十六個號碼組成。需要在紅色區選擇6個號碼和在藍色區選擇1個號碼合成一次有效號碼組。
作為賭狗前端,提取這段話幾個關鍵點:
- 紅色區 1 ~ 33
- 藍色區 1 ~ 16
- 取紅 6 個,取藍 1 個
別說了我腦子裡又有函式了
// ...randomconst dualColor = () =>
{
const reds = [1, 2, ..33] const blues = [1, 2, ..16] const red = random(reds, 6) const blue = random(blues, 1) return [red, blue]
}複製程式碼
這個時候我們看一眼方法,你這方法不對啊!以紅色為例子,我們的函式表達結果是:直接把號碼打散後流出前 6 個,而遊戲規則指出號碼不僅一個一個放出,而且放的過程中並沒有停止打散。所以這個隨機函式顯然不合理。那還能怎麼辦,改啊!
// 打散後流出第一個和剩下的號碼組function random(arr) {
const newarr = arr.sort(() =>
Math.random() - 0.5) const val = newarr.shift() return [val, newarr]
}function dualColor() {
let redballs = [1, 2, ..33] let blueballs = [1, 2, ..16] let red = [], blue = [] for (let i = 0;
i <
6;
i++) {
const balls = random(redballs) red.push(balls[0]) redballs = balls[1]
} blue.push(random(blueballs)[0]) return [red, blue]
}複製程式碼
做法優化
說實話這麼寫程式可憋死我了…
不會真的有人這麼寫陣列吧
如果按照正常隨機數方法那麼做的話,我確實提供一個最大值一個最小值就結束了,關鍵這是個陣列,又沒有什麼range
之類的可以用,不會真的有人手寫 1 ~ 33 吧。不過我們可以曲線撈一下
// 0 ~ 9const arr = [...Array(10).keys()]複製程式碼
所以第一點改造兩個球陣列
let reds: number[] = [...Array(33).keys()].map(i =>
i+1) let blues: number[] = [...Array(16).keys()].map(i =>
i+1)複製程式碼
為什麼 +1
呢?號碼也沒從 0
開始的啊!
也許我們應該讓隨機函式更純更通用?
我們需要讓抽獎更靈活一些,畢竟只要是抽的,我全都要。我想讓隨機函式能接受一個陣列和我想要的個數,再返回結果。
同樣的會出現需要迴圈取值的情況,為什麼我們不用神奇的尾遞迴呢?
function randomVal( arr: number[], total: number, temp: number[] = [],): number[] {
const [head, ...body] = arr .sort(() =>
Math.random() - 0.5) return !total ? temp : randomVal(body, total - 1, temp.concat(head));
}複製程式碼
俺尋思這兩個號組寫起來可太累了
改!都可以改!我們可以改成存放起始值和終止值的元組
function dualColor() {
const reds: [number, number] = [1, 33] const blues: [number, number] = [1, 16] return [randomVal(reds, 6), randomVal(blues, 1)]
}複製程式碼
相應的隨機函式也做點小改動
function randomVal( fromto: number[], total: number, temp: number[] = [],): number[] {
const [head, ...body] = (temp.length ? fromto : [...Array(fromto[1]).keys()] .map(item =>
item + 1) .splice(fromto[0] - 1) ).sort(() =>
Math.random() - 0.5);
return !total ? temp : randomVal(body, total - 1, temp.concat(head))
}複製程式碼
我好像看漏了規則
除此之外還有三種:(一)從紅色區中選擇7–20個,從藍色區中選擇1個。(二)從紅色區中選擇6個,從藍色區中選擇2–16個。(三)從紅色區中選擇7–20個,從藍色區中選擇2–16個。
雖然我看不懂,但是這麼改就沒問題了吧
// 我管你幾個,全給你安排上!function dualColor(red: number = 6, blue: number = 1) {
const reds: [number, number] = [1, 33] const blues: [number, number] = [1, 16] return [randomVal(reds, red), randomVal(blues, blue)]
}複製程式碼
所以最後解決方案是
function dualColor(red: number = 6, blue: number = 1) {
const reds: [number, number] = [1, 33] const blues: [number, number] = [1, 16] return [randomBall(reds, red), randomBall(blues, blue)]
}function randomBall( fromto: number[], total: number, temp: number[] = [],): number[] {
const [head, ...body] = (temp.length ? fromto : [...Array(fromto[1]).keys()] .splice(fromto[0] - 1) .map(item =>
item + 1) ).sort(() =>
Math.random() - 0.5);
return !total ? temp : randomBall(body, total - 1, temp.concat(head))
}複製程式碼
賞心悅目?