មូលដ្ឋានគ្រឹះប្រូបាប

មាតិកា

  1. ភាពអាចកើតឡើងគឺជាលេខមួយ
  2. ប្រូបាបជាករណីអំណោយផលលើករណីសរុប
  3. Event ឯករាជ្យ និងពាក្យ AND
  4. ល្បិច៖ រាប់ផ្ទុយ
  5. Expected Value
  6. ចំនួនគត់ចៃដន្យ ធ្វើឲ្យត្រឹមត្រូវ
  7. Simulation៖ ឲ្យកុំព្យូទ័ររាប់
  8. Birthday Paradox និងការជំនួសគ្នានៃ ID
  9. A/B Test៖ ជាសំណាង ឬជាការពិត?
  10. Math.random មិនមែនសម្រាប់អាថ៌កំបាំងទេ
  11. រូបមន្ត៖ Module probability
  12. សាកល្បងដោយខ្លួនឯង

1. ភាពអាចកើតឡើងគឺជាលេខមួយ

តើថ្ងៃនេះនឹងភ្លៀងទេ? តើ variant នៃ A/B test នេះនឹងឈ្នះឬ? តើអ្នកប្រើពីរនាក់នឹងទទួលបាន ID ចៃដន្យដូចគ្នាឬ? កូដរបស់អ្នកជារឿយៗត្រូវដោះស្រាយជាមួយរឿងដែលអាចនឹងកើតឡើង ឬមិនកើតឡើង។ ប្រូបាប ផ្តល់ភាពអាចកើតឡើងជាលេខមួយចន្លោះ 0 និង 1៖

ប្រូបាបអត្ថន័យ
0មិនអាចទៅរួច។ វាមិនដែលកើតឡើងទេ
0.5ពាក់កណ្តាលនៃពេលវេលា ដូចកាក់ដែលទ្រទ្រង់យុត្តិធម៌ធ្លាក់ចេញមុខ
1ពិតប្រាកដ។ វាកើតឡើងជានិច្ច

អ្នកក៏នឹងឃើញប្រូបាបជាភាគរយដែរ៖ 0.25 គឺ 25%។ កម្មវិធីជាធម្មតារក្សាវាជាចំនួនទសភាគ ចន្លោះ 0 និង 1 ពីព្រោះវាគុណគ្នាបានយ៉ាងល្អ ដូចដែលអ្នកនឹងឃើញ។

ជំពូកនេះបន្តពី ការរាប់៖ Permutation និង Combination។ នៅពេលគ្រប់លទ្ធផលមានលទ្ធភាពស្មើគ្នា ប្រូបាបគឺគ្រាន់តែជាការរាប់។

2. ប្រូបាបជាករណីអំណោយផលលើករណីសរុប

សម្រាប់លទ្ធផលដែលមានលទ្ធភាពស្មើគ្នា៖

probability = (number of outcomes you want) / (total number of outcomes)

គ្រាប់ឡុកឡាក់ប្រាំមួយមុខយុត្តិធម៌មាន 6 លទ្ធផល។ ឱកាសបោះបាន 6 គឺ 1 / 6។ ឱកាសបោះបានលេខគូគឺ 3 / 6 (លទ្ធផល 2, 4 និង 6) ដែលស្មើ 0.5។

const favorable = 3; // 2, 4, 6
const total = 6;
console.log(favorable / total); // 0.5

ពាក្យ "មានលទ្ធភាពស្មើគ្នា" ធ្វើការងារពិតប្រាកដ។ ប្រសិនបើអ្នកសួរថា "តើខ្ញុំនឹងឈ្នះឆ្នោតទេ? វាកើតឡើង ឬមិនកើតឡើង ដូច្នេះវាគឺ 50%" អ្នកបានរាប់ លទ្ធផលដែលមិនមានលទ្ធភាពស្មើគ្នា។ ការរាប់ត្រឹមត្រូវសម្រាប់ឆ្នោត 6 ពី 49 មកពីជំពូកមុន៖ សំបុត្រឈ្នះមួយក្នុងចំណោម C(49, 6) = 13,983,816 ដែលប្រហែល 0.00000007។

