วิทยาลัยนานาชาตินวัตกรรมดิจิทัล มหาวิทยาลัยเชียงใหม่
8 มกราคม 2569
ทำไมความรู้สึกถึงเชื่อถือไม่ได้?
(async () => {
const uid = "mh_final_v2_" + Math.random().toString(36).substr(2, 9);
const box = html`<div style="width:100%; display:flex; justify-content:center; font-family:'Sarabun', system-ui, sans-serif; color:#334155;">
<style>
.mh-wrapper {
width: 100%; max-width: 800px;
background: white; padding: 25px; border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08); border: 1px solid #e2e8f0;
}
.mh-header { text-align:center; margin-bottom: 20px; }
.mh-title { margin:0; color:#b45309; font-size: 24px; }
/* Grid Layout */
.mh-grid {
display: grid;
grid-template-columns: 1fr 280px;
gap: 25px;
align-items: start;
}
/* Left: Doors */
.col-left {
display: flex; justify-content: center; align-items: center;
min-height: 250px; background: #fdfaf6; border-radius: 12px;
border: 2px dashed #fed7aa;
}
.doors-row { display: flex; gap: 15px; perspective: 800px; }
/* Right: Controls */
.col-right {
display: flex; flex-direction: column; gap: 15px;
}
/* Rules Box (New) */
.rules-mini {
background: #f0f9ff; border-left: 4px solid #3b82f6;
padding: 10px 15px; font-size: 13px; color: #0c4a6e;
border-radius: 4px; line-height: 1.5;
}
/* Elements */
.door-box {
width: 85px; height: 140px; position: relative; cursor: pointer;
transition: transform 0.2s;
}
.door-box:active { transform: scale(0.95); }
.door {
width: 100%; height: 100%; position: absolute; top:0; left:0;
background: linear-gradient(to bottom right, #f59e0b, #d97706);
border: 3px solid #b45309; border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 36px; color: white; font-weight: 800; text-shadow: 1px 1px 0 rgba(0,0,0,0.2);
transition: transform 0.5s, box-shadow 0.3s;
transform-origin: left; z-index: 2;
box-shadow: 3px 3px 10px rgba(0,0,0,0.15);
}
.door:hover { filter: brightness(1.1); }
.door.open { transform: rotateY(-110deg); pointer-events:none; }
.door.selected {
background: linear-gradient(to bottom right, #3b82f6, #2563eb); border-color: #1e3a8a;
}
.door.clickable-option {
cursor: pointer; animation: pulse-border 1.5s infinite;
}
@keyframes pulse-border {
0% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0.7); }
100% { box-shadow: 0 0 0 8px rgba(139, 92, 246, 0); }
}
.door-content {
width: 100%; height: 100%; position: absolute; top:0; left:0;
background: #f1f5f9; border-radius: 6px;
display: flex; align-items: center; justify-content: center;
font-size: 55px; z-index: 1; overflow:hidden;
box-shadow: inset 2px 2px 5px rgba(0,0,0,0.1);
}
.anim-win { animation: bounce 1s infinite; }
.anim-lose { animation: shake 0.5s; }
@keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px) scale(1.1); } }
@keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
/* UI Controls */
.status-main { font-size: 18px; font-weight: bold; color: #1e293b; min-height: 24px; }
.status-sub { font-size: 13px; color: #64748b; margin-bottom: 10px; min-height: 20px; }
.btn-restart {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white; font-weight: bold; font-size:16px;
padding: 10px 0; width: 100%; border: none; border-radius: 8px;
cursor: pointer; box-shadow: 0 4px 10px rgba(16, 185, 129, 0.3);
}
.btn-restart:hover { transform: translateY(-2px); }
.stats-box {
background: #f8fafc; padding: 12px; border-radius: 8px;
border: 1px solid #e2e8f0; font-size: 13px;
display: flex; flex-direction: column; gap: 8px;
}
.stat-row { display: flex; justify-content: space-between; }
.stat-val { font-weight: bold; }
</style>
<div class="mh-wrapper">
<div class="mh-header">
</div>
<div class="mh-grid">
<div class="col-left">
<div class="doors-row" id="${uid}-doors"></div>
</div>
<div class="col-right">
<div class="rules-mini">
<b>📜 วิธีเล่น:</b><br>
1. คลิกเลือก 1 ประตู<br>
2. Host เปิดประตูแพะให้ดู 🐐<br>
3. คลิก <b>"ประตูเดิม"</b> หรือ <b>"ประตูใหม่"</b> เพื่อวัดดวง!
</div>
<div style="height:1px; background:#e2e8f0; margin:5px 0;"></div>
<div style="text-align:center;">
<div id="${uid}-msg" class="status-main">...</div>
<div id="${uid}-sub" class="status-sub"></div>
<div id="${uid}-ctrl" style="width:100%; min-height:45px;"></div>
</div>
<div class="stats-box">
<div class="stat-row">
<span>⚡ เปลี่ยน (Switch)</span>
<span id="${uid}-sw" class="stat-val" style="color:#8b5cf6">0/0</span>
</div>
<div style="height:1px; background:#e2e8f0;"></div>
<div class="stat-row">
<span>🔒 ไม่เปลี่ยน (Stay)</span>
<span id="${uid}-st" class="stat-val" style="color:#64748b">0/0</span>
</div>
</div>
</div>
</div>
</div>
</div>`;
// State
let prizeDoor = 0;
let selectedDoor = -1;
let openedDoor = -1;
let gameState = "PICK";
let stats = { swW:0, swT:0, stW:0, stT:0 };
const $ = (s) => box.querySelector(`#${uid}-${s}`);
const elDoors = $("doors");
const elMsg = $("msg");
const elSub = $("sub");
const elCtrl = $("ctrl");
function initRound() {
gameState = "PICK";
selectedDoor = -1;
openedDoor = -1;
prizeDoor = Math.floor(Math.random() * 3);
elMsg.textContent = "👇 เลือกประตู 1 บาน";
elSub.textContent = "เพื่อเริ่มเกม";
elCtrl.innerHTML = "";
renderDoors();
}
function pickDoor(idx) {
selectedDoor = idx;
const options = [0,1,2].filter(d => d !== prizeDoor && d !== selectedDoor);
openedDoor = options[Math.floor(Math.random() * options.length)];
gameState = "DECIDE";
elMsg.innerHTML = `Host เปิดเบอร์ ${openedDoor+1} (แพะ)`;
elSub.innerHTML = `คลิกที่ <b>ประตู</b> เพื่อยืนยัน หรือ เปลี่ยน`;
renderDoors();
}
function finishRound(finalChoice) {
const isSwitch = (finalChoice !== selectedDoor);
selectedDoor = finalChoice;
gameState = "RESULT";
const isWin = (finalChoice === prizeDoor);
if(isSwitch) { stats.swT++; if(isWin) stats.swW++; }
else { stats.stT++; if(isWin) stats.stW++; }
const calc = (w,t) => t===0 ? "0%" : Math.round(w/t*100)+"%";
$(`sw`).textContent = `${stats.swW}/${stats.swT} (${calc(stats.swW, stats.swT)})`;
$(`st`).textContent = `${stats.stW}/${stats.stT} (${calc(stats.stW, stats.stT)})`;
if (isWin) {
elMsg.innerHTML = `🎉 <span style="color:#10b981">ยินดีด้วย! ชนะ!</span>`;
elSub.innerHTML = `คุณเลือก ${isSwitch ? "Switch" : "Stay"} แล้วได้รถ`;
} else {
elMsg.innerHTML = `🐐 <span style="color:#ef4444">เสียใจด้วย...</span>`;
elSub.innerHTML = `รถอยู่ที่ประตู ${prizeDoor+1}`;
}
const btn = document.createElement("button");
btn.className = "btn-restart";
btn.innerHTML = "🔄 เล่นอีกครั้ง";
btn.onclick = initRound;
elCtrl.appendChild(btn);
renderDoors(true);
}
function renderDoors(revealAll = false) {
elDoors.innerHTML = "";
for (let i = 0; i < 3; i++) {
const boxDiv = document.createElement("div");
boxDiv.className = "door-box";
const content = document.createElement("div");
content.className = "door-content";
content.textContent = (i === prizeDoor) ? "🚗" : "🐐";
if (revealAll && i === selectedDoor) {
if (i === prizeDoor) content.classList.add("anim-win");
else content.classList.add("anim-lose");
}
const door = document.createElement("div");
door.className = "door";
door.textContent = i + 1;
if (i === selectedDoor) {
door.classList.add("selected");
if (gameState === "DECIDE") door.textContent = "🔒";
}
let isOpen = false;
if (i === openedDoor) isOpen = true;
if (revealAll) isOpen = true;
if (isOpen) {
door.classList.add("open");
}
else if (gameState === "DECIDE") {
door.classList.add("clickable-option");
if (i !== selectedDoor) {
door.style.background = "linear-gradient(to bottom right, #8b5cf6, #7c3aed)";
door.style.borderColor = "#5b21b6";
door.textContent = "⚡";
}
door.onclick = () => finishRound(i);
}
else if (gameState === "PICK") {
door.onclick = () => pickDoor(i);
}
boxDiv.append(content, door);
elDoors.append(boxDiv);
}
}
initRound();
return box;
})();ตอนเริ่มเกม มีประตู 3 บาน สมมติคุณเลือก ประตูเบอร์ 1 ให้มองว่าเราแบ่งประตูออกเป็น 2 ทีมทันทีครับ:
🟦 ทีมของคุณ (ประตู 1): มีสมาชิก 1 บาน = โอกาสชนะ 1/3 (33%)
🟥 ทีมคนอื่น (ประตู 2 และ 3): มีสมาชิก 2 บาน = โอกาสชนะ 2/3 (66%)
ถาม: ถ้ายืนยันจะอยู่ทีมเดิม โอกาสชนะของคุณคือเท่าไหร่? ตอบ: 33% (เพราะทีมคุณมีแค่ประตูเดียว)
พิธีกร (ซึ่งรู้เฉลย) เดินไปที่ 🟥 ทีมคนอื่น และเปิดประตูแพะให้ดู 1 บาน (สมมติเปิดประตู 2) บ: การเปิดประตูแพะ ไม่ได้ทำให้ความน่าจะเป็นรวมของ “ทีมคนอื่น” ลดลง** ทีมนั้นยังคงกุมความได้เปรียบที่ 2/3 (66%) อยู่เหมือนเดิมตั้งแต่วินาทีแรกที่เริ่มเกม
เพียงแต่ตอนนี้… พลัง 66% นั้นไม่ได้กระจายอยู่ 2 ประตูแล้ว แต่มัน “ไหลมารวมกัน” อยู่ที่ประตูเดียวที่เหลือรอดครับ (ประตู 3)
ดังนั้น: การ “เปลี่ยน” ก็คือการกระโดดจากทีมเล็ก (33%) ไปอยู่ทีมใหญ่ (66%) ที่เหลือตัวรอดเพียงตัวเดียวนั่นเอง