អ្នកសរសេរកម្មវិធីប្រើវាដើម្បីឆ្លើយសំណួរដូចជា "ប្រសិនបើ server មួយត្រូវបានជ្រើសដោយចៃដន្យស្មើៗគ្នា ពី 8 server តើទំនងប៉ុនណាដែល request ធ្លាក់លើ server ទី 3?" ចម្លើយ៖ 1 / 8 = 0.125។

3. Event ឯករាជ្យ និងពាក្យ AND

Event ពីរគឺ ឯករាជ្យ នៅពេលមួយមិនមានឥទ្ធិពលលើមួយទៀត។ ការបោះកាក់ពីរដង គ្រាប់ឡុកឡាក់ពីរ ឬសំបុត្រឆ្នោតរបស់អ្នកប្រើពីរនាក់ខុសគ្នា គឺជាឧទាហរណ៍។

សម្រាប់ event ឯករាជ្យ ឱកាសដែលទាំងពីរ កើតឡើង គឺជា ផលគុណ នៃប្រូបាបរបស់ពួកវា៖

P(A and B) = P(A) * P(B)

ឱកាសបោះបាន 6 ពីរដងជាមួយគ្រាប់ឡុកឡាក់ពីរគឺ 1/6 * 1/6 = 1/36៖

console.log(1 / 6 * (1 / 6)); // 0.027777777777777776

ហេតុអ្វីការគុណដំណើរការ? នេះគឺជា rule of product ពីជំពូកមុនក្នុង ទម្រង់ក្លែងបន្លំ៖ មានគូលទ្ធផលគ្រាប់ឡុកឡាក់ដែលមានលទ្ធភាពស្មើគ្នា 6 * 6 = 36 ហើយ មានតែមួយប៉ុណ្ណោះដែលជា 6 ទ្វេ។

ប្រូបាបមានច្រើនបំផុតត្រឹម 1 ដូច្នេះការគុណវាតែងតែធ្វើឲ្យលទ្ធផល តូចជាង ឬស្មើ៖ "នេះ និង នោះ" មិនដែលទំនងជាង "នេះ" ទេ។

ឧទាហរណ៍ប្រចាំថ្ងៃមួយទៀត៖ សេវាកម្មមួយសន្យា uptime 99% (0.99) ហើយ ពឹងលើ database ដែលមានដំណើរការ 99% ដែរ។ site របស់អ្នកដំណើរការតែនៅពេល ទាំងពីរ ដំណើរការ៖

console.log(0.99 * 0.99); // 0.9801

ប្រហែល 98% ទាបជាងទាំងពីរ។ ខ្សែសង្វាក់នៃការពឹងផ្អែកបន្ថយភាពអាចទុកចិត្តបានដោយស្ងាត់ៗ ហើយនោះគឺជាការគុណនៃប្រូបាប។

ប្រយ័ត្ន៖ ច្បាប់នេះត្រឹមត្រូវតែសម្រាប់ event ឯករាជ្យ ប៉ុណ្ណោះ។ ប្រសិនបើ event ទីពីរ ពឹងលើ event ទីមួយ (ចាប់បៀដោយមិនដាក់វាត្រឡប់វិញ) អ្នកមិនអាចគ្រាន់តែគុណ ប្រូបាបធម្មតាបានទេ។

4. ល្បិច៖ រាប់ផ្ទុយ

តើឱកាសបោះបាន 6 យ៉ាងហោចណាស់មួយដង ក្នុងចំនួនបោះបួនដងគឺប៉ុន្មាន? ការរាប់ គ្រប់វិធីដើម្បីបាន 6 មួយ ពីរ បី ឬបួន គឺលំបាក។ ត្រឡប់សំណួរវិញ៖ វិធីតែមួយគត់ដែល មិន បាន 6 សោះ គឺបោះបានមិនមែន 6 បួនដងជាប់គ្នា។

P(at least one) = 1 - P(none)

ឱកាសបានមិនមែន 6 ក្នុងការបោះមួយដងគឺ 5/6 ហើយការបោះឯករាជ្យបួនដង គុណគ្នា៖

const none = (5 / 6) ** 4;
console.log(none);     // 0.4822530864197531
console.log(1 - none); // 0.5177469135802468

ប្រហែល 51.8%។ ល្បិច "មួយដកផ្ទុយ" នេះគឺជាចលនាមានប្រយោជន៍បំផុតតែមួយគត់ក្នុង ប្រូបាបមូលដ្ឋាន ហើយអ្នកនឹងប្រើវាម្តងទៀតសម្រាប់ birthday paradox ខាងក្រោម។

5. Expected Value

ប្រូបាបប្រាប់អ្នកថាតើលទ្ធផលនីមួយៗទំនងប៉ុនណា។ Expected value ប្រាប់ អ្នកអំពីលទ្ធផលមធ្យមដែលអ្នកនឹងឃើញ ប្រសិនបើអ្នកធ្វើស្ថានភាពនោះម្តងហើយម្តងទៀតច្រើនដង។ អ្នកគណនាវាដោយគុណតម្លៃនៃលទ្ធផលនីមួយៗនឹង ប្រូបាបរបស់វា ហើយបូកលទ្ធផលទាំងអស់៖

expected value = (value1 * prob1) + (value2 * prob2) + ...

សម្រាប់គ្រាប់ឡុកឡាក់ គ្រប់តម្លៃពី 1 ដល់ 6 មានប្រូបាប 1/6៖

const outcomes = [1, 2, 3, 4, 5, 6].map((value) => ({
  value,
  probability: 1 / 6,
}));

const ev = outcomes.reduce((sum, o) => sum + o.value * o.probability, 0);
console.log(ev); // 3.5

គ្មានការបោះណាមួយជា 3.5 ទេ។ លេខនេះពណ៌នាមធ្យមរយៈពេលវែង។

Expected value គឺជារបៀបដែលអ្នកវិនិច្ឆ័យការភ្នាល់ ឬការសម្រេចចិត្តដែលពាក់ព័ន្ធនឹងលុយ។ ស្រមៃថា សំបុត្រ raffle មួយតម្លៃ 2 ដុល្លារ។ មានឱកាស 0.0005 (1 ក្នុង 2,000) ក្នុងការ ឈ្នះរង្វាន់ 4,000 ដុល្លារ ហើយបើមិនដូច្នោះទេ គ្មានអ្វីសោះ៖

const prize = 4000 * 0.0005;
console.log(prize); // 2

សំបុត្រមានតម្លៃ 2 ដុល្លារជាមធ្យម ស្មើនឹងតម្លៃរបស់វាពិតប្រាកដ ដូច្នេះក្នុងចំណោមសំបុត្រ ច្រើន អ្នកស្មើដើម។ ប្រសិនបើឱកាសឈ្នះរង្វាន់ជា 1 ក្នុង 4,000 expected value នឹងជា 1 ដុល្លារ ហើយអ្នកនឹងខាត 1 ដុល្លារក្នុងមួយសំបុត្រជាមធ្យម។ ល្បែង និងឆ្នោតត្រូវបានរៀបចំឲ្យ expected value ទាបជាងតម្លៃ។

គំនិតដូចគ្នាវាយតម្លៃការសម្រេចចិត្តផ្នែកវិស្វកម្ម៖ "ការព្យាយាមម្តងទៀតនេះជោគជ័យ 70% នៃ ពេលវេលា ហើយចំណាយ 200 ms" ផ្តល់ឲ្យអ្នកនូវចំនួនដងដែលរំពឹងទុកនៃការព្យាយាម និង ការពន្យារពេលសរុបដើម្បីធ្វើផែនការ។

6. ចំនួនគត់ចៃដន្យ ធ្វើឲ្យត្រឹមត្រូវ

Math.random() ត្រឡប់ចំនួនទសភាគចាប់ពី 0 ឡើងទៅ ប៉ុន្តែ មិនរួមបញ្ចូល 1។ ដើម្បីទទួលបានចំនួនគត់ក្នុងជួរមួយ ត្រូវពង្រីក បង្គត់ ចុះ ហើយរំកិលវា។ សម្រាប់ការបោះគ្រាប់ឡុកឡាក់ 1 ដល់ 6 រួមបញ្ចូលទាំងពីរ៖

const roll = Math.floor(Math.random() * 6) + 1;