เซตตัวอย่าง \(S\): ไพ่ทั้งหมด 52 ใบที่ไม่ซ้ำกัน
S = {เอซโพแดง, 2 โพแดง, \(\ldots\), คิงโพดำ}
เหตุการณ์ \(A\): การจั่วไพ่สีแดง
A = {ไพ่โพแดงทั้งหมดและไพ่ข้าวหลามตัดทั้งหมด(รวม 26 ใบ)}
สัจพจน์ของโคลโมโกรอฟ:
ให้ \(S\) เป็นเซตตัวอย่าง (sample space) และให้ \(A\) เป็นเหตุการณ์ใด ๆ ที่เป็นเซตย่อยของ \(S\) สัจพจน์ทั้งสามมีดังนี้:
สัจพจน์ที่ 1 ความน่าจะเป็นต้องไม่เป็นค่าลบ
\[P(A) \geq 0\]
สำหรับทุกเหตุการณ์ \(A\) หมายความว่าค่าความน่าจะเป็นต้องไม่เป็นค่าลบ อาจเป็นศูนย์หรือเป็นค่าบวกก็ได้เสมอ
สัจพจน์ที่ 2 ความน่าจะเป็นของเซตตัวอย่างเท่ากับ 1
\[P(S) = 1\]
ซึ่งหมายความว่า ความน่าจะเป็นของเหตุการณ์ที่ครอบคลุมผลลัพธ์ที่เป็นไปได้ทั้งหมด ต้องมีค่าเท่ากับ 1
สัจพจน์ที่ 3 การบวกความน่าจะเป็นของเหตุการณ์ที่เกิดร่วมกันไม่ได้
ถ้า \(A\) และ \(B\) เป็น เหตุการณ์ที่เกิดร่วมกันไม่ได้
หมายความว่าไม่มีผลลัพธ์ใดที่เหมือนกัน (เช่น \(A \cap B = \emptyset\)) ดังนั้น
\[
P(A \cup B) = P(A) + P(B)
\]
ซึ่งหมายความว่า ถ้าเหตุการณ์สองเหตุการณ์ไม่สามารถเกิดขึ้นพร้อมกันได้ ความน่าจะเป็นที่เหตุการณ์ใดเหตุการณ์หนึ่งจะเกิดขึ้น เท่ากับผลรวมของความน่าจะเป็นของแต่ละเหตุการณ์
จากสัจพจน์ทั้งสามข้อ เราสามารถอนุมานสมบัติสำคัญอื่น ๆ ได้ เช่น
ความน่าจะเป็นของเหตุการณ์ที่เป็นไปไม่ได้มีค่าเท่ากับศูนย์
\[P(\emptyset) = 0\]
เหตุการณ์ที่เป็นไปไม่ได้จะมีความน่าจะเป็นเท่ากับศูนย์
เนื่องจากไม่สามารถเกิดขึ้นได้เลย
กฎของส่วนเติมเต็ม (Complement Rule)
\[P(A^c) = 1 - P(A)\]
ซึ่งหมายความว่า หากความน่าจะเป็นของเหตุการณ์ \(A\) คือ \(P(A)\) แล้วความน่าจะเป็นของเหตุการณ์ที่ ไม่ใช่ \(A\) จะเท่ากับ
\[1 - P(A)\]
กฎการบวกทั่วไปของความน่าจะเป็น (General Addition Rule of Probability)
สำหรับเหตุการณ์ใด ๆ \(A\) และ \(B\):
\[P(A \cup B) = P(A) + P(B) - P(A \cap B)\]
สูตรนี้ใช้ได้แม้ในกรณีที่เหตุการณ์ทั้งสองมีส่วนที่ทับซ้อนกัน
การทอยลูกเต๋าหนึ่งลูก
ให้ \(S = \{1, 2, 3, 4, 5, 6\}\)
ใช้กฎการบวก: \[P(A \cup B) = P(A) + P(B) - P(A \cap B)\]
\[= 0.5 + 0.333 - 0.167 = 0.666\]
From Randomness to Patterns
viewof galtonBoardAutoScaling = (function() {
const uid = "gt_scale_" + Math.random().toString(36).substr(2, 9);
const width = 600;
const height = 500;
const MIN_VAL = 50;
const MAX_VAL = 200;
const DEFAULT_VAL = 100;
const container = html`<div style="font-family:'Sarabun', sans-serif; display:flex; flex-direction:column; align-items:center; gap:15px; background:white; padding:20px; border-radius:16px; border:1px solid #e2e8f0; box-shadow:0 4px 15px rgba(0,0,0,0.05); max-width:700px; margin:0 auto;">
<canvas width="${width}" height="${height}" style="background:#f8fafc; border-radius:8px; border:1px solid #cbd5e1;"></canvas>
<div style="display:flex; gap:10px; flex-wrap:wrap; justify-content:center; align-items:center; background:#f1f5f9; padding:10px; border-radius:12px;">
<div style="display:flex; align-items:center; gap:5px; margin-right:10px;">
<label style="font-size:14px; font-weight:bold; color:#475569;">เป้าหมาย:</label>
<input id="${uid}-target" type="number"
value="${DEFAULT_VAL}" min="${MIN_VAL}" max="${MAX_VAL}"
style="width:80px; padding:5px; border-radius:4px; border:1px solid #cbd5e1; text-align:center; font-weight:bold; color:#334155;">
</div>
<button id="${uid}-play" style="background:#3b82f6; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer;">▶ เริ่ม</button>
<button id="${uid}-fast" style="background:#8b5cf6; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer;">⏩ เททันที</button>
<button id="${uid}-reset" style="background:#ef4444; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer;">↺ ล้างค่า</button>
</div>
<div style="font-size:14px; color:#475569;">
ลงถังแล้ว: <b id="${uid}-finished" style="color:#059669;">0</b>
</div>
</div>`;
const canvas = container.querySelector("canvas");
const ctx = canvas.getContext("2d");
const inpTarget = container.querySelector(`#${uid}-target`);
const elFinished = container.querySelector(`#${uid}-finished`);
const btnPlay = container.querySelector(`#${uid}-play`);
const btnFast = container.querySelector(`#${uid}-fast`);
const btnReset = container.querySelector(`#${uid}-reset`);
const ROWS = 12;
const START_X = width / 2;
const START_Y = 50;
const SPACING_X = 26;
const SPACING_Y = 25;
const PEG_R = 3;
const BALL_R = 4;
const FLOOR_Y = START_Y + (ROWS * SPACING_Y) + 20;
// 🔥 พื้นที่ความสูงสูงสุดที่ยอมให้กองลูกบอลสูงได้ (Pixel)
const MAX_STACK_HEIGHT = 200;
let balls = [];
let bins = new Array(ROWS + 2).fill(0);
let countSpawned = 0;
let countFinished = 0;
let isRunning = false;
let animationId;
let spawnerId;
function getSafeTarget() {
let val = parseInt(inpTarget.value) || DEFAULT_VAL;
if (val < MIN_VAL) val = MIN_VAL;
if (val > MAX_VAL) val = MAX_VAL;
inpTarget.value = val;
return val;
}
class Ball {
constructor() {
this.r = 0; this.t = 0; this.path = [];
let binIdx = 0;
for(let i=0; i<ROWS; i++) {
const dir = Math.random() < 0.5 ? -1 : 1;
if(dir === 1) binIdx++;
this.path.push({ r: i+1, bin: binIdx });
}
this.x = START_X; this.y = START_Y - 20;
}
update() {
if (this.r >= ROWS) return true;
const targetStep = this.path[this.r];
const targetX = START_X + (targetStep.bin - (this.r + 1) / 2) * SPACING_X;
const targetY = START_Y + (this.r + 1) * SPACING_Y;
this.t += 0.15;
if (this.t >= 1) {
this.r++; this.t = 0; this.x = targetX; this.y = targetY;
if (this.r === ROWS) {
bins[targetStep.bin]++; countFinished++;
elFinished.innerText = countFinished.toLocaleString();
return true;
}
} else {
const prevBin = (this.r === 0) ? 0 : this.path[this.r - 1].bin;
const startX = (this.r === 0) ? START_X : START_X + (prevBin - this.r / 2) * SPACING_X;
const startY = START_Y + this.r * SPACING_Y;
this.x = startX + (targetX - startX) * this.t;
this.y = startY + (targetY - startY) * this.t;
this.y -= Math.sin(this.t * Math.PI) * 5;
}
return false;
}
draw() {
ctx.beginPath(); ctx.arc(this.x, this.y, BALL_R, 0, Math.PI * 2);
ctx.fillStyle = "#3b82f6"; ctx.fill();
}
}
function nCr(n, r) {
if (r < 0 || r > n) return 0;
let res = 1; for (let i = 0; i < r; i++) res = res * (n - i) / (i + 1); return res;
}
// --- 🔥 ฟังก์ชันคำนวณ Scale Factor ---
function getScaleFactor() {
const maxBinCount = Math.max(...bins, 1);
const normalHeight = maxBinCount * (BALL_R * 2);
// ถ้าความสูงจริง เกินโควต้าที่ตั้งไว้ (MAX_STACK_HEIGHT)
// ให้หาอัตราส่วนเพื่อย่อลงมา
if (normalHeight > MAX_STACK_HEIGHT) {
return MAX_STACK_HEIGHT / normalHeight;
}
return 1; // ถ้ายังไม่สูงเกิน ก็ใช้ขนาดจริง (1:1)
}
function drawNormalCurve() {
ctx.beginPath(); ctx.strokeStyle = "#ef4444"; ctx.lineWidth = 2;
const n = ROWS;
const maxProb = nCr(n, Math.floor(n / 2)) * Math.pow(0.5, n);
const scaleFactor = getScaleFactor();
// คำนวณความสูงสูงสุดที่กราฟแท่ง (Balls) แสดงผลอยู่จริง ณ ตอนนี้
const maxBin = Math.max(...bins);
const currentVisHeight = maxBin * (BALL_R * 2) * scaleFactor;
// Scale เส้นแดงให้สูงเท่ากับยอดกองลูกบอล
const curveScale = (currentVisHeight / maxProb);
for (let x = 0; x < width; x += 5) {
const binIdx = (x - START_X) / SPACING_X + ROWS / 2;
const mu = n / 2; const sigma = Math.sqrt(n * 0.25);
const prob = (1 / (sigma * Math.sqrt(2 * Math.PI))) * Math.exp(-0.5 * Math.pow((binIdx - mu) / sigma, 2));
const plotY = FLOOR_Y - (prob * countFinished * (curveScale / countFinished * (maxBin / maxProb))); // Simplified scaling match
// วิธีคิดง่ายๆ: ยอด Curve ต้องเท่ากับ currentVisHeight
// ยอด Curve คือ prob_at_mu * scale
// prob_at_mu ~= 1 / (sigma * sqrt(2pi)) ~= maxProb (approx for binomial)
// ใช้ logic วาดตามสัดส่วนจอแทน
const relativeY = FLOOR_Y - (prob / maxProb * currentVisHeight);
if (x === 0) ctx.moveTo(x, relativeY); else ctx.lineTo(x, relativeY);
}
ctx.stroke();
}
function drawLoop() {
ctx.clearRect(0, 0, width, height);
// Draw Pegs
ctx.fillStyle = "#94a3b8";
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c <= r; c++) {
const x = START_X + (c - r / 2) * SPACING_X;
const y = START_Y + r * SPACING_Y + 10;
ctx.beginPath(); ctx.arc(x, y, PEG_R, 0, Math.PI * 2); ctx.fill();
}
}
ctx.beginPath(); ctx.moveTo(0, FLOOR_Y); ctx.lineTo(width, FLOOR_Y);
ctx.strokeStyle = "#cbd5e1"; ctx.stroke();
// --- 🔥 วาดกองลูกบอลแบบ Auto-Scale ---
const scaleFactor = getScaleFactor();
const diameter = BALL_R * 2;
const scaledStep = diameter * scaleFactor; // ระยะห่างระหว่างลูก (ถ้าย่อจะน้อยลง)
for (let i = 0; i <= ROWS; i++) {
const count = bins[i];
const x = START_X + (i - ROWS / 2) * SPACING_X;
for (let j = 0; j < count; j++) {
// คำนวณตำแหน่ง Y โดยใช้ scaledStep
const y = FLOOR_Y - BALL_R - (j * scaledStep);
ctx.beginPath(); ctx.arc(x, y, BALL_R, 0, Math.PI * 2);
// ทำให้สีจางลงนิดนึงถ้ามันซ้อนกันเยอะๆ จะได้ดูสวย
ctx.fillStyle = scaleFactor < 0.5 ? "rgba(96, 165, 250, 0.8)" : "#60a5fa";
ctx.fill();
// ถ้าซ้อนกันแน่นมาก ไม่ต้องวาดขอบ (เส้นดำ) จะได้ไม่รก
if (scaleFactor > 0.3) {
ctx.strokeStyle = "#2563eb"; ctx.lineWidth = 0.5; ctx.stroke();
}
}
}
if (countFinished > 20) drawNormalCurve();
for (let i = balls.length - 1; i >= 0; i--) {
const finished = balls[i].update();
if (!finished) balls[i].draw(); else balls.splice(i, 1);
}
animationId = requestAnimationFrame(drawLoop);
}
function stopSpawner() {
clearInterval(spawnerId); spawnerId = null; isRunning = false;
btnPlay.innerText = "▶ เริ่ม"; btnPlay.style.backgroundColor = "#3b82f6";
}
btnPlay.onclick = () => {
const target = getSafeTarget();
if (isRunning) { stopSpawner(); } else {
if (countSpawned >= target) return;
isRunning = true;
btnPlay.innerText = "⏸ หยุด"; btnPlay.style.backgroundColor = "#fbbf24";
spawnerId = setInterval(() => {
if (countSpawned >= target) { stopSpawner(); return; }
balls.push(new Ball()); countSpawned++;
}, 30);
}
};
btnFast.onclick = () => {
const target = getSafeTarget();
const needed = Math.max(0, target - countSpawned);
for (let i = 0; i < needed; i++) {
let bin = 0;
for (let r = 0; r < ROWS; r++) { if (Math.random() > 0.5) bin++; }
bins[bin]++; countSpawned++; countFinished++;
}
elFinished.innerText = countFinished.toLocaleString();
stopSpawner();
};
btnReset.onclick = () => {
stopSpawner(); balls = []; bins.fill(0);
countSpawned = 0; countFinished = 0;
elFinished.innerText = "0";
};
drawLoop();
return container;
})()ตัวแปรสุ่ม คือ ตัวแปรที่แทนค่าของผลลัพธ์จากการทดลองแบบสุ่ม โดยค่าของมันถูกกำหนดจากความบังเอิญหรือความน่าจะเป็น ตัวแปรสุ่มมักถูกใช้ในสถิติและทฤษฎีความน่าจะเป็นเพื่ออธิบายการกระจายความน่าจะเป็นของข้อมูล
ตัวแปรสุ่มแบ่งออกเป็นสองประเภทหลัก ได้แก่:
ตัวแปรสุ่มแบบไม่ต่อเนื่อง (Discrete Random Variable)
ตัวแปรสุ่มแบบต่อเนื่อง (Continuous Random Variable)
มีค่าที่เป็นไปได้แบบนับได้ (countable number of possible values)
มักใช้กับเหตุการณ์ที่สามารถนับผลลัพธ์ได้ เช่น
จำนวนแต้มที่ได้จากการทอยลูกเต๋า หรือจำนวนคำตอบที่ถูกต้องในแบบทดสอบ
ตัวอย่าง
การทอยลูกเต๋า: ให้ \(X\) เป็นค่าที่ปรากฏบนลูกเต๋า → \(X\) = {1, 2, 3, 4, 5, 6}
การโยนเหรียญ: ให้ \(Y\) เป็นจำนวนครั้งที่ได้หัวเมื่อโยนเหรียญ 3 ครั้ง →
\(Y = \{0, 1, 2, 3\}\)
จำนวนลูกค้าที่มาซื้อของในแต่ละวัน: ให้ \(X\) เป็นจำนวนลูกค้าที่มาซื้อของในแต่ละวัน → \(X = \{0, 1, 2, 3, 4, \cdots \}\)
เป็นตัวแปรที่สามารถมีค่า ใด ๆ ก็ได้ ภายในช่วงของจำนวนจริง
ใช้สำหรับ ปริมาณที่สามารถวัดได้ เช่น น้ำหนัก ส่วนสูง หรือเวลา
ตัวอย่าง
เวลาการให้บริการลูกค้า: ตัวแปร \(T\) อาจมีค่าอยู่ระหว่าง 0 ถึง 10 นาที
อุณหภูมิของเมือง: ตัวแปร \(Z\) อาจมีค่าอยู่ในช่วง 25°C ถึง 35°C
อัตราผลตอบแทนจากการลงทุน: ตัวแปร \(r \in (-100\%, \infty)\)
เมื่อเราสามารถนิยามรูปแบบฟังก์ชันเฉพาะของการกระจายได้ เราจะเรียกสิ่งนั้นว่า การแจกแจงความน่าจะเป็น (Probability Distribution)
การแจกแจงความน่าจะเป็น คือการอธิบายว่าค่าของตัวแปรสุ่มแต่ละค่ามีโอกาสเกิดขึ้นบ่อยเพียงใด หรือมีความเป็นไปได้มากน้อยเพียงใด
สมบัติของการแจกแจงความน่าจะเป็นแบบไม่ต่อเนื่อง (Properties of a Discrete Probability Distribution)
ให้ \(X\) เป็นตัวแปรสุ่ม และให้ \(P(X)\) เป็นความน่าจะเป็นของค่าที่เป็นไปได้แต่ละค่าของ \(X\)
โดยต้องเป็นไปตามเงื่อนไขดังต่อไปนี้:
การทอยลูกเต๋า
ให้ตัวแปรสุ่ม \(X\) แทนค่าตัวเลขที่ปรากฏบนลูกเต๋าหกหน้า
(\(X = 1, 2, 3, 4, 5, 6\))
\[ P(X) = \begin{cases} \frac{1}{6}, & X = 1, 2, 3, 4, 5, 6 \\ 0, & \text{อื่น ๆ} \end{cases} \]
คำถามที่ 1:
จงหาความน่าจะเป็นที่จำนวนที่ทอยได้จะ น้อยกว่า 4
\[ P(X < 4) = P(1) + P(2) + P(3) = \frac{1}{6} + \frac{1}{6} + \frac{1}{6} = \frac{3}{6} = 0.5 \]
คำถามที่ 2:
จงหาความน่าจะเป็นที่จำนวนที่ทอยได้จะเป็น เลขคู่
เลขคู่บนลูกเต๋า: 2, 4, 6
\[ P(\text{เลขคู่}) = P(2) + P(4) + P(6) = \frac{1}{6} + \frac{1}{6} + \frac{1}{6} = \frac{3}{6} = 0.5 \]
คำถามที่ 3:
จงหาความน่าจะเป็นที่จำนวนที่ทอยได้จะ มากกว่าหรือเท่ากับ 5
ตัวเลขที่เป็นไปได้: 5, 6
\[ P(X \geq 5) = P(5) + P(6) = \frac{1}{6} + \frac{1}{6} = \frac{2}{6} = \frac{1}{3} \approx 0.333 \]
การแจกแจงความน่าจะเป็นแบบไม่ต่อเนื่องที่สำคัญ
การแจกแจงแบบเบอร์นูลลี (Bernoulli Distribution):
ใช้สำหรับเหตุการณ์ที่มีเพียงสองผลลัพธ์ เช่น “สำเร็จ / ล้มเหลว”
การแจกแจงแบบทวินาม (Binomial Distribution):
ใช้จำลองการทดลองที่เป็นอิสระหลายครั้ง โดยแต่ละครั้งมีสองผลลัพธ์ที่เป็นไปได้
การแจกแจงแบบปัวซอง (Poisson Distribution):
ใช้จำลองจำนวนเหตุการณ์ที่เกิดขึ้นภายในช่วงเวลาหรือพื้นที่คงที่
คำจำกัดความ (Definition):
การแจกแจงแบบเบอร์นูลลี เป็น การแจกแจงความน่าจะเป็นแบบไม่ต่อเนื่อง
สำหรับตัวแปรสุ่มที่มี เพียงสองผลลัพธ์ที่เป็นไปได้เท่านั้น ได้แก่:
ความสำเร็จ (Success) — มักแทนด้วยค่า 1
ความล้มเหลว (Failure) — มักแทนด้วยค่า 0
การแจกแจงนี้จำลองผลลัพธ์ของ การทดลองเพียงครั้งเดียว
ซึ่งผลลัพธ์สามารถเกิดได้เพียงหนึ่งในสองกรณีเท่านั้น
นิยามทางคณิตศาสตร์ (Mathematical Definition):
ให้ \(X \sim \text{Bernoulli}(p)\) โดยที่
\(X \in \{0, 1\}\)
\(p\) คือ ความน่าจะเป็นของความสำเร็จ (เช่น \(P(X = 1) = p\))
\(1 - p\) คือ ความน่าจะเป็นของความล้มเหลว (เช่น \(P(X = 0) = 1 - p\))
\(0 \leq p \leq 1\)
ฟังก์ชันมวลความน่าจะเป็น (Probability Mass Function; PMF):
\[ P(X = x) = p^x (1 - p)^{1 - x}, \quad \text{เมื่อ } x \in \{0, 1\} \]
คุณสมบัติ (Properties):
ค่าเฉลี่ย (Mean): \(\mathbb{E}[X] = p\)
ความแปรปรวน (Variance): \(\text{Var}(X) = p(1 - p)\)
ตัวอย่าง (Examples):
การโยนเหรียญ (หัว = 1, ก้อย = 0)
การสอบผ่านหรือไม่ผ่าน (ผ่าน = 1, ไม่ผ่าน = 0)
การคลิกโฆษณา (คลิก = 1, ไม่คลิก = 0)
การตรวจสอบสินค้าในโรงงาน (มีตำหนิ = 1, ไม่มีตำหนิ = 0)
ทำไมการแจกแจงแบบเบอร์นูลลีจึงสำคัญ?
เป็น พื้นฐานสำคัญ สำหรับการแจกแจงอื่น ๆ เช่น การแจกแจงแบบทวินาม (Binomial distribution) ซึ่งใช้จำลองจำนวนความสำเร็จจากการทดลองแบบเบอร์นูลลีหลายครั้งที่เป็นอิสระต่อกัน
ถูกนำไปใช้ในหลายสาขา เช่น การจำแนกแบบทวิภาค (Binary Classification), การเรียนรู้ของเครื่อง (Machine Learning), เศรษฐศาสตร์ และ การควบคุมคุณภาพ (Quality Control) เป็นต้น
ให้ \(X \sim \text{Bernoulli}(p)\)
เราจะใช้ค่าของ \(p\) (ความน่าจะเป็นของความสำเร็จ) ที่แตกต่างกันในแต่ละตัวอย่าง
ตัวอย่างที่ 1: การโยนเหรียญที่ยุติธรรม
หัว = 1 (ความสำเร็จ) และ ก้อย = 0 (ความล้มเหลว) ดังนั้น \(p = 0.5\)
คำถาม: ค่า \(P(X = 1)\) คือเท่าใด?
คำตอบ: \(P(X = 1) = p = 0.5\)
คำถาม: ค่า \(P(X = 0)\) คือเท่าใด?
คำตอบ: \(P(X = 0) = 1 - p = 0.5\)
คำถาม: ค่าคาดหมาย (Expected Value) คือเท่าใด?
คำตอบ: \(\mathbb{E}[X] = p = 0.5\)
ตัวอย่างที่ 2: การควบคุมคุณภาพในโรงงาน
เครื่องจักรผลิตชิ้นส่วน โดยมีความน่าจะเป็นที่ชิ้นส่วนนั้นจะเป็น ของเสีย (Defective) เท่ากับ 0.1
ให้ \(X = 1\) เมื่อชิ้นส่วนเป็นของเสีย และ \(X = 0\) เมื่อไม่เป็นของเสีย
คำถาม: ความน่าจะเป็นที่ชิ้นส่วนจะเป็นของเสียคือเท่าใด?
คำตอบ: \(P(X = 1) = p = 0.1\)
คำถาม: ความแปรปรวนของการแจกแจงนี้คือเท่าใด?
คำตอบ: \(\text{Var}(X) = p(1 - p) = 0.1 \times 0.9 = 0.09\)
ตัวอย่างที่ 3: การคลิกโฆษณาออนไลน์
มีความน่าจะเป็นที่ผู้ใช้จะคลิกโฆษณาเท่ากับ 0.25
ให้ \(X = 1\) เมื่อมีการคลิก และ \(X = 0\) เมื่อไม่มีการคลิก
คำถาม: ค่า \(P(X = 1)\) คือเท่าใด?
คำตอบ: \(P(X = 1) = p = 0.25\)
คำถาม: ค่า \(P(X = 0)\) คือเท่าใด?
คำตอบ: \(P(X = 0) = 1 - p = 0.75\). คำถาม: ส่วนเบี่ยงเบนมาตรฐาน (Standard Deviation) คือเท่าใด?
คำตอบ: \(\text{SD}(X) = \sqrt{p(1 - p)} = \sqrt{0.25 \times 0.75} = \sqrt{0.1875} \approx 0.433\)
(async () => {
// ===================== Layout & Styles =====================
// แก้ไข max-width จาก 1120px เป็น 1008px (ลดลง 10%)
const box = html`<div style="max-width:1008px;font:14px system-ui, -apple-system, Segoe UI, Roboto, sans-serif; color:#0f172a;">
<style>
.sa-wrap{ display:grid; grid-template-columns:320px 1fr; gap:14px; align-items:start; }
.sa-side{ border:1px solid #cbd5e1; border-radius:12px; padding:12px; background:#f8fafc; }
.sa-main{ display:grid; gap:10px; margin-top:10px; }
.sa-h{ font-weight:700; margin:4px 0 6px; color:#334155; }
.group{ border:1px dashed #cbd5e1; border-radius:10px; padding:10px; background:#fff; margin-bottom:10px; }
.group > .title{ font-weight:700; margin-bottom:6px; font-size:13px; color:#0f172a; }
.hint{ color:#64748b; font-size:12px; margin-top:4px; line-height:1.4; }
/* Inputs */
input[type="range"] { width: 100%; cursor: pointer; }
.val-disp { float:right; font-family:ui-monospace, monospace; font-size:12px; color:#2563eb; font-weight:bold; }
/* KPI Cards */
#kpi{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:6px; margin-bottom:10px; }
.k{ border:1px solid #cbd5e1; border-radius:10px; background:#fff; padding:6px 10px; display:flex; flex-direction:column; justify-content:center; }
.k b{ display:block; font-size:16px; margin-bottom:2px; color:#0f172a; }
.k div{ font-size:11px; color:#64748b; }
/* Coins Visualization */
.coin-grid { display:flex; flex-wrap:wrap; gap:4px; justify-content:center; padding:15px; background:#f1f5f9; border-radius:10px; min-height:60px; }
.coin { width:24px; height:24px; border-radius:50%; border:2px solid #94a3b8; background:#e2e8f0; transition: all 0.2s; font-size:10px; display:flex; align-items:center; justify-content:center; font-weight:bold; color:#fff; }
.coin.H { background:#3b82f6; border-color:#2563eb; } /* Head - Blue */
.coin.T { background:#ef4444; border-color:#dc2626; } /* Tail - Red */
/* Chart */
.chart-container { position:relative; height:250px; width:100%; border-bottom:1px solid #cbd5e1; margin-top:10px; display:flex; align-items:flex-end; gap:2px; padding:0 10px; }
.bar-group { flex:1; display:flex; flex-direction:column; justify-content:flex-end; position:relative; height:100%; }
.bar-bg { background:#e2e8f0; width:100%; position:absolute; bottom:0; z-index:1; opacity:0.3; border-top:2px dashed #94a3b8; transition:height 0.3s; }
.bar-fg { background:#3b82f6; width:70%; margin:0 auto; z-index:2; position:absolute; bottom:0; left:0; right:0; transition:height 0.1s; border-radius:2px 2px 0 0; }
.bar-label { text-align:center; font-size:10px; margin-top:4px; color:#64748b; position:absolute; bottom:-20px; width:100%; }
.bar-val { position:absolute; width:100%; text-align:center; font-size:10px; color:#0f172a; z-index:3; font-weight:bold; margin-bottom:2px; }
/* Buttons */
.btn { padding:6px 12px; border:1px solid #cbd5e1; border-radius:20px; background:#fff; cursor:pointer; font-size:13px; font-weight:500; transition:all 0.1s; }
.btn:hover { background:#f1f5f9; }
.btn:active { transform:scale(0.98); }
.btn.primary { background:#3b82f6; color:white; border-color:transparent; }
.btn.primary:hover { background:#2563eb; }
.btn.danger { color:#ef4444; border-color:#fca5a5; }
.ctrl-row { display:flex; gap:8px; margin-top:8px; }
</style>
<div class="sa-wrap">
<div class="sa-side">
<div class="sa-h">Binomial Simulator</div>
<div class="group">
<div class="title">Parameters <span id="n-disp" class="val-disp">n=10</span></div>
<input type="range" id="rg-n" min="2" max="30" step="1" value="10">
<div class="hint">จำนวนเหรียญ (Trials, n)</div>
</div>
<div class="group">
<div class="title">Probability <span id="p-disp" class="val-disp">p=0.50</span></div>
<input type="range" id="rg-p" min="0.05" max="0.95" step="0.05" value="0.5">
<div class="hint">โอกาสออกหัว (Probability of Success, p)</div>
</div>
<div class="group">
<div class="title">Controls</div>
<div class="ctrl-row">
<button class="btn primary" id="btn-run">▶ Run (1x)</button>
<button class="btn" id="btn-fast">⏩ Fast</button>
<button class="btn danger" id="btn-reset">↺ Reset</button>
</div>
<div class="hint" style="margin-top:8px">กด <b>Run</b> เพื่อโยนเหรียญทีละชุด หรือ <b>Fast</b> เพื่อจำลองรัวๆ</div>
</div>
<div class="group">
<div class="title">Info</div>
<div class="hint">
<b>Binomial Distribution:</b><br>
X ∼ B(n, p)<br><br>
กราฟแท่งสีเทาจางๆ คือค่าความน่าจะเป็นทางทฤษฎี<br>
กราฟแท่งสีน้ำเงิน คือผลที่ได้จากการทดลองจริง
</div>
</div>
</div>
<div class="sa-main">
<div id="kpi"></div>
<div class="group" style="margin:0">
<div class="title">Last Toss Result (ผลการโยนล่าสุด)</div>
<div id="coin-area" class="coin-grid"></div>
<div style="text-align:center; margin-top:5px; font-size:12px; color:#64748b">
<span style="color:#2563eb">● Head (H)</span> | <span style="color:#ef4444">● Tail (T)</span>
</div>
</div>
<div class="group" style="margin:0">
<div class="title">Distribution: Theoretical vs. Empirical</div>
<div id="chart-area" class="chart-container"></div>
<div style="height:20px"></div> </div>
</div>
</div>
</div>`;
// ===================== Selectors =====================
const rgN = box.querySelector("#rg-n");
const rgP = box.querySelector("#rg-p");
const dispN = box.querySelector("#n-disp");
const dispP = box.querySelector("#p-disp");
const btnRun = box.querySelector("#btn-run");
const btnFast = box.querySelector("#btn-fast");
const btnReset = box.querySelector("#btn-reset");
const coinArea = box.querySelector("#coin-area");
const chartArea = box.querySelector("#chart-area");
const kpiArea = box.querySelector("#kpi");
// ===================== Math Helpers =====================
function factorial(n) {
if (n === 0 || n === 1) return 1;
let r = 1; for (let i = 2; i <= n; i++) r *= i;
return r;
}
function nCr(n, r) {
return factorial(n) / (factorial(r) * factorial(n - r));
}
function binomialPMF(n, p, k) {
return nCr(n, k) * Math.pow(p, k) * Math.pow(1 - p, n - k);
}
// ===================== State =====================
let state = {
n: 10,
p: 0.5,
counts: new Array(11).fill(0), // counts[k] = how many times we got k heads
totalExperiments: 0,
sumX: 0, // for empirical mean
isRunning: false,
timer: null,
speed: 100 // ms
};
// ===================== Render Logic =====================
function updateKPI() {
const { n, p, totalExperiments, sumX } = state;
const empMean = totalExperiments > 0 ? (sumX / totalExperiments).toFixed(2) : "0.00";
const theoMean = (n * p).toFixed(2);
const theoSD = Math.sqrt(n * p * (1 - p)).toFixed(2);
kpiArea.innerHTML = `
<div class="k"><b>${totalExperiments.toLocaleString()}</b><div>Total Experiments</div></div>
<div class="k"><b>${empMean}</b><div>Empirical Mean<br>(ค่าเฉลี่ยจากการทดลอง)</div></div>
<div class="k" style="background:#f0f9ff; border-color:#bae6fd"><b>${theoMean}</b><div>Theoretical Mean<br>(ค่าเฉลี่ยทฤษฎี μ = np)</div></div>
<div class="k" style="background:#f0f9ff; border-color:#bae6fd"><b>${theoSD}</b><div>Theoretical SD<br>(σ = √[np(1-p)])</div></div>
`;
}
function renderCoins(headsCount, details) {
// details is array of booleans (true=head)
coinArea.innerHTML = "";
details.forEach(isHead => {
const el = document.createElement("div");
el.className = `coin ${isHead ? 'H' : 'T'}`;
el.textContent = isHead ? 'H' : 'T';
coinArea.appendChild(el);
});
}
function renderChart() {
chartArea.innerHTML = "";
const { n, p, counts, totalExperiments } = state;
// Find max probability to scale the Y-axis properly
// Check theoretical max
let maxProb = 0;
const theoProbs = [];
for(let k=0; k<=n; k++) {
const prob = binomialPMF(n, p, k);
theoProbs.push(prob);
if(prob > maxProb) maxProb = prob;
}
// Check empirical max
if (totalExperiments > 0) {
counts.forEach(c => {
const prob = c / totalExperiments;
if(prob > maxProb) maxProb = prob;
});
}
// Add some headroom
const yMax = Math.min(1, maxProb * 1.2);
for (let k = 0; k <= n; k++) {
const group = document.createElement("div");
group.className = "bar-group";
// Theoretical (Ghost Bar)
const tH = (theoProbs[k] / yMax) * 100;
const bg = document.createElement("div");
bg.className = "bar-bg";
bg.style.height = `${tH}%`;
bg.title = `Theoretical P(X=${k}): ${theoProbs[k].toFixed(3)}`;
// Empirical (Solid Bar)
const empProb = totalExperiments > 0 ? (counts[k] / totalExperiments) : 0;
const eH = (empProb / yMax) * 100;
const fg = document.createElement("div");
fg.className = "bar-fg";
fg.style.height = `${eH}%`;
// Label X-axis
const label = document.createElement("div");
label.className = "bar-label";
label.textContent = k;
// Show label only for some bars if n is large to prevent crowding
if (n > 20 && k % 2 !== 0) label.style.display = 'none';
// Value Label (on top of bar if space permits)
/* const val = document.createElement("div");
val.className = "bar-val";
val.style.bottom = `${eH}%`;
val.textContent = empProb > 0.01 ? empProb.toFixed(2) : "";
*/
group.appendChild(bg);
group.appendChild(fg);
group.appendChild(label);
chartArea.appendChild(group);
}
}
// ===================== Simulation Logic =====================
function resetData() {
state.counts = new Array(parseInt(state.n) + 1).fill(0);
state.totalExperiments = 0;
state.sumX = 0;
state.isRunning = false;
clearInterval(state.timer);
updateKPI();
renderChart();
// Clear coins
coinArea.innerHTML = `<div style="color:#94a3b8; align-self:center;">Press Run to toss coins</div>`;
}
function runExperiment(batchSize = 1) {
const { n, p } = state;
let lastDetails = [];
let lastHeads = 0;
for (let b = 0; b < batchSize; b++) {
let heads = 0;
let details = [];
for (let i = 0; i < n; i++) {
const isHead = Math.random() < p;
if (isHead) heads++;
details.push(isHead);
}
state.counts[heads]++;
state.totalExperiments++;
state.sumX += heads;
if (b === batchSize - 1) {
lastDetails = details;
lastHeads = heads;
}
}
return { lastHeads, lastDetails };
}
// ===================== Event Listeners =====================
rgN.addEventListener("input", (e) => {
const val = parseInt(e.target.value);
dispN.textContent = `n=${val}`;
state.n = val;
// When N changes, we must reset because the bins change
resetData();
});
rgP.addEventListener("input", (e) => {
const val = parseFloat(e.target.value);
dispP.textContent = `p=${val.toFixed(2)}`;
state.p = val;
// When P changes, data is invalid for the new distribution
resetData();
});
btnReset.addEventListener("click", resetData);
btnRun.addEventListener("click", () => {
// Single Run or Slow Loop
if (state.isRunning) {
// Stop
state.isRunning = false;
clearInterval(state.timer);
btnRun.textContent = "▶ Run (1x)";
} else {
// Run once immediately
const res = runExperiment(1);
updateKPI();
renderCoins(res.lastHeads, res.lastDetails);
renderChart();
// Then loop slowly
state.isRunning = true;
btnRun.textContent = "⏸ Stop";
state.timer = setInterval(() => {
const res = runExperiment(1);
updateKPI();
renderCoins(res.lastHeads, res.lastDetails);
renderChart();
}, 800); // Slow enough to see animation
}
});
btnFast.addEventListener("click", () => {
// Fast batch mode
const res = runExperiment(100); // Do 100 runs instantly
updateKPI();
renderCoins(res.lastHeads, res.lastDetails); // Show result of the last one
renderChart();
});
// ===================== Init =====================
resetData();
return box;
})();การแจกแจงแบบทวินาม เป็น การแจกแจงความน่าจะเป็นแบบไม่ต่อเนื่อง ซึ่งอธิบายจำนวน ความสำเร็จ ที่เกิดขึ้นจากจำนวนการทดลองแบบ เบอร์นูลลีที่เป็นอิสระ (Independent Bernoulli Trials) โดยแต่ละการทดลองมีเพียงสองผลลัพธ์เท่านั้นคือ ความสำเร็จ หรือ ความล้มเหลว
คำจำกัดความ (Definition):
หากตัวแปรสุ่ม \(X \sim \text{Binomial}(n, p)\) ดังนั้น
ฟังก์ชันมวลความน่าจะเป็น (Probability Mass Function; PMF):
\[ P(X = k) = \binom{n}{k} p^k (1 - p)^{n - k} \]
\(\binom{n}{k} = \dfrac{n!}{k!(n - k)!}\)
\(k\): จำนวนครั้งของความสำเร็จ
ค่าเฉลี่ยและความแปรปรวน (Mean and Variance):
ค่าเฉลี่ย (Expected Value): \(\mathbb{E}[X] = np\)
ความแปรปรวน (Variance): \(\text{Var}(X) = np(1 - p)\)
ตัวอย่างในชีวิตจริง (Examples in Real Life):
| สถานการณ์ | การทดลอง (Trial) | ความสำเร็จ (Success) |
|---|---|---|
| การโยนเหรียญ 10 ครั้ง | แต่ละครั้งที่โยน | ได้หัว (Head) |
| การสำรวจความคิดเห็น 20 คน | แต่ละคนที่ตอบ | ชอบผลิตภัณฑ์ (Likes product) |
| การตรวจสอบคุณภาพสินค้า 100 ชิ้น | แต่ละชิ้นสินค้า | ไม่ชำรุด (Not defective) |
เมื่อใดควรใช้การแจกแจงแบบทวินาม (When to Use Binomial Distribution):
ตัวอย่างที่ 1: การโยนเหรียญที่ยุติธรรม 5 ครั้ง
คุณโยนเหรียญที่ ยุติธรรม 5 ครั้ง จงหาความน่าจะเป็นที่จะได้ หัว 3 ครั้งพอดี
ให้ \(X \sim \text{Binomial}(n = 5, p = 0.5)\)
วิธีทำทีละขั้นตอน (Step-by-step): \(n = 5\), \(k = 3\), \(p = 0.5\)
\[ \begin{aligned} P(X = 3) &= \binom{5}{3}(0.5)^3(1 - 0.5)^{5 - 3}\\ &= \frac{5!}{3!2!}(0.5)^3(0.5)^2\\ &= 10 \times 0.125 \times 0.25 = 0.3125 \end{aligned} \]
คำตอบ (Answer): \(P(X = 3) = 0.3125\)
ตัวอย่างที่ 2: สินค้าที่ชำรุดในชุดการผลิต
เครื่องจักรผลิตสินค้าที่มีอัตราของเสีย 10%
หากตรวจสอบสินค้า 8 ชิ้น จงหาความน่าจะเป็นที่มี ของเสีย 2 ชิ้นพอดี
ให้ \(X \sim \text{Binomial}(n = 8, p = 0.1)\)
วิธีทำทีละขั้นตอน (Step-by-step):
\[ \begin{aligned} P(X = 2) &= \binom{8}{2}(0.1)^2(0.9)^6\\ &= \frac{8!}{2!6!}(0.01)(0.531441)\\ &= 28 \times 0.01 \times 0.531441 = 0.1488 \end{aligned} \]
คำตอบ (Answer): \(P(X = 2) \approx 0.1488\)
ตัวอย่างที่ 3: การคลิกโฆษณาออนไลน์
ผู้ใช้แต่ละคนที่เห็นโฆษณามีโอกาสคลิกเท่ากับ 25%
จากผู้ชมทั้งหมด 12 คน จงหาความน่าจะเป็นที่มี 4 คนพอดี ที่คลิกโฆษณา
ให้ \(X \sim \text{Binomial}(n = 12, p = 0.25)\)
วิธีทำทีละขั้นตอน (Step-by-step):
\[ \begin{aligned} P(X = 4) &= \binom{12}{4}(0.25)^4(0.75)^8\\ &= 495 \times 0.00390625 \times 0.100112915\\ &= 495 \times 0.000390625 \approx 0.1937 \end{aligned} \]
คำตอบ (Answer): \(P(X = 4) \approx 0.1937\)
viewof coinWalk = (function() {
const uid = "rw_" + Math.random().toString(36).substr(2, 9);
const width = 600;
const height = 400;
const container = html`<div style="font-family:'Sarabun', sans-serif; display:flex; flex-direction:column; align-items:center; gap:15px; background:white; padding:25px; border-radius:16px; border:1px solid #e2e8f0; box-shadow:0 4px 15px rgba(0,0,0,0.05); max-width:700px; margin:0 auto;">
<div style="position:relative;">
<canvas width="${width}" height="${height}" style="background:#f8fafc; border-radius:8px; border:1px solid #cbd5e1;"></canvas>
<div style="position:absolute; top:50%; right:10px; transform:translateY(-50%); font-size:12px; color:#94a3b8; pointer-events:none;">0 (Start)</div>
</div>
<div style="display:flex; gap:20px; width:100%; justify-content:center; flex-wrap:wrap;">
<div style="display:flex; align-items:center; gap:8px;">
<span style="font-size:24px;">🤴</span>
<div style="display:flex; flex-direction:column;">
<span style="font-size:12px; color:#64748b;">หัว (Heads)</span>
<b id="${uid}-h" style="color:#059669; font-size:18px;">0</b>
</div>
</div>
<div style="width:1px; background:#e2e8f0;"></div>
<div style="display:flex; align-items:center; gap:8px;">
<span style="font-size:24px;">🦅</span>
<div style="display:flex; flex-direction:column;">
<span style="font-size:12px; color:#64748b;">ก้อย (Tails)</span>
<b id="${uid}-t" style="color:#dc2626; font-size:18px;">0</b>
</div>
</div>
<div style="width:1px; background:#e2e8f0;"></div>
<div style="display:flex; align-items:center; gap:8px;">
<span style="font-size:24px;">📊</span>
<div style="display:flex; flex-direction:column;">
<span style="font-size:12px; color:#64748b;">ผลต่าง (Gap)</span>
<b id="${uid}-gap" style="color:#334155; font-size:18px;">0</b>
</div>
</div>
</div>
<div style="display:flex; gap:10px; flex-wrap:wrap; justify-content:center;">
<button id="${uid}-btn-1" style="background:#3b82f6; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer;">โยน 1 ครั้ง</button>
<button id="${uid}-btn-auto" style="background:#f59e0b; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer; min-width:100px;">▶ Auto Run</button>
<button id="${uid}-btn-reset" style="background:#ef4444; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer;">↺ เริ่มใหม่</button>
</div>
<div style="font-size:13px; color:#64748b; background:#f1f5f9; padding:12px; border-radius:8px; text-align:center; line-height:1.5;">
กราฟแสดงผลรวมสะสม: <span style="color:#059669; font-weight:bold;">ขึ้น (+1)</span> หรือ <span style="color:#dc2626; font-weight:bold;">ลง (-1)</span><br>
สังเกตไหมว่า แม้โอกาสจะเป็น 50/50 แต่กราฟมักจะไม่กลับมาที่ 0 บ่อยนัก (Random Walk Phenomenon)
</div>
</div>`;
const canvas = container.querySelector("canvas");
const ctx = canvas.getContext("2d");
const elH = container.querySelector(`#${uid}-h`);
const elT = container.querySelector(`#${uid}-t`);
const elGap = container.querySelector(`#${uid}-gap`);
const btn1 = container.querySelector(`#${uid}-btn-1`);
const btnAuto = container.querySelector(`#${uid}-btn-auto`);
const btnReset = container.querySelector(`#${uid}-btn-reset`);
// State
let history = [0]; // Start at 0
let heads = 0;
let tails = 0;
let isRunning = false;
let autoInterval;
function updateDisplay() {
elH.innerText = heads.toLocaleString();
elT.innerText = tails.toLocaleString();
const gap = heads - tails;
elGap.innerText = (gap > 0 ? "+" : "") + gap;
elGap.style.color = gap > 0 ? "#059669" : (gap < 0 ? "#dc2626" : "#334155");
drawGraph();
}
function drawGraph() {
ctx.clearRect(0, 0, width, height);
// 1. Calculate Scales
const totalPoints = history.length;
// Find min/max to auto-scale Y axis
let maxVal = 0;
for(let v of history) if(Math.abs(v) > maxVal) maxVal = Math.abs(v);
if (maxVal < 10) maxVal = 10; // Min vertical range
const margin = 20;
const chartW = width;
const chartH = height;
const centerY = height / 2;
// Scale Factors
const stepX = chartW / (totalPoints > 100 ? totalPoints : 100);
// If points > width, we might need to compress, but here let's strictly scale to fit
// Actually simpler: X maps 0..totalPoints to 0..width
const scaleY = (chartH / 2 - margin) / maxVal;
// 2. Draw Center Line (Zero)
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(width, centerY);
ctx.strokeStyle = "#cbd5e1";
ctx.lineWidth = 2;
ctx.stroke();
// 3. Draw Path
ctx.beginPath();
ctx.moveTo(0, centerY); // Start at 0
for (let i = 1; i < totalPoints; i++) {
const x = (i / (totalPoints - 1)) * width;
// Map value to Y. Value 0 = centerY. Value + = Up.
const y = centerY - (history[i] * scaleY);
ctx.lineTo(x, y);
}
ctx.lineJoin = "round";
ctx.lineWidth = 2;
// Dynamic color based on current winning status
const currentVal = history[history.length - 1];
ctx.strokeStyle = currentVal >= 0 ? "#059669" : "#dc2626";
ctx.stroke();
// 4. Draw Current Point (Dot)
const lastX = width;
const lastY = centerY - (currentVal * scaleY);
ctx.beginPath();
ctx.arc(lastX, lastY, 4, 0, Math.PI*2);
ctx.fillStyle = ctx.strokeStyle;
ctx.fill();
// Draw Current Value Text near dot
ctx.fillStyle = "#334155";
ctx.font = "bold 12px sans-serif";
ctx.textAlign = "right";
ctx.fillText(currentVal, lastX - 10, lastY - 10);
}
function flip(count) {
for(let i=0; i<count; i++) {
const isHead = Math.random() < 0.5;
if (isHead) heads++; else tails++;
const lastVal = history[history.length - 1];
history.push(lastVal + (isHead ? 1 : -1));
}
updateDisplay();
}
// --- Listeners ---
btn1.onclick = () => flip(1);
btnAuto.onclick = () => {
if (isRunning) {
clearInterval(autoInterval);
isRunning = false;
btnAuto.innerText = "▶ Auto Run";
btnAuto.style.background = "#f59e0b";
} else {
isRunning = true;
btnAuto.innerText = "⏸ Stop";
btnAuto.style.background = "#ef4444";
autoInterval = setInterval(() => {
flip(1);
}, 30); // Speed
}
};
btnReset.onclick = () => {
if(isRunning) btnAuto.click();
history = [0];
heads = 0;
tails = 0;
updateDisplay();
};
// Init
updateDisplay();
return container;
})()Special Cases & Distributions
(async () => {
const uid = "bd_clear_btn_" + Math.random().toString(36).substr(2, 9);
// --- 🧑🏫 คลังไอคอนหน้าคน (90+ แบบ) ---
const iconBank = [
"🧑", "👩", "👨", "🧒", "👦", "👧", "👶", "🧓", "👴", "👵",
"👱", "👱♀️", "🧔", "🕴️", "💃", "🕺", "🏃", "🏃♀️", "🧘", "🧘♀️",
"👮", "👮♀️", "👷", "👷♀️", "💂", "💂♀️", "🕵️", "🕵️♀️", "👩⚕️", "👨⚕️",
"👩🌾", "👨🌾", "👩🍳", "👨🍳", "👩🎓", "👨🎓", "👩🎤", "👨🎤", "👩🏫", "👨🏫",
"👩🏭", "👨🏭", "👩💻", "👨💻", "👩💼", "👨💼", "👩🔧", "👨🔧", "👩🔬", "👨🔬",
"👩🎨", "👨🎨", "👩🚒", "👨🚒", "👩✈️", "👨✈️", "👩🚀", "👨🚀", "👩⚖️", "👨⚖️",
"🤴", "👸", "👳", "👳♀️", "👲", "🧕", "🤵", "👰", "🤰", "🤱",
"🦸", "🦸♀️", "🦹", "🦹♀️", "🧙", "🧙♀️", "🧚", "🧚♀️", "🧛", "🧛♀️",
"🧜", "🧜♀️", "🧝", "🧝♀️", "🧞", "🧞♀️", "🧟", "🧟♀️", "👽", "🤖"
];
const box = html`<div style="width:100%; display:flex; justify-content:center; font-family:'Sarabun', system-ui, sans-serif; color:#334155;">
<style>
.bd-wrapper {
width: 100%; max-width: 800px;
background: white; padding: 25px; border-radius: 16px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08); border: 1px solid #e2e8f0;
display: flex; flex-direction: column; gap: 20px;
}
/* Header & Controls */
.bd-header { text-align:center; }
.bd-title { margin:0; color:#db2777; font-size: 24px; margin-bottom: 10px; }
/* Control Row */
.ctrl-row {
display:flex; justify-content:center; align-items:center; gap: 20px;
background: #fdf2f8; padding: 12px 20px; border-radius: 50px;
border: 1px solid #fbcfe8; flex-wrap: wrap;
}
.input-group { display:flex; align-items:center; gap:10px; }
input[type=range] { cursor: pointer; accent-color: #db2777; width: 160px; }
/* 🔥 ปรับแก้ปุ่มให้ชัดเจนขึ้น (สีน้ำเงินตัดชมพู) */
.btn-new {
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); /* ไล่สีน้ำเงิน */
color: white; border: 1px solid #1d4ed8; padding: 8px 24px;
border-radius: 50px; font-weight: bold; cursor: pointer; font-size: 15px;
box-shadow: 0 4px 6px rgba(37, 99, 235, 0.3); /* เงาสีน้ำเงิน */
transition: all 0.2s;
display: flex; align-items: center; gap: 8px;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.btn-new:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(37, 99, 235, 0.4);
filter: brightness(1.1);
}
.btn-new:active { transform: translateY(0); }
/* Stats Area */
.stats-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 5px;
}
.stat-card {
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px;
padding: 10px; text-align: center;
}
.stat-label { font-size: 13px; color: #64748b; }
.stat-val { font-size: 20px; font-weight: 800; color: #1e293b; }
/* People Grid */
.people-grid {
display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
min-height: 200px; align-content: flex-start;
}
.person {
width: 48px; height: 58px;
background: white; border: 2px solid #cbd5e1; border-radius: 10px;
display: flex; flex-direction: column; align-items: center; justify-content: center;
font-size: 10px; position: relative; transition: all 0.3s;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
.person-icon { font-size: 24px; margin-bottom: -3px; line-height: 1; }
.person-date { font-weight: bold; color: #475569; font-size: 10px; }
/* Match Highlight */
.person.match {
background: #fce7f3; border-color: #db2777; transform: scale(1.15); z-index: 10;
box-shadow: 0 6px 15px rgba(219, 39, 119, 0.25);
}
.person.match .person-date { color: #db2777; }
/* Footer Explanation */
.footer-expl {
text-align:center; font-size:13px; color:#64748b;
background:#f1f5f9; padding:12px; border-radius:8px; line-height: 1.6;
}
</style>
<div class="bd-wrapper">
<div class="bd-header">
<div class="ctrl-row">
<div class="input-group">
<label style="font-size:14px; font-weight:bold; color:#be185d;">จำนวนคน (N):</label>
<input type="range" id="${uid}-slider" min="2" max="100" value="23">
<span id="${uid}-n-disp" style="font-weight:bold; font-size:18px; color:#be185d; width:30px;">23</span>
</div>
<div style="width:1px; height:20px; background:#fbcfe8; margin:0 5px;"></div>
<button class="btn-new" id="${uid}-btn">
🎲 สุ่มใหม่
</button>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">โอกาสทางทฤษฎี (Theory)</div>
<div id="${uid}-prob" class="stat-val" style="color:#db2777">50.7%</div>
</div>
<div class="stat-card">
<div class="stat-label">ผลลัพธ์รอบนี้ (Result)</div>
<div id="${uid}-result" class="stat-val">...</div>
</div>
</div>
<div class="people-grid" id="${uid}-grid"></div>
<div class="footer-expl">
💡 <b>รู้หรือไม่?</b> ถ้ามีคนในห้องแค่ <b>23 คน</b> โอกาสเจอคู่เกิดวันเดียวกันสูงถึง <b>50%</b> <br>
แต่ถ้าเพิ่มเป็น <b>70 คน</b> โอกาสจะพุ่งไปถึง <b>99.9%</b>! (ลองเลื่อน Slider ดูครับ)
</div>
</div>
</div>`;
// --- Logic ---
const $ = (s) => box.querySelector(`#${uid}-${s}`);
const months = ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."];
function getDayOfYear(d) {
let m = 0;
let day = d;
const daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31];
while (day > daysInMonth[m]) {
day -= daysInMonth[m];
m++;
}
return { d: day, m: months[m], id: `${day}-${m}` };
}
function calculateTheory(n) {
let nonMatchProb = 1;
for (let i = 0; i < n; i++) {
nonMatchProb *= (365 - i) / 365;
}
return ((1 - nonMatchProb) * 100).toFixed(1) + "%";
}
function runSimulation() {
const n = parseInt($("slider").value);
$(`n-disp`).textContent = n;
$(`prob`).textContent = calculateTheory(n);
const grid = $(`grid`);
grid.innerHTML = "";
const seen = {};
const matches = new Set();
// สร้างคน
for(let i=0; i<n; i++) {
const dayNum = Math.floor(Math.random() * 365) + 1;
const dateObj = getDayOfYear(dayNum);
if (seen[dateObj.id]) {
matches.add(dateObj.id);
} else {
seen[dateObj.id] = true;
}
const icon = iconBank[i % iconBank.length];
const el = document.createElement("div");
el.className = "person";
el.dataset.dateId = dateObj.id;
el.innerHTML = `
<div class="person-icon">${icon}</div>
<div class="person-date">${dateObj.d} ${dateObj.m}</div>
`;
grid.appendChild(el);
}
// Highlight Matches
const allPeople = grid.querySelectorAll(".person");
allPeople.forEach(p => {
if (matches.has(p.dataset.dateId)) {
p.classList.add("match");
}
});
// Update Result Text
const resEl = $(`result`);
if (matches.size > 0) {
resEl.innerHTML = `<span style="color:#10b981">เจอคู่เหมือน! ✅</span>`;
} else {
resEl.innerHTML = `<span style="color:#94a3b8">ไม่เจอ... ❌</span>`;
}
}
// Events
$(`slider`).addEventListener("input", runSimulation);
$(`btn`).addEventListener("click", runSimulation);
// Init
runSimulation();
return box;
})();การแจกแจงแบบปัวซอง เป็น การแจกแจงความน่าจะเป็นแบบไม่ต่อเนื่อง ที่ใช้จำลองจำนวนของ เหตุการณ์ (events) ที่เกิดขึ้นภายใน ช่วงเวลาหรือพื้นที่คงที่ ภายใต้สมมติฐานดังต่อไปนี้:
คำจำกัดความ (Definition):
หากตัวแปรสุ่ม \(X \sim \text{Poisson}(\lambda)\)
จะอธิบาย ความน่าจะเป็นของการเกิดเหตุการณ์จำนวน \(k\) ครั้งพอดี ภายในช่วงเวลาหรือพื้นที่ที่กำหนด
ฟังก์ชันมวลความน่าจะเป็น (Probability Mass Function; PMF): \[ P(X = k) = \frac{e^{-\lambda} \lambda^k}{k!} \]
โดยที่:
ค่าเฉลี่ยและความแปรปรวน (Mean and Variance):
เมื่อใดควรใช้การแจกแจงแบบปัวซอง (When to Use Poisson Distribution):
ใช้ในการนับจำนวน เหตุการณ์ที่เกิดขึ้นไม่บ่อย (rare events) ในช่วงเวลา หรือในพื้นที่
เหตุการณ์เกิดขึ้นแบบสุ่ม (random) และ เป็นอิสระต่อกัน (independent)
อัตราการเกิดเหตุการณ์ (rate) มีค่า คงที่ตลอดเวลา
ตัวอย่างในชีวิตจริง (Real-life Examples):
| สถานการณ์ | ตัวแปรแบบปัวซอง (Poisson variable) |
|---|---|
| จำนวนสายโทรศัพท์ที่เข้าศูนย์บริการต่อชั่วโมง | จำนวนสายโทรศัพท์ (Number of calls) |
| จำนวนคำผิดในแต่ละหน้าของหนังสือ | จำนวนคำผิด (Number of typos) |
| จำนวนผู้ป่วยที่มาถึงห้องฉุกเฉินในแต่ละคืน | จำนวนผู้ป่วย (Number of patients) |
| จำนวนอีเมลที่ได้รับในแต่ละวัน | จำนวนอีเมล (Number of emails) |
ตัวอย่างที่ 1: ศูนย์บริการลูกค้า (Call Center)
ศูนย์บริการลูกค้าได้รับสายโทรศัพท์ เฉลี่ย 4 สายต่อชั่วโมง จงหาความน่าจะเป็นดังต่อไปนี้:
a) \(P(X = 2)\): มีสายโทรศัพท์เข้า 2 สายพอดี
\[ P(X = 2) = \frac{e^{-4} \cdot 4^2}{2!} = \frac{e^{-4} \cdot 16}{2} = 8 \cdot e^{-4} \approx 8 \cdot 0.0183 = 0.1465 \]
b) \(P(X \leq 2)\): มีสายโทรศัพท์เข้า ไม่เกิน 2 สาย
\[P(X \leq 2) = P(0) + P(1) + P(2)\]
\[ \begin{aligned} P(0) &= \frac{e^{-4} \cdot 4^0}{0!} = e^{-4} = 0.0183 \\ P(1) &= \frac{e^{-4} \cdot 4^1}{1!} = 4 \cdot e^{-4} = 0.0733 \\ P(2) &= 0.1465 \ \text{(จากข้อ a)} \\ P(X \leq 2) &= 0.0183 + 0.0733 + 0.1465 = 0.2381 \end{aligned} \]
c) \(P(X \geq 3)\): มีสายโทรศัพท์เข้า ตั้งแต่ 3 สายขึ้นไป
\[ P(X \geq 3) = 1 - P(X \leq 2) \]
\[ P(X \geq 3) = 1 - 0.2381 = 0.7619 \]
ตัวอย่างที่ 2: ห้องฉุกเฉินในโรงพยาบาล (Hospital ER)
โดยเฉลี่ยมี ผู้ป่วยมาถึงห้องฉุกเฉิน 3 คนต่อคืน จงหาความน่าจะเป็นดังต่อไปนี้:
a) \(P(X = 5)\): มีผู้ป่วยมา 5 คนพอดี
\[ P(X = 5) = \frac{e^{-3} \cdot 3^5}{5!} = \frac{e^{-3} \cdot 243}{120} \approx 0.0498 \cdot 2.025 = 0.1008 \]
b) \(P(X \leq 5)\): มีผู้ป่วยมา ไม่เกิน 5 คน
\[P(X \leq 5) = \sum_{k=0}^{5} P(k)=P(0)+P(1)+P(2)+P(3)+P(4)+P(5)\]
\[ \begin{aligned} P(0) &= e^{-3} = 0.0498 \\ P(1) &= 3 \cdot e^{-3} = 0.1494 \\ P(2) &= \frac{9}{2} e^{-3} = 0.2240 \\ P(3) &= \frac{27}{6} e^{-3} = 0.2240 \\ P(4) &= \frac{81}{24} e^{-3} = 0.1680 \\ P(5) &= 0.1008 \\ P(X \leq 5) &= 0.0498 + 0.1494 + 0.2240\\ &~~~+ 0.2240 + 0.1680 + 0.1008 = 0.9160 \end{aligned} \]
c) \(P(X \geq 2)\): มีผู้ป่วยมา อย่างน้อย 2 คน
\[ P(X \geq 2) = 1 - P(0) - P(1) \]
\[ P(X \geq 2) = 1 - (0.0498 + 0.1494) = 1 - 0.1992 = 0.8008 \]
ในโปรแกรม Jamovi คุณสามารถติดตั้งและใช้งานโมดูลภายนอกที่ชื่อว่า distrACTION เพื่อคำนวณค่าความน่าจะเป็นสำหรับทั้งการแจกแจงแบบ Binomial และ Poisson ได้
สมบัติของการแจกแจงความน่าจะเป็นแบบต่อเนื่อง (Properties of a Continuous Probability Distribution)
ให้ \(f(x)\) เป็น ฟังก์ชันความหนาแน่นของความน่าจะเป็น (Probability Density Function; PDF) ซึ่งต้องเป็นไปตามเงื่อนไขดังต่อไปนี้:
ความน่าจะเป็นที่ตัวแปรสุ่ม \(X\) จะอยู่ในช่วง \(a \leq X \leq b\)
คำนวณได้ดังนี้:
\[ \begin{aligned} P(a < X < b) &= P(a \leq X < b) \\ &= P(a < X \leq b) \\ &= P(a \leq X \leq b) = \int_{a}^{b} f(x) \, dx \end{aligned} \]
การแจกแจงปกติ (Normal Distribution)
\[ f(x) = \frac{1}{\sigma \sqrt{2\pi}} e^{-\frac{(x - \mu)^2}{2\sigma^2}}, \quad \mu \in \mathbb{R},\ \sigma^2 > 0,\ x \in \mathbb{R} \]
การแจกแจงปกติ (Normal Distribution):
ใช้กันอย่างแพร่หลายในทางสถิติ
การแจกแจงแบบสม่ำเสมอ (Uniform Distribution):
ทุกค่าภายในช่วงที่กำหนดมีความน่าจะเป็นเท่ากัน
การแจกแจงแบบเอ็กซ์โปเนนเชียล (Exponential Distribution):
มักใช้จำลองเวลาการรอคอยของเหตุการณ์ (waiting times)
viewof monteCarloPi = (function() {
const uid = "mc_unicode_" + Math.random().toString(36).substr(2, 9);
const size = 400; // Canvas size
const r = size / 2;
const container = html`<div style="font-family:'Sarabun', sans-serif; display:flex; flex-direction:column; align-items:center; gap:15px; background:white; padding:25px; border-radius:16px; border:1px solid #e2e8f0; box-shadow:0 4px 15px rgba(0,0,0,0.05); max-width:700px; margin:0 auto;">
<div style="position:relative;">
<canvas width="${size}" height="${size}" style="background:white; border-radius:4px; border:1px solid #cbd5e1; cursor:crosshair;"></canvas>
<div style="position:absolute; top:0; left:0; width:${size}px; height:${size}px; pointer-events:none; border:2px solid #334155; box-sizing:border-box;"></div>
<div style="position:absolute; top:0; left:0; width:${size}px; height:${size}px; border-radius:50%; border:2px solid #be185d; pointer-events:none; box-sizing:border-box; opacity:0.5;"></div>
</div>
<div style="display:flex; gap:10px; flex-wrap:wrap; justify-content:center; align-items:center; background:#fdf2f8; padding:10px 20px; border-radius:50px; border:1px solid #fbcfe8;">
<button id="${uid}-btn-1" style="background:#be185d; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer;">+1 เป้า</button>
<button id="${uid}-btn-100" style="background:#db2777; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer;">+100</button>
<button id="${uid}-btn-auto" style="background:#be185d; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer; min-width:80px;">▶ Auto</button>
<button id="${uid}-btn-reset" style="background:#9ca3af; color:white; border:none; padding:8px 20px; border-radius:50px; font-weight:bold; cursor:pointer;">↺ Reset</button>
</div>
<div style="display:grid; grid-template-columns: 1fr 1fr 1fr; gap:15px; width:100%; text-align:center;">
<div style="background:#f8fafc; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
<div style="font-size:12px; color:#64748b;">Total Points</div>
<div id="${uid}-total" style="font-size:20px; font-weight:bold; color:#334155;">0</div>
</div>
<div style="background:#f8fafc; padding:10px; border-radius:8px; border:1px solid #e2e8f0;">
<div style="font-size:12px; color:#64748b;">Inside Circle</div>
<div id="${uid}-inside" style="font-size:20px; font-weight:bold; color:#db2777;">0</div>
</div>
<div style="background:#f0fdf4; padding:10px; border-radius:8px; border:1px solid #bbf7d0;">
<div style="font-size:12px; color:#166534;">Estimated π</div>
<div id="${uid}-pi" style="font-size:24px; font-weight:bold; color:#15803d;">-</div>
<div style="font-size:11px; color:#166534;">(Real: 3.14159...)</div>
</div>
</div>
<div style="font-size:13px; color:#64748b; background:#f1f5f9; padding:15px; border-radius:8px; width:100%; line-height:1.6;">
<b>สูตรคำนวณ:</b> π ≈ 4 × (จุดในวงกลม ÷ จุดทั้งหมด) <br>
<i>(ที่มา: พื้นที่วงกลม πr² หารด้วย พื้นที่สี่เหลี่ยม (2r)² = π/4)</i>
</div>
</div>`;
const canvas = container.querySelector("canvas");
const ctx = canvas.getContext("2d");
const elTotal = container.querySelector(`#${uid}-total`);
const elInside = container.querySelector(`#${uid}-inside`);
const elPi = container.querySelector(`#${uid}-pi`);
const btn1 = container.querySelector(`#${uid}-btn-1`);
const btn100 = container.querySelector(`#${uid}-btn-100`);
const btnAuto = container.querySelector(`#${uid}-btn-auto`);
const btnReset = container.querySelector(`#${uid}-btn-reset`);
let total = 0;
let inside = 0;
let isRunning = false;
let autoInterval;
// Clear Canvas initially
ctx.fillStyle = "white";
ctx.fillRect(0,0,size,size);
function addPoints(count) {
for(let i=0; i<count; i++) {
const x = Math.random() * size;
const y = Math.random() * size;
const dx = x - r;
const dy = y - r;
const distSq = dx*dx + dy*dy;
const isInside = distSq <= (r*r);
if (isInside) inside++;
total++;
ctx.fillStyle = isInside ? "#db2777" : "#cbd5e1";
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI*2);
ctx.fill();
}
updateStats();
}
function updateStats() {
elTotal.innerText = total.toLocaleString();
elInside.innerText = inside.toLocaleString();
if (total > 0) {
const piEst = 4 * (inside / total);
elPi.innerText = piEst.toFixed(5);
} else {
elPi.innerText = "-";
}
}
// --- Listeners ---
btn1.onclick = () => addPoints(1);
btn100.onclick = () => addPoints(100);
btnAuto.onclick = () => {
if (isRunning) {
clearInterval(autoInterval);
isRunning = false;
btnAuto.innerText = "▶ Auto";
btnAuto.style.background = "#be185d";
} else {
isRunning = true;
btnAuto.innerText = "⏸ Stop";
btnAuto.style.background = "#fbbf24";
autoInterval = setInterval(() => {
addPoints(50);
}, 30);
}
};
btnReset.onclick = () => {
if(isRunning) btnAuto.click();
total = 0;
inside = 0;
ctx.clearRect(0,0,size,size);
updateStats();
};
return container;
})()viewof gfGame = (function() {
const uid = "gf_" + Math.random().toString(36).substr(2, 9);
const container = html`<div style="font-family:'Sarabun', sans-serif; display:flex; flex-direction:column; align-items:center; gap:20px; background:white; padding:25px; border-radius:20px; border:2px solid #fbcfe8; box-shadow:0 10px 25px rgba(219, 39, 119, 0.15); max-width:600px; margin:0 auto;">
<div style="font-size:14px; color:#64748b; text-align:center;">
ภารกิจ: ตามหา <b>"คนที่ใช่ที่สุด"</b> (Perfect Match) เพียงคนเดียว!<br>
<span style="font-size:12px; color:#ef4444;">*กฎ: ถ้า "เท" แล้ว จะกลับมาจีบใหม่ไม่ได้นะจ๊ะ</span>
</div>
<div style="display:flex; gap:15px; align-items:center; justify-content:center; min-height:200px;">
<div style="display:flex; flex-direction:column; align-items:center; gap:5px; margin-right:15px;">
<div style="font-size:12px; color:#94a3b8;">คนคุยลำดับที่</div>
<div id="${uid}-progress" style="font-size:28px; font-weight:bold; color:#f472b6;">1/10</div>
<div style="font-size:40px;">💃</div>
</div>
<div id="${uid}-card" style="width:160px; height:200px; background:#fff1f2; border:3px solid #be185d; border-radius:16px; display:flex; flex-direction:column; align-items:center; justify-content:center; box-shadow:0 8px 20px rgba(190, 24, 93, 0.2); transition:transform 0.3s; position:relative;">
<div style="font-size:13px; color:#be185d; font-weight:bold; margin-bottom:5px; text-transform:uppercase;">Love Score</div>
<div id="${uid}-score" style="font-size:56px; font-weight:800; color:#831843;">?</div>
<div style="font-size:24px; position:absolute; bottom:15px;">❤️</div>
<div id="${uid}-rank" style="position:absolute; top:-10px; right:-10px; background:#f59e0b; color:white; font-size:10px; padding:4px 8px; border-radius:10px; font-weight:bold; visibility:hidden; box-shadow:0 2px 5px rgba(0,0,0,0.2);">
ดีสุดที่เคยเจอ! ✨
</div>
</div>
</div>
<div id="${uid}-controls" style="display:flex; gap:15px; width:100%; justify-content:center;">
<button id="${uid}-btn-pass" style="flex:1; max-width:150px; background:#e2e8f0; color:#475569; border:none; padding:12px; border-radius:50px; font-weight:bold; cursor:pointer; font-size:16px; transition:0.2s; display:flex; align-items:center; justify-content:center; gap:5px;">
💔 เท/ผ่าน
</button>
<button id="${uid}-btn-hire" style="flex:1; max-width:150px; background:linear-gradient(135deg, #ec4899 0%, #db2777 100%); color:white; border:none; padding:12px; border-radius:50px; font-weight:bold; cursor:pointer; font-size:16px; box-shadow:0 4px 10px rgba(219, 39, 119, 0.4); transition:0.2s; display:flex; align-items:center; justify-content:center; gap:5px;">
💍 ขอเป็นแฟน
</button>
</div>
<div id="${uid}-msg" style="min-height:40px; text-align:center; font-weight:bold; color:#334155;">
คนนี้เป็นไง? จะคบ หรือ จะคุยคนถัดไป?
</div>
<div style="width:100%; background:#fdf2f8; padding:12px; border-radius:12px; border:1px solid #fbcfe8;">
<div style="font-size:12px; color:#db2777; margin-bottom:5px;">แฟนเก่าที่ผ่านมา (ย้อนกลับไม่ได้):</div>
<div id="${uid}-history" style="display:flex; gap:6px; flex-wrap:wrap; font-size:12px; font-family:monospace; min-height:20px; color:#9d174d;">
-
</div>
</div>
<button id="${uid}-btn-restart" style="background:#10b981; color:white; border:none; padding:10px 30px; border-radius:50px; font-weight:bold; cursor:pointer; display:none; font-size:16px; box-shadow:0 4px 10px rgba(16, 185, 129, 0.3);">
🔄 เริ่มจีบใหม่ (Move On)
</button>
<div style="font-size:13px; color:#475569; background:#fff; padding:12px; border-radius:8px; border:1px dashed #cbd5e1; width:100%; text-align:center;">
<b>สถิติบอกว่า:</b> อย่าเพิ่งรีบมีแฟนคนแรกๆ! <br>
ให้ดูใจไปก่อนสัก <b>3-4 คน (37%)</b> เพื่อตั้งมาตรฐาน แล้วหลังจากนั้นใครดีกว่ามาตรฐานเดิม <b>ให้ขอแต่งงานเลย!</b> 👰♀️
</div>
</div>`;
// --- Logic ---
const $ = (s) => container.querySelector(`#${uid}-${s}`);
const TOTAL_CANDIDATES = 10;
let candidates = [];
let currentIndex = 0;
let maxSoFar = 0;
let trueMax = 0;
let gameState = "PLAY";
function initGame() {
const pool = Array.from({length: 100}, (_, i) => i + 1);
for (let i = pool.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[pool[i], pool[j]] = [pool[j], pool[i]];
}
candidates = pool.slice(0, TOTAL_CANDIDATES);
trueMax = Math.max(...candidates);
currentIndex = 0;
maxSoFar = 0;
gameState = "PLAY";
$(`history`).innerHTML = "";
$(`btn-restart`).style.display = "none";
$(`controls`).style.display = "flex";
$(`btn-pass`).disabled = false;
$(`msg`).innerHTML = `คนที่ 1: ความรักครั้งแรก... จะคบเลยมั้ย?`;
$(`msg`).style.color = "#334155";
showCandidate(0);
}
function showCandidate(idx) {
const score = candidates[idx];
$(`progress`).innerText = `${idx + 1}/${TOTAL_CANDIDATES}`;
$(`score`).innerText = score;
const card = $(`card`);
card.style.transform = "scale(0.9) rotate(-2deg)";
setTimeout(() => card.style.transform = "scale(1) rotate(0deg)", 150);
const isBestSoFar = score > maxSoFar;
$(`rank`).style.visibility = isBestSoFar ? "visible" : "hidden";
if (idx === TOTAL_CANDIDATES - 1) {
$(`btn-pass`).innerText = "🚫 หมดสต๊อก";
$(`btn-pass`).disabled = true;
$(`msg`).innerText = "คนสุดท้ายแล้ว! ไฟลท์บังคับ ต้องคบคนนี้แหละ";
} else {
$(`btn-pass`).innerText = "💔 เท/ผ่าน";
$(`btn-pass`).disabled = false;
}
}
function updateHistory(score, isMaxSoFar) {
const span = document.createElement("span");
span.innerText = "💔" + score;
span.style.padding = "4px 8px";
span.style.borderRadius = "50px";
span.style.background = isMaxSoFar ? "#fce7f3" : "#f1f5f9";
span.style.color = isMaxSoFar ? "#be185d" : "#94a3b8";
span.style.fontWeight = isMaxSoFar ? "bold" : "normal";
span.style.border = isMaxSoFar ? "1px solid #fbcfe8" : "1px solid #e2e8f0";
$(`history`).appendChild(span);
}
function endGame(result, chosenScore) {
gameState = "END";
$(`controls`).style.display = "none";
$(`btn-restart`).style.display = "block";
$(`rank`).style.visibility = "hidden";
if (result === "WIN") {
$(`card`).style.background = "#d1fae5";
$(`card`).style.borderColor = "#059669";
$(`score`).style.color = "#047857";
$(`msg`).innerHTML = `🎉 <b>Happy Ending!</b><br>คุณเจอเนื้อคู่ที่เพอร์เฟกต์ที่สุด (คะแนน ${chosenScore})`;
$(`msg`).style.color = "#059669";
} else {
$(`card`).style.background = "#fee2e2";
$(`card`).style.borderColor = "#dc2626";
$(`score`).style.color = "#b91c1c";
const maxIdx = candidates.indexOf(trueMax) + 1;
$(`msg`).innerHTML = `😭 <b>Bad Ending...</b> คุณเลือกผิดคน (${chosenScore})<br>คนที่ใช่ที่สุดคือคนที่ ${maxIdx} (คะแนน ${trueMax})`;
$(`msg`).style.color = "#dc2626";
}
// Reveal all
for(let i = currentIndex + 1; i < TOTAL_CANDIDATES; i++) {
updateHistory(candidates[i], false);
}
}
// --- Handlers ---
$(`btn-pass`).onclick = () => {
const currentScore = candidates[currentIndex];
if (currentScore > maxSoFar) maxSoFar = currentScore;
updateHistory(currentScore, currentScore === maxSoFar);
currentIndex++;
if (currentIndex < TOTAL_CANDIDATES) {
showCandidate(currentIndex);
$(`msg`).innerText = `คนที่ ${currentIndex+1}: คนนี้ล่ะ? ตรงสเปกมั้ย?`;
}
};
$(`btn-hire`).onclick = () => {
const chosenScore = candidates[currentIndex];
if (chosenScore === trueMax) {
endGame("WIN", chosenScore);
} else {
endGame("LOSE", chosenScore);
}
};
$(`btn-restart`).onclick = initGame;
initGame();
return container;
})()ในชีวิตจริง เรามักเจอสถานการณ์ที่ “ของดีมักมาทีหลัง… แต่ถ้า รอนานไป ของดีอาจจะผ่านไปแล้ว”
โจทย์ของคณิตศาสตร์ข้อนี้คือ: “เราควรจะหยุด ‘ดูใจ’ และเริ่ม ‘ตัดสินใจ’ ที่คนลำดับที่เท่าไหร่ ถึงจะมีโอกาสชนะมากที่สุด?”
นักคณิตศาสตร์ได้พิสูจน์ออกมาแล้วว่า จุดที่สมดุลที่สุดระหว่าง “การหาข้อมูล” กับ “การตัดสินใจ” คือตัวเลข 37% ครับ
กลยุทธ์การเล่นเพื่อชนะ (Algorithm): แบ่งคนทั้งหมด (N) ออกเป็น 2 ช่วงครับ:
ช่วงเก็บข้อมูล (Explore Phase - 37% แรก):
สมมติมี 10 คน → (ปัดเป็น 3 หรือ 4 คนแรก)
กฎ: ห้ามเลือกใครเลยในช่วงนี้! ให้ปฏิเสธทุกคน
เป้าหมาย: เพื่อหาว่า
“มาตรฐานความเก่งสูงสุด” ของกลุ่มนี้คือเท่าไหร่ (Set Benchmark)
ช่วงล่าเป้าหมาย (Leap Phase - คนที่เหลือ):
เริ่มตั้งแต่คนที่ 4 เป็นต้นไป
กฎ: เจอใครคนแรกที่มีคะแนน “สูงกว่า” มาตรฐานสูงสุดในช่วงแรก… ให้เลือกทันที! (ไม่ต้องดูต่อแล้ว)
สมมติคะแนนคนคุย 10 คน เรียงตามลำดับที่เข้ามาดังนี้: [40, 70, 55, 60, 85, 95, 20, 90, 10, 30] (คนเก่งสุดคือ 95 อยู่ลำดับที่ 6)
ถ้าใช้สูตร 37% (ดู 3 คนแรก):
ช่วงดูใจ (3 คนแรก): 40, 70, 55
เราปล่อยผ่านหมด ❌
แต่เราจำไว้ว่า “มาตรฐานสูงสุดตอนนี้คือ 70” (Benchmark)
ช่วงตัดสินใจ (คนที่ 4 เป็นต้นไป):
คนที่ 4 (คะแนน 60): น้อยกว่า 70… ผ่าน ❌
คนที่ 5 (คะแนน 85): มากกว่า 70 แล้ว!… เลือกทันที! ✅
ผลลัพธ์: ในกรณีนี้ เราจะได้คนคะแนน 85 (ซึ่งเก่งเป็นอันดับ 2) แม้จะไม่ใช่ 95 แต่ก็ได้คนเกรด A+ แน่นอน และดีกว่าการสุ่มเลือกมาก
(หมายเหตุ: ถ้าคนเก่งที่สุดดันอยู่ใน 3 คนแรก สูตรนี้จะล้มเหลวทันที เพราะเราจะหาคนที่ดีกว่ามาตรฐานไม่ได้เลย จนต้องจำใจเลือกคนสุดท้าย แต่นั่นคือความเสี่ยงที่คำนวณมาแล้วว่าคุ้มค่าที่สุดครับ)
ตัวเลขนี้ไม่ได้มาจากการสุ่ม แต่มาจากค่าคงที่ทางคณิตศาสตร์ที่ชื่อว่า
(Euler’s number) มีค่าประมาณ
มีค่าประมาณ 0.368 หรือ 36.8%
กราฟความน่าจะเป็นจะบอกว่า:
สรุป: ไม่ว่าจะจีบสาว, หาเลขา, หรือหาที่จอดรถ… อย่าเพิ่งรีบตัดสินใจตั้งแต่แรก และอย่ารอจนวินาทีสุดท้าย “ดูเชิงสัก 1 ใน 3 แล้วลุยเลย” คือทางรอดที่ดีที่สุดครับ! 😉