អានវាពីខាងក្នុងចេញមកក្រៅ៖

  • Math.random() ផ្តល់ 0 <= r < 1។
  • * 6 ផ្តល់ 0 <= x < 6។
  • Math.floor(...) ផ្តល់មួយក្នុងចំណោម 0, 1, 2, 3, 4, 5 ដែលមានលទ្ធភាពស្មើគ្នា។
  • + 1 រំកិលវាទៅ 1 ដល់ 6។

កំណែទូទៅ ដែលរួមបញ្ចូលទាំងចុងទាំងពីរ គឺ៖

function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

+ 1 ងាយភ្លេច។ បើគ្មានវា max មិនអាចត្រូវបានត្រឡប់ឡើយ ដែលជា off-by-one bug បុរាណ។

ការព្រមានពីរអំពីកំណែដែលនិយមប្រើប៉ុន្តែខុស៖

  • Math.round(Math.random() * 5) + 1 មិនស្មើគ្នាទេ។ round ផ្តល់ លទ្ធផលទីមួយ និងចុងក្រោយតែពាក់កណ្តាលនៃឱកាសរបស់លទ្ធផលកណ្តាល។ ប្រើ floor ជានិច្ច។
  • ការប្រើ % លើចំនួនគត់ចៃដន្យធំ (randomBits % 6) អាច លំអៀង បន្តិច នៅពេលជួរមិនចែកដាច់។ សម្រាប់ល្បែង និងការលាយ ភាពលំអៀងនោះមិនសំខាន់។ សម្រាប់សុវត្ថិភាព វាសំខាន់ ហើយយើងដោះស្រាយវា ក្នុងរូបមន្តដោយប្រើ retry loop។

7. Simulation៖ ឲ្យកុំព្យូទ័ររាប់

ពេលខ្លះគណិតវិទ្យាច្បាស់លាស់ពិបាក ឬអ្នកគ្រាន់តែចង់ពិនិត្យចម្លើយរបស់អ្នក។ Simulation ដំណើរការការពិសោធន៍ច្រើនដង ហើយរាប់ថាតើ event កើតឡើងញឹកញាប់ប៉ុនណា។ តាម law of large numbers កាលណាអ្នកដំណើរការ trial កាន់តែច្រើន ប្រភាគនោះកាន់តែជិតប្រូបាបពិត។

Helper ទូទៅមួយ៖ ដំណើរការ experiment() ដែលត្រឡប់ true ឬ false ច្រើនដង ហើយរាយការណ៍ប្រភាគនៃ true៖

function simulate(trials, experiment) {
  let hits = 0;
  for (let i = 0; i < trials; i++) {
    if (experiment()) hits++;
  }
  return hits / trials;
}

// ឱកាសបោះបាន 6 ពីរដងលើគ្រាប់ឡុកឡាក់ពីរ។ ចម្លើយច្បាស់លាស់គឺ 1/36 = 0.0277...
const p = simulate(100000, () => randomInt(1, 6) === 6 && randomInt(1, 6) === 6);
console.log(p); // varies each run, typically between 0.026 and 0.030

លទ្ធផលរបស់អ្នកនឹងខុសគ្នាគ្រប់ពេល ពីព្រោះគ្រាប់ឡុកឡាក់ជាចៃដន្យ។ ជាមួយ 100,000 trial ចម្លើយជាធម្មតានៅក្នុងចន្លោះប្រហែល 0.001 នៃតម្លៃពិត។ ជាមួយ 1,000 trial វាប្រែប្រួលច្រើនជាងនេះ ដូច្នេះប្រើ trial គ្រប់គ្រាន់សម្រាប់ ភាពជាក់លាក់ដែលអ្នកត្រូវការ។

Simulation ក៏បញ្ជាក់ពី generator ដែលយុត្តិធម៌ដែរ។ បោះគ្រាប់ឡុកឡាក់ 60,000 ដង ហើយរាប់៖

const counts = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 };
for (let i = 0; i < 60000; i++) counts[randomInt(1, 6)]++;
console.log(counts);
// varies, but each count lands near 10000, typically 9800 to 10200

ប្រសិនបើមុខណាមួយមានលេខលើសឆ្ងាយ អ្នកនឹងដឹងថា generator ឬរូបមន្តរបស់អ្នក មាន bug។

8. Birthday Paradox និងការជំនួសគ្នានៃ ID

តើត្រូវមានមនុស្សប៉ុន្មាននាក់ក្នុងបន្ទប់ មុនពេលពីរនាក់ទំនងជាមានថ្ងៃកំណើតដូចគ្នា? មនុស្សភាគច្រើនទាយប្រហែល 180 (ពាក់កណ្តាលនៃ 365)។ ចម្លើយពិតគឺ 23។

វាមើលទៅដូចជាខុស ពីព្រោះយើងប្រៀបធៀបមនុស្សម្នាក់នឹងអ្នកផ្សេងទៀត។ ប៉ុន្តែជាមួយ 23 នាក់ មាន គូ C(23, 2) = 253 ហើយគូណាមួយដែលត្រូវគ្នាក៏រាប់ដែរ។

ប្រើល្បិចផ្ទុយ។ ឱកាសដែល គ្មាននរណា មានថ្ងៃកំណើតដូចគ្នា៖

  • មនុស្សទីពីរត្រូវជៀសវាង 1 ថ្ងៃ៖ 364/365។
  • មនុស្សទីបីត្រូវជៀសវាង 2 ថ្ងៃ៖ 363/365។
  • ហើយបន្តដូច្នេះ ដោយគុណ (ជម្រើសឯករាជ្យ)។

បន្ទាប់មកប្រូបាបនៃការជំនួសគ្នា (collision) គឺ 1 - ផលគុណនោះ៖

function birthdayCollision(people, days = 365) {
  let allDifferent = 1;
  for (let i = 0; i < people; i++) {
    allDifferent *= (days - i) / days;
  }
  return 1 - allDifferent;
}

console.log(birthdayCollision(10).toFixed(4)); // 0.1169
console.log(birthdayCollision(23).toFixed(4)); // 0.5073
console.log(birthdayCollision(30).toFixed(4)); // 0.7063
console.log(birthdayCollision(50).toFixed(4)); // 0.9704
console.log(birthdayCollision(70).toFixed(4)); // 0.9992

នៅ 23 នាក់ វាហួស 50%។ នៅ 70 វាស្ទើរតែប្រាកដ។

នេះមិនមែនគ្រាន់តែជាល្បិចក្នុងពិធីជប់លៀងទេ។ វាគឺជារបៀបដែលអ្នកវិភាគអំពី ការជំនួសគ្នានៃ ID។ ស្រមៃថាផ្តល់ ID 32-bit ចៃដន្យដល់ការបញ្ជាទិញថ្មីនីមួយៗ។ នោះគឺ ប្រហែល 4.3 ពាន់លានតម្លៃដែលអាចមាន (2 ** 32) ដែលស្តាប់ទៅធំណាស់។ ប៉ុន្តែ គណិតវិទ្យា birthday ប្រាប់ថាអ្នកមិនត្រូវការការបញ្ជាទិញរាប់ពាន់លានមុនពេលស្ទួន លេចឡើងទេ៖

console.log(birthdayCollision(100000, 2 ** 32).toFixed(4)); // 0.6878

នោះគឺប្រហែល 0.69។ ជាមួយ ID តែ 100,000 មាន ឱកាសប្រហែល 69% ដែលយ៉ាងហោចណាស់ពីរមានតម្លៃដូចគ្នា។ Collision លេចឡើងនៅប្រហែល ឫសការ៉េ នៃចំនួន ID ដែលអាចមាន។

នោះជាមូលហេតុដែល ID ចៃដន្យសម្រាប់ database ត្រូវបានធ្វើឲ្យវែងជាងនេះច្រើន។ UUID កំណែ 4 មាន 122 bit ចៃដន្យ (2 ** 122) ប្រហែល 5.3 x 10^36 តម្លៃ។ នៅទំហំនោះ អ្នកនឹងត្រូវបង្កើត ID រាប់ពាន់លានក្នុងមួយវិនាទីអស់ជាច្រើនទសវត្សរ៍ ដើម្បីទទួលបាន ឱកាស collision ដែលសំខាន់។ កូដចៃដន្យខ្លី (ដូចជា កូដ coupon 6 តួអក្សរ ឬ link ខ្លី) ត្រូវការការពិនិត្យ collision ឬ retry ពេលមានជម្លោះ។

9. A/B Test៖ ជាសំណាង ឬជាការពិត?

អ្នកផ្លាស់ប្តូរពណ៌ប៊ូតុង។ Variant A បំប្លែងអ្នកទស្សនា 100 នាក់ក្នុងចំណោម 1,000 (10%) ហើយ variant B បំប្លែង 113 នាក់ក្នុងចំណោម 1,000 (11.3%)។ តើ B ល្អជាង ឬ ជាសំណាង?

អ្នកអាចមានអារម្មណ៍ចំពោះចម្លើយដោយ simulation។ ធ្វើពុតថាទាំងពីរ variant មាន អត្រាបំប្លែងពិត ដូចគ្នា គឺ 10% ហើយមើលថាតើក្រុម 1,000 នាក់ពីរអាចមើលទៅខុសគ្នាប៉ុនណា ដោយសារតែចៃដន្យ៖

function conversions(visitors, rate) {
  return simulate(visitors, () => Math.random() < rate);
}

for (let i = 0; i < 5; i++) {
  const a = conversions(1000, 0.1);
  const b = conversions(1000, 0.1);
  console.log(a, b);
}
// varies, e.g. 0.113 0.099 / 0.094 0.108 / ...
// ក្រុមទាំងពីរមានអត្រាពិតដូចគ្នា ប៉ុន្តែពួកវាតែងខុសគ្នាមួយពិន្ទុ ឬច្រើនជាងនេះ។

ទោះបីអត្រាពិតដូចគ្នាក៏ដោយ គំរូទាំងពីរតែងខុសគ្នាមួយភាគរយ ឬច្រើនជាងនេះ។ ដូច្នេះ 10% ទល់នឹង 11.3% លើអ្នកទស្សនា 1,000 នាក់ក្នុងមួយក្រុម បញ្ជាក់បានតិចតួចណាស់។ ចំណុចសំខាន់ពីរ ដោយមិនត្រូវការរូបមន្ត៖

  • គំរូតូចមានសំឡេងរំខាន។ អ្នកទស្សនាកាន់តែច្រើនបន្ថយសំឡេងរំខាន ដូចដែល trial កាន់តែច្រើន ធ្វើឲ្យ simulation ស្ងប់នៅជិតចម្លើយច្បាស់លាស់។
  • សម្រេចទំហំគំរូជាមុន។ ប្រសិនបើអ្នកលួចមើលរៀងរាល់ម៉ោង ហើយឈប់ ពេល B មើលទៅនាំមុខ អ្នកទំនងជាឈប់ត្រូវពេលសំណាងល្អ។

ស្ថិតិពិតប្រាកដផ្តល់ឧបករណ៍ច្បាស់លាស់ (confidence interval, p-value) ដែល នៅហួសពីស៊េរីនេះ។ អ្វីដែលអ្នកមានឥឡូវគឺអារម្មណ៍សភាវគតិអំពីមូលហេតុដែលវាមាន។

10. Math.random មិនមែនសម្រាប់អាថ៌កំបាំងទេ

Math.random() គឺជា generator pseudo-random៖ លឿន និងល្អសម្រាប់ល្បែង ការលាយ animation និង simulation។ ប៉ុន្តែវាមិនត្រូវបានរចនាឡើងដើម្បីទប់ទល់នឹង អ្នកដែលព្យាយាមទាយលទ្ធផលរបស់វាទេ។ បើមានលទ្ធផលពីរបី អ្នកវាយប្រហារ ពេលខ្លះអាចរកស្ថានភាពខាងក្នុងរបស់ generator ហើយទាយតម្លៃបន្ទាប់។ កុំប្រើវាសម្រាប់៖

  • token កំណត់ពាក្យសម្ងាត់ឡើងវិញ
  • session ID ឬ API key
  • កូដប្រើម្តង (OTP)

សម្រាប់ទាំងនោះ ប្រើប្រភព សុវត្ថិភាពខាងគ្រីបតូក្រាហ្វី ដែលភ្ជាប់មកជាមួយ JavaScript គឺ crypto.getRandomValues ដែលប្រើប្រភពចៃដន្យរបស់ប្រព័ន្ធប្រតិបត្តិការ។ វាមានជា global ក្នុង Node.js និងក្នុង browser៖

const bytes = new Uint32Array(1);
crypto.getRandomValues(bytes);
console.log(bytes[0]); // a random whole number from 0 to 4294967295

វាបំពេញ typed array ដោយ bit ចៃដន្យ។ ដើម្បីបំប្លែងវាជាលេខពី 0 ដល់ max - 1 ដោយគ្មានភាពលំអៀង យើងបោះចោលតម្លៃណាក៏ដោយក្នុងផ្នែកសល់ដែលមិនស្មើ នៅខាងលើ ហើយព្យាយាមម្តងទៀត។ អ្នកនឹងឃើញវាក្នុងរូបមន្ត។

កូដ 6 ខ្ទង់ពី app ធនាគារ ឬការទូទាត់ KHQR ឬពាក្យសម្ងាត់ប្រើម្តងសម្រាប់ចូលគណនី ត្រូវតែមកពី generator ប្រភេទនេះ មិនមែន Math.random ទេ។ (ក្នុង Node អ្នកក៏អាចប្រើ crypto.randomInt(0, 10) ពី module node:crypto ដែលធ្វើដូចគ្នា។)

11. រូបមន្ត៖ Module probability

បង្កើត src/probability.js ក្នុង mathlab៖

// src/probability.js — ភាពអាចកើតឡើង expectation និង simulation។

// ចំនួនគត់ពី min ដល់ max រួមបញ្ចូលទាំងពីរចុង។
export function randomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

// គ្មានភាពលំអៀង និងសុវត្ថិភាពសម្រាប់ token៖ ប្រើប្រភពចៃដន្យរបស់ OS។
export function secureRandomInt(max) {
  // ត្រឡប់ 0 .. max-1 (max ត្រូវនៅចន្លោះ 1 និង 2**32)។
  const limit = 2 ** 32 - (2 ** 32 % max);
  const buf = new Uint32Array(1);
  do {
    crypto.getRandomValues(buf);
  } while (buf[0] >= limit);
  return buf[0] % max;
}

// outcomes: [{ value, probability }, ...]
export function expectedValue(outcomes) {
  return outcomes.reduce((sum, o) => sum + o.value * o.probability, 0);
}

// ឱកាសដែលយ៉ាងហោចណាស់ពីរនាក់ក្នុងចំណោម `people` មានតម្លៃដូចគ្នាក្នុង `days` ជម្រើស។
export function birthdayCollision(people, days = 365) {
  let allDifferent = 1;
  for (let i = 0; i < people; i++) {
    allDifferent *= (days - i) / days;
  }
  return 1 - allDifferent;
}

// ដំណើរការការពិសោធន៍ច្រើនដង ហើយត្រឡប់ប្រភាគនៃលទ្ធផល true។
export function simulate(trials, experiment) {
  let hits = 0;
  for (let i = 0; i < trials; i++) {
    if (experiment()) hits++;
  }
  return hits / trials;
}

ក្នុង secureRandomInt លេខ 2 ** 32 % max គឺជាទំហំនៃផ្នែកសល់ដែលមិនស្មើ។ សម្រាប់ max = 10, 2 ** 32 មិនមែនជាពហុគុណនៃ 10 ទេ ដូច្នេះ 6 តម្លៃចុងក្រោយនឹងធ្វើឲ្យខ្ទង់តូចមានលទ្ធភាពខ្ពស់បន្តិច។ Loop do/while បដិសេធតម្លៃទាំងនោះ ហើយជួរដែលនៅសល់ចែកដាច់ ដូច្នេះ % ឥឡូវ យុត្តិធម៌។

បន្ថែម export ទៅ src/index.js៖

export * from "./probability.js";

សាកល្បងវាដោយ node demo.js៖

import {
  randomInt, secureRandomInt, expectedValue, birthdayCollision, simulate,
} from "./src/index.js";

console.log(randomInt(1, 6));          // varies: 1 to 6

const die = [1, 2, 3, 4, 5, 6].map((value) => ({ value, probability: 1 / 6 }));
console.log(expectedValue(die));       // 3.5

console.log(birthdayCollision(23).toFixed(4));  // 0.5073

// ឱកាសដែលគ្រាប់ឡុកឡាក់ពីរបូកបាន 7។ ចម្លើយច្បាស់លាស់៖ 6/36 = 0.1666...
console.log(simulate(100000, () => randomInt(1, 6) + randomInt(1, 6) === 7));
// varies, typically between 0.163 and 0.170

const code = Array.from({ length: 6 }, () => secureRandomInt(10)).join("");
console.log(code);                     // varies: a 6-digit string like "026161"

ដំណើរការវាពីរដង។ បន្ទាត់កំណត់ (3.5 និង 0.5073) នៅដដែល ហើយបន្ទាត់ចៃដន្យប្រែប្រួល។ សូមកត់សម្គាល់ថាកូដរក្សាលេខសូន្យនៅខាងមុខ ពីព្រោះយើងបង្កើតវាជា string មិនមែនជា number។

12. សាកល្បងដោយខ្លួនឯង

  1. គ្រាប់ឡុកឡាក់ពីរ។ រាយបញ្ជីដោយដៃនូវវិធីដែលគ្រាប់ឡុកឡាក់ពីរអាចបូកបាន 7 រួច ចែកនឹង 36។ ប្រៀបធៀបជាមួយលទ្ធផល simulate។ តើផលបូកណាទំនងបំផុត ហើយហេតុអ្វី?
  2. Uptime។ សេវាកម្មបីនីមួយៗមាន uptime 99.5% ហើយ app របស់អ្នកត្រូវការទាំងបី។ ប្រើច្បាប់គុណដើម្បីរក uptime រួម។ បន្ទាប់មកប្រើល្បិច "រាប់ផ្ទុយ" ដើម្បីរកថាតើនោះមានន័យថា downtime ប៉ុន្មាននាទីក្នុងមួយខែ 30 ថ្ងៃ (30 * 24 * 60 នាទី)។
  3. Expected value។ ល្បែងមួយតម្លៃ 1 ដុល្លារដើម្បីលេង។ អ្នកបោះគ្រាប់ឡុកឡាក់៖ 6 ទទួលបាន 5 ដុល្លារ អ្វីផ្សេងទៀតគ្មានអ្វីសោះ។ សរសេរលទ្ធផលសម្រាប់ expectedValue ហើយសម្រេចថាតើល្បែងនេះអំណោយផលដល់អ្នក ឬម្ចាស់ល្បែង។
  4. រកចំណុចហាសិបហាសិប។ ដោយប្រើ birthdayCollision រកទំហំក្រុម តូចបំផុតដែលឱកាសនៃតម្លៃដូចគ្នាក្នុងចំណោម ID ដែលអាចមាន 1,000,000 ហួស 50%។ Loop people ឡើងលើ។ អ្នកគួរទៅដល់ជិត ឫសការ៉េនៃ 1,000,000 គុណនឹងប្រហែល 1.18។
  5. ពិនិត្យភាពយុត្តិធម៌។ បោះ randomInt(1, 6) 600,000 ដង ហើយរាប់មុខនីមួយៗ។ បន្ទាប់មកធ្វើដូចគ្នាជាមួយ Math.round(Math.random() * 5) + 1 ហើយមើល ថាតើមុខ 1 និង 6 ប្រៀបធៀបនឹងមុខផ្សេងទៀតយ៉ាងណា។
  6. Token សុវត្ថិភាព។ សរសេរ makeToken(length) ដែលប្រើ secureRandomInt ដើម្បីបង្កើត token ពីតួអក្សរ 62 a-z A-Z 0-9។ ប្រើ passwordSpace ពីជំពូកមុនដើម្បីប្រាប់ថាមាន token 16 តួអក្សរខុសៗគ្នាប៉ុន្មាន។

ប្រូបាបបិទពាក់កណ្តាល "ប៉ុន្មាន និងទំនងប៉ុនណា" នៃស៊េរី។ ជំពូកចុងក្រោយងាកទៅរកអ្វីដែលអ្នកអាចមើលឃើញលើអេក្រង់៖ កូអរដោនេ ធរណីមាត្រ និងការដាក់ទាំងអស់ចូលគ្នា គ្របដណ្តប់ point, distance, មុំ, animation ហើយបន្ទាប់មកដាក់ mathlab ទាំងមូលចូលគ្នាជារបាយការណ៍តែមួយ។

ទិញកាហ្វេជូនខ្ញុំ

ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!