ฉันจะสร้างคอลเลกชันที่มีน้ำหนักแล้วเลือกองค์ประกอบแบบสุ่มได้อย่างไร


34

ฉันมีกล่องของขวัญที่ฉันต้องการเติมด้วยรายการแบบสุ่ม แต่ฉันต้องการให้แต่ละรายการมีโอกาสในการเลือกที่แตกต่างกัน ตัวอย่างเช่น:

  • โอกาส 5% 10 ทอง
  • โอกาสดาบ 20%
  • โอกาส 45% ของการป้องกัน
  • โอกาส 20% ของเกราะ
  • โอกาส 10% ของมื้อยา

ฉันจะสร้างมันขึ้นมาได้อย่างไรเพื่อให้ฉันเลือกหนึ่งในสิ่งที่กล่าวมาข้างต้นโดยที่เปอร์เซ็นต์เหล่านั้นมีโอกาสในการได้รับของขวัญ


1
ในทางทฤษฎี FYI นั้นมีเวลา O (1) ต่อตัวอย่างสำหรับการแจกแจง จำกัด ใด ๆ แม้แต่การกระจายที่มีการเปลี่ยนแปลงรายการแบบไดนามิก ดูเช่นcstheory.stackexchange.com/questions/37648/...
Neal Young

คำตอบ:


37

โซลูชันความน่าจะเป็นแบบอ่อน

โซลูชันความน่าจะเป็นฮาร์ดโค้ดมีข้อเสียที่คุณต้องกำหนดความน่าจะเป็นในรหัสของคุณ คุณไม่สามารถระบุได้ในขณะใช้งานจริง นอกจากนี้ยังยากที่จะรักษา

นี่คืออัลกอริทึมรุ่นเดียวกันของไดนามิก

  1. สร้างอาร์เรย์ของคู่ของรายการจริงและน้ำหนักของแต่ละรายการ
  2. เมื่อคุณเพิ่มรายการน้ำหนักของรายการต้องเป็นน้ำหนักของตัวเองบวกกับผลรวมของน้ำหนักของรายการทั้งหมดที่มีอยู่ในอาร์เรย์ ดังนั้นคุณควรติดตามผลรวมแยกกัน โดยเฉพาะอย่างยิ่งเพราะคุณจะต้องใช้ในขั้นตอนต่อไป
  3. เพื่อดึงวัตถุสร้างตัวเลขสุ่มระหว่าง 0 และผลรวมของน้ำหนักของรายการทั้งหมด
  4. วนซ้ำตั้งแต่ต้นจนจบจนกว่าคุณจะพบรายการที่มีน้ำหนักมากกว่าหรือเท่ากับตัวเลขสุ่ม

นี่คือตัวอย่างการใช้งานในJavaในรูปแบบของคลาสเทมเพลตซึ่งคุณสามารถสร้างอินสแตนซ์สำหรับวัตถุใด ๆ ที่เกมของคุณใช้ จากนั้นคุณสามารถเพิ่มวัตถุด้วยวิธีการ.addEntry(object, relativeWeight)และเลือกหนึ่งในรายการที่คุณเพิ่มไว้ก่อนหน้าด้วย.get()

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class WeightedRandomBag<T extends Object> {

    private class Entry {
        double accumulatedWeight;
        T object;
    }

    private List<Entry> entries = new ArrayList<>();
    private double accumulatedWeight;
    private Random rand = new Random();

    public void addEntry(T object, double weight) {
        accumulatedWeight += weight;
        Entry e = new Entry();
        e.object = object;
        e.accumulatedWeight = accumulatedWeight;
        entries.add(e);
    }

    public T getRandom() {
        double r = rand.nextDouble() * accumulatedWeight;

        for (Entry entry: entries) {
            if (entry.accumulatedWeight >= r) {
                return entry.object;
            }
        }
        return null; //should only happen when there are no entries
    }
}

การใช้งาน:

WeightedRandomBag<String> itemDrops = new WeightedRandomBag<>();

// Setup - a real game would read this information from a configuration file or database
itemDrops.addEntry("10 Gold",  5.0);
itemDrops.addEntry("Sword",   20.0);
itemDrops.addEntry("Shield",  45.0);
itemDrops.addEntry("Armor",   20.0);
itemDrops.addEntry("Potion",  10.0);

// drawing random entries from it
for (int i = 0; i < 20; i++) {
    System.out.println(itemDrops.getRandom());
}

นี่คือคลาสเดียวกันที่ใช้ในC #สำหรับโครงการ Unity, XNA หรือ MonoGame ของคุณ:

using System;
using System.Collections.Generic;

class WeightedRandomBag<T>  {

    private struct Entry {
        public double accumulatedWeight;
        public T item;
    }

    private List<Entry> entries = new List<Entry>();
    private double accumulatedWeight;
    private Random rand = new Random();

    public void AddEntry(T item, double weight) {
        accumulatedWeight += weight;
        entries.Add(new Entry { item = item, accumulatedWeight = accumulatedWeight });
    }

    public T GetRandom() {
        double r = rand.NextDouble() * accumulatedWeight;

        foreach (Entry entry in entries) {
            if (entry.accumulatedWeight >= r) {
                return entry.item;
            }
        }
        return default(T); //should only happen when there are no entries
    }
}

และนี่คือหนึ่งในJavaScript :

var WeightedRandomBag = function() {

    var entries = [];
    var accumulatedWeight = 0.0;

    this.addEntry = function(object, weight) {
        accumulatedWeight += weight;
        entries.push( { object: object, accumulatedWeight: accumulatedWeight });
    }

    this.getRandom = function() {
        var r = Math.random() * accumulatedWeight;
        return entries.find(function(entry) {
            return entry.accumulatedWeight >= r;
        }).object;
    }   
}

มือโปร:

  • สามารถจัดการกับอัตราส่วนน้ำหนักใด ๆ คุณสามารถมีรายการที่มีความน่าจะเป็นทางดาราศาสตร์น้อยในชุดถ้าคุณต้องการ น้ำหนักไม่จำเป็นต้องเพิ่มสูงสุด 100
  • คุณสามารถอ่านรายการและตุ้มน้ำหนักขณะใช้งานจริง
  • การใช้หน่วยความจำตามสัดส่วนของจำนวนรายการในอาร์เรย์

Contra:

  • ต้องมีการเขียนโปรแกรมเพิ่มเติมเพื่อให้ถูกต้อง
  • ในกรณีที่เลวร้ายที่สุดคุณอาจต้องทำซ้ำทั้งอาร์เรย์ ( O(n)ความซับซ้อนของรันไทม์) ดังนั้นเมื่อคุณมีรายการที่มีขนาดใหญ่มากและวาดบ่อยมากมันอาจจะช้า การเพิ่มประสิทธิภาพอย่างง่าย ๆ คือการใส่ไอเท็มที่น่าจะเป็นอันดับแรกที่สุดดังนั้นอัลกอริทึมจะยุติในช่วงต้นในกรณีส่วนใหญ่ การปรับให้เหมาะสมที่ซับซ้อนยิ่งขึ้นที่คุณสามารถทำได้คือการใช้ประโยชน์จากความจริงที่ว่าอาเรย์นั้นถูกจัดเรียงและทำการค้นหาแบบแบ่งสองส่วน ใช้เวลาเพียงแค่O(log n)นี้
  • คุณต้องสร้างรายการในหน่วยความจำก่อนจึงจะสามารถใช้งานได้ (แม้ว่าคุณสามารถเพิ่มรายการได้อย่างง่ายดายในขณะรันไทม์นอกจากนี้ยังสามารถเพิ่มรายการที่นำออกได้ แต่จะต้องอัปเดตน้ำหนักที่สะสมของรายการทั้งหมด มีอีกO(n)กรณีรันไทม์ที่เลวร้ายที่สุด)

2
รหัส C # สามารถเขียนได้โดยใช้ LINQ: return entry.FirstOrDefault (e => e.accumulatedWeight> = r) ที่สำคัญกว่านั้นมีความเป็นไปได้เล็กน้อยที่เกิดจากการสูญเสียความแม่นยำของจุดลอยตัวอัลกอริทึมนี้จะส่งคืนค่าว่างหากค่าสุ่มได้รับมากกว่าค่าสะสมเล็กน้อย เพื่อเป็นการป้องกันไว้ล่วงหน้าคุณอาจเพิ่มค่าเล็กน้อย (เช่น 1.0) ให้กับองค์ประกอบสุดท้าย แต่จากนั้นคุณจะต้องระบุอย่างชัดเจนในโค้ดของคุณว่ารายการนั้นเป็นรายการสุดท้าย
IM

1
ตัวแปรเล็ก ๆ ตัวหนึ่งเกี่ยวกับเรื่องนี้ฉันได้ใช้เป็นการส่วนตัวถ้าคุณต้องการให้ค่าน้ำหนักใน runtime ไม่เปลี่ยนเป็นค่า Weight-plus-all-previous คุณสามารถลบน้ำหนักของแต่ละรายการที่ส่งผ่านจากค่าสุ่มของคุณหยุดเมื่อ ค่าสุ่มน้อยกว่าน้ำหนักรายการปัจจุบัน (หรือเมื่อลบน้ำหนักทำให้ค่า <0)
Lunin

2
@ BlueRaja-DannyPflughoeft การเพิ่มประสิทธิภาพก่อนวัยอันควร ... คำถามเกี่ยวกับการเลือกวัตถุจากกล่องของขวัญเปิด ใครจะไปเปิด 1,000 กล่องต่อวินาที
IMil

4
@IMil: ไม่มีคำถามที่เป็นที่รับทั้งหมดทั่วไปสำหรับการเลือกรายการถ่วงน้ำหนักแบบสุ่ม สำหรับ lootboxes เฉพาะคำตอบนี้อาจจะเป็นเรื่องปกติเพราะมีจำนวนน้อยของรายการและน่าจะไม่เปลี่ยนแปลง( แต่เนื่องจากผู้ที่มักจะทำบนเซิร์ฟเวอร์ 1000 / วินาทีไม่สมจริงสำหรับเกมที่นิยม)
BlueRaja - Danny Pflughoeft

4
@opa จากนั้นตั้งค่าสถานะเพื่อปิดเป็นผู้ล่อลวง มันผิดจริงหรือไม่ที่จะยกเลิกคำตอบที่ดีเพียงเพราะคำถามนั้นถูกถามมาก่อน
Baldrickk

27

หมายเหตุ: ฉันสร้างห้องสมุด C # สำหรับปัญหาที่แน่นอนนี้

โซลูชันอื่น ๆ นั้นใช้ได้ถ้าคุณมีรายการจำนวนน้อยและความน่าจะเป็นของคุณจะไม่เปลี่ยนแปลง อย่างไรก็ตามด้วยรายการจำนวนมากหรือการเปลี่ยนแปลงความน่าจะเป็น(เช่นการลบรายการหลังจากเลือกพวกเขา)คุณจะต้องการสิ่งที่ทรงพลัง

ต่อไปนี้เป็นคำตอบทั่วไปสองประการ(ทั้งสองอย่างรวมอยู่ในไลบรารีด้านบน)

วิธี Alias ​​ของวอล์คเกอร์

ทางออกที่ฉลาดที่เร็วมาก( O(1)!)ถ้าความน่าจะเป็นของคุณคงที่ ในสาระสำคัญอัลกอริทึมสร้าง dartboard 2D ("นามแฝงตาราง") จากความน่าจะเป็นของคุณและโยนโผที่มัน

dartboard

มีบทความออนไลน์มากมายเกี่ยวกับวิธีการทำงานหากคุณต้องการเรียนรู้เพิ่มเติม

ปัญหาเดียวก็คือว่าหากความน่าจะเป็นของคุณเปลี่ยนไปคุณต้องสร้างตารางนามแฝงใหม่ซึ่งช้า ดังนั้นหากคุณต้องการลบรายการหลังจากเลือกแล้วนี่ไม่ใช่ทางออกสำหรับคุณ

วิธีแก้ปัญหาแบบทรี

วิธีแก้ปัญหาทั่วไปอื่น ๆ คือการสร้างอาร์เรย์ที่แต่ละรายการเก็บผลรวมของความน่าจะเป็นและรายการทั้งหมดไว้ข้างหน้า จากนั้นเพียงแค่สร้างตัวเลขสุ่มจาก [0,1) และทำการค้นหาแบบไบนารี่เพื่อหาว่าหมายเลขนั้นเข้าสู่รายการใด

วิธีแก้ปัญหานี้ง่ายในการเขียนโค้ด / ทำความเข้าใจ แต่การเลือกจะช้ากว่าวิธีของ Alias ​​และการเปลี่ยนแปลงความน่าจะเป็นยังคงO(n)อยู่ เราสามารถปรับปรุงได้โดยเปลี่ยนอาร์เรย์ให้เป็นแผนภูมิการค้นหาแบบไบนารีซึ่งแต่ละโหนดจะติดตามผลรวมของความน่าจะเป็นในรายการทั้งหมดในแผนผังย่อยของมัน จากนั้นเมื่อเราสร้างตัวเลขจาก [0,1) เราสามารถเดินลงไปตามต้นไม้เพื่อค้นหารายการที่มันแสดง

สิ่งนี้ให้เราO(log n)เลือกรายการและเปลี่ยนความน่าจะเป็น! ทำให้NextWithRemoval()เร็วมาก!

ผลที่ได้

ต่อไปนี้เป็นเกณฑ์มาตรฐานอย่างรวดเร็วจากไลบรารีด้านบนเปรียบเทียบวิธีทั้งสองนี้

         WeightedRandomizer Benchmarks | ต้นไม้ | ตาราง
-------------------------------------------------- ---------------------------------
เพิ่ม () x10000 + NextWithReplacement () x10: | 4 ms | 2 มิลลิวินาที
เพิ่ม () x10000 + NextWithReplacement () x10000: | 7 ms | 4 มิลลิวินาที
เพิ่ม () x10000 + NextWithReplacement () x100000: | 35 ms | 28 ms
(เพิ่ม () + NextWithReplacement ()) x10000 (interleaved) | 8 ms | 5403 มิลลิวินาที
เพิ่ม () x10000 + NextWithRemoval () x10000: | 10 ms | 5948 มิลลิวินาที

ดังนั้นอย่างที่คุณเห็นสำหรับกรณีพิเศษของความน่าจะเป็นคงที่ (ไม่เปลี่ยนแปลง) วิธีการ Alias ​​ของ Walker's จะเร็วกว่าประมาณ 50-100% แต่ในกรณีที่มีพลวัตมากขึ้นต้นไม้จะมีขนาดของคำสั่งเร็วกว่ามาก !


วิธีการแก้ปัญหาที่มีต้นไม้ขึ้นอยู่ยังช่วยให้เราดีเวลาทำงาน ( nlog(n)) เมื่อเรียงลำดับรายการโดยน้ำหนัก
Nathan Merrill

2
ฉันสงสัยในผลลัพธ์ของคุณ แต่นี่เป็นคำตอบที่ถูกต้อง ไม่แน่ใจว่าทำไมนี่ไม่ใช่คำตอบยอดนิยมการพิจารณาว่านี่เป็นวิธีที่ยอมรับได้จริงในการจัดการปัญหานี้
WHN

ไฟล์ใดที่มีโซลูชันแบบทรี ประการที่สองตารางมาตรฐานของคุณ: Walkers Alias ​​เป็นคอลัมน์ "table" หรือไม่
Yakk

1
@Yakk: รหัสสำหรับการแก้ปัญหาที่มีต้นไม้ขึ้นอยู่เป็นที่นี่ มันสร้างขึ้นเมื่อการดำเนินการเปิดแหล่งที่มาของAA-ต้นไม้ และ 'ใช่' สำหรับคำถามที่สองของคุณ
BlueRaja - Danny Pflughoeft

1
ส่วนวอล์คเกอร์นั้นเป็นเพียงลิงค์เดียว
สะสม

17

โซลูชัน Wheel of Fortune

คุณสามารถใช้วิธีนี้เมื่อความน่าจะเป็นในกลุ่มรายการของคุณมีตัวหารร่วมที่ค่อนข้างใหญ่และคุณต้องดึงมันออกมาบ่อยครั้ง

สร้างอาร์เรย์ของตัวเลือก แต่ให้แต่ละองค์ประกอบใส่เข้าไปหลาย ๆ ครั้งโดยมีจำนวนซ้ำขององค์ประกอบแต่ละส่วนตามสัดส่วนโอกาสที่จะปรากฏ สำหรับตัวอย่างข้างต้นองค์ประกอบทั้งหมดมีความน่าจะเป็นซึ่งเป็นตัวคูณ 5% ดังนั้นคุณสามารถสร้างอาร์เรย์ขององค์ประกอบ 20 แบบดังนี้

10 gold
sword
sword
sword
sword
shield
shield
shield
shield
shield
shield
shield
armor
armor
armor
armor
potion
potion

จากนั้นเพียงเลือกองค์ประกอบสุ่มของรายการนั้นโดยสร้างจำนวนเต็มหนึ่งตัวที่มีค่าระหว่าง 0 ถึงความยาวของอาร์เรย์ - 1

ข้อเสีย:

  • คุณต้องสร้างอาร์เรย์ในครั้งแรกที่คุณต้องการสร้างรายการ
  • เมื่อหนึ่งในองค์ประกอบของคุณควรมีความน่าจะเป็นต่ำมากคุณจะพบกับอาร์เรย์ขนาดใหญ่มากซึ่งอาจต้องใช้หน่วยความจำจำนวนมาก

ข้อดี:

  • เมื่อคุณมีอาเรย์แล้วและต้องการที่จะดึงมันออกมาหลาย ๆ ครั้งมันจะเร็วมาก เพียงหนึ่งจำนวนเต็มแบบสุ่มและเข้าถึงหนึ่งอาร์เรย์

2
ในฐานะที่เป็นโซลูชั่นไฮบริดเพื่อหลีกเลี่ยงข้อเสียที่สองคุณสามารถกำหนดช่องสุดท้ายเป็น "อื่น ๆ " และจัดการกับช่องทางอื่นเช่นวิธีการเรียงลำดับของ Philipp ดังนั้นคุณอาจจะเติมช่องที่ผ่านมากับการเสนอขายอาร์เรย์โอกาส 99.9% ของยาและเพียงแค่โอกาสที่ 0.1% Epic Scepter of the Apocalypseของ แนวทางสองระดับดังกล่าวใช้ประโยชน์จากข้อดีของทั้งสองวิธี
Cort Ammon

1
ฉันใช้รูปแบบนี้ค่อนข้างหลากหลายในโครงการของฉันเอง สิ่งที่ฉันทำคือการคำนวณแต่ละรายการ & น้ำหนักและเก็บไว้ในอาเรย์[('gold', 1),('sword',4),...], สรุปน้ำหนักทั้งหมดแล้วหมุนจำนวนสุ่มจาก 0 ถึงผลรวมแล้วย้ำอาร์เรย์และคำนวณที่ดินจำนวนสุ่ม (เช่น กreduce.) ทำงานได้ดีสำหรับอาร์เรย์ที่อัปเดตบ่อยและไม่มีหน่วยความจำหลัก

1
@Thebluefish วิธีการแก้ปัญหาที่อธิบายไว้ในคำตอบอื่น ๆ ของฉัน "การแก้ปัญหาความน่าจะเป็นรหัสอ่อนนุ่ม"
ฟิลิปป์

7

โซลูชันความน่าจะเป็นแบบตายตัว

วิธีที่ง่ายที่สุดในการหาไอเท็มแบบสุ่มจากคอลเลคชั่นที่มีน้ำหนักคือการสำรวจห่วงโซ่ของคำสั่ง if-else ที่แต่ละ if-else เพิ่มขึ้นอาจเป็นไปได้

int rand = random(100); //Random number between 1 and 100 (inclusive)
if(rand <= 5) //5% chance
{
    print("You found 10 gold!");
}
else if(rand <= 25) //20% chance
{
    print("You found a sword!");
}
else if(rand <= 70) //45% chance
{
    print("You found a shield!");
}
else if(rand <= 90) //20% chance
{
    print("You found armor!");
}
else //10% chance
{
    print("You found a potion!");
}

เหตุผลที่เงื่อนไขเท่ากับโอกาสของมันและโอกาสที่เงื่อนไขทั้งหมดก่อนหน้านี้เป็นเพราะเงื่อนไขก่อนหน้านี้ได้ตัดความเป็นไปได้ของรายการเหล่านั้นไปแล้ว ดังนั้นสำหรับเงื่อนไขของโล่else if(rand <= 70)70 เท่ากับโอกาส 45% ของโล่รวมทั้งโอกาสทอง 5% และโอกาสดาบ 20%

ข้อดี:

  • ง่ายต่อการเขียนโปรแกรมเพราะไม่จำเป็นต้องมีโครงสร้างข้อมูล

ข้อเสีย:

  • บำรุงรักษายากเพราะคุณต้องคงอัตราการดร็อปไว้ในโค้ดของคุณ คุณไม่สามารถระบุได้ในขณะใช้งานจริง ดังนั้นหากคุณต้องการหลักฐานเพิ่มเติมในอนาคตคุณควรตรวจสอบคำตอบอื่น ๆ

3
นี่คงเป็นเรื่องที่น่ารำคาญจริงๆ เช่นถ้าคุณต้องการเอาทองคำออกและทำให้ potion เป็นจุดคุณจำเป็นต้องปรับความน่าจะเป็นของรายการทั้งหมดระหว่างพวกเขา
Alexander - Reinstate Monica

1
เพื่อหลีกเลี่ยงปัญหาที่ @Alexander กล่าวถึงคุณสามารถลบอัตราปัจจุบันในแต่ละขั้นตอนแทนการเพิ่มไปยังแต่ละเงื่อนไข
AlexanderJ93

2

ใน C # คุณสามารถใช้การสแกน Linq เพื่อเรียกใช้การสะสมของคุณเพื่อตรวจสอบกับตัวเลขสุ่มในช่วง 0 ถึง 100.0f และ. First () เพื่อรับ เช่นเดียวกับรหัสหนึ่งบรรทัด

ดังนั้นสิ่งที่ชอบ:

var item = a.Select(x =>
{
    sum += x.prob;
    if (rand < sum)
        return x.item;
    else
        return null;
 }).FirstOrDefault());

sumเป็นจำนวนเต็มเริ่มต้นเป็นศูนย์และaเป็นรายการ prob / item structs / tuples / อินสแตนซ์ randเป็นตัวเลขสุ่มที่สร้างขึ้นก่อนหน้านี้ในช่วง

สิ่งนี้จะสะสมผลรวมเหนือรายการของช่วงจนกระทั่งมันเกินจำนวนสุ่มที่เลือกไว้ก่อนหน้านี้และส่งคืนรายการหรือโมฆะโดยที่โมฆะจะถูกส่งคืนหากช่วงเลขสุ่ม (เช่น 100) น้อยกว่าช่วงน้ำหนักรวมโดยไม่ได้ตั้งใจ และหมายเลขสุ่มที่เลือกนั้นอยู่นอกช่วงน้ำหนักรวม

อย่างไรก็ตามคุณจะสังเกตเห็นว่าน้ำหนักใน OP ใกล้เคียงกับการแจกแจงแบบปกติ (Bell Curve) ฉันคิดว่าโดยทั่วไปคุณจะไม่ต้องการช่วงที่เฉพาะเจาะจงคุณจะมีแนวโน้มที่จะต้องการการกระจายที่ลดลงอย่างใดอย่างหนึ่งรอบ ๆ เส้นโค้งระฆังหรือเพียงแค่ในเส้นโค้งเลขชี้กำลังลดลง (เช่น) ในกรณีนี้คุณสามารถใช้สูตรทางคณิตศาสตร์เพื่อสร้างดัชนีลงในอาร์เรย์ของรายการเรียงลำดับตามความน่าจะเป็นที่ต้องการ ตัวอย่างที่ดีคือ CDF ในการแจกแจงแบบปกติ

นอกจากนี้ยังมีตัวอย่างที่นี่

อีกตัวอย่างหนึ่งคือคุณสามารถใช้ค่าสุ่มจาก 90 องศาถึง 180 องศาเพื่อรับจตุภาคล่างขวาของวงกลมนำส่วนประกอบ x โดยใช้ cos (r) และใช้ดัชนีนั้นในรายการจัดลำดับความสำคัญ

ด้วยสูตรที่แตกต่างกันคุณสามารถมีวิธีการทั่วไปที่คุณเพียงแค่ป้อนรายการที่มีความสำคัญของความยาว (เช่น N) และแมปผลลัพธ์ของสูตร (เช่น: cos (x) คือ 0 ถึง 1) โดยการคูณ (เช่น: Ncos (x ) = 0 ถึง N) เพื่อรับดัชนี


3
คุณให้รหัสบรรทัดนี้กับเราได้ไหมถ้ามันเป็นเพียงหนึ่งบรรทัด ฉันไม่คุ้นเคยกับ C # ดังนั้นฉันไม่รู้ว่าคุณหมายถึงอะไร
HEGX64

@ HEGX64 ถูกเพิ่ม แต่ใช้มือถือและเครื่องมือแก้ไขไม่ทำงาน คุณสามารถแก้ไขได้ไหม
Sentinel

4
คุณสามารถเปลี่ยนคำตอบนี้เพื่ออธิบายแนวคิดที่อยู่เบื้องหลังมันได้หรือไม่แทนที่จะใช้ภาษาใดภาษาหนึ่งเป็นพิเศษ?
Raimund Krämer

@ RaimundKrämer Erm เสร็จแล้วเหรอ?
Sentinel

Downvote โดยไม่มีคำอธิบาย = ไร้ประโยชน์และต่อต้านสังคม
WGroleau

1

ความน่าจะเป็นไม่จำเป็นต้องเขียนรหัสยาก ไอเท็มและขีด จำกัด สามารถอยู่ร่วมกันในอาร์เรย์

for X in itemsrange loop
  If items (X).threshold < random() then
     Announce (items(X).name)
     Exit loop
  End if
End loop

คุณต้องสะสมเกณฑ์ไว้ แต่คุณสามารถทำได้เมื่อสร้างไฟล์พารามิเตอร์แทนการเข้ารหัส


3
คุณช่วยอธิบายวิธีการคำนวณเกณฑ์ที่ถูกต้องได้ไหม? ตัวอย่างเช่นหากคุณมีสามรายการที่มีโอกาส 33% แต่ละรายการคุณจะสร้างตารางนี้อย่างไร เนื่องจากสุ่มใหม่ () ถูกสร้างขึ้นในแต่ละครั้งครั้งแรกจะต้อง 0.3333 ครั้งที่สองจะต้อง 0.5 และสุดท้ายจะต้อง 1.0 หรือว่าฉันอ่านอัลกอริทึมผิด
ท่อ

คุณคำนวณวิธีที่คนอื่นทำในคำตอบของพวกเขา สำหรับความน่าจะเป็นที่เท่าเทียมกันของรายการ X ขีด จำกัด แรกคือ 1 / X, วินาที, 2 / X เป็นต้น
WGroleau

การทำเช่นนั้นสำหรับ 3 รายการในอัลกอริทึมนี้จะทำให้ขีด จำกัด 1/3, 2/3 และ 3/3 แต่ความน่าจะเป็นผลลัพธ์ 1/3, 4/9 และ 2/9 สำหรับรายการแรกรายการที่สองและสาม คุณหมายถึงการมีสายเรียกเข้าrandom()ในลูปจริงหรือ
ท่อ

ไม่นั่นเป็นข้อผิดพลาดอย่างแน่นอน การตรวจสอบแต่ละครั้งต้องใช้หมายเลขสุ่มที่เหมือนกัน
WGroleau

0

ฉันทำฟังก์ชันนี้แล้ว: https://github.com/thewheelmaker/GDscript_Weighted_Random ทันที! ในกรณีของคุณคุณสามารถใช้มันเช่นนี้:

on_normal_case([5,20,45,20,10],0)

มันให้ตัวเลขระหว่าง 0 ถึง 4 แต่คุณสามารถใส่มันลงในอาร์เรย์ที่คุณได้ไอเท็ม

item_array[on_normal_case([5,20,45,20,10],0)]

หรือในฟังก์ชั่น:

item_function(on_normal_case([5,20,45,20,10],0))

นี่คือรหัส ฉันทำมันใน GDscript คุณทำได้ แต่มันสามารถเปลี่ยนภาษาอื่น ๆ ได้ตรวจสอบข้อผิดพลาดทางตรรกะด้วย:

func on_normal_case(arrayy,transformm):
    var random_num=0
    var sum=0
    var summatut=0
    #func sumarrays_inarray(array):
    for i in range(arrayy.size()):
        sum=sum+arrayy[i]
#func no_fixu_random_num(here_range,start_from):
    random_num=randi()%sum+1
#Randomies be pressed down
#first start from zero
    if 0<=random_num and random_num<=arrayy[0]:
        #print(random_num)
        #print(array[0])
        return 0+ transformm
    summatut=summatut+arrayy[0]
    for i in range(arrayy.size()-1):
        #they must pluss together
        #if array[i]<=random_num and random_num<array[i+1]:
        if summatut<random_num and random_num<=summatut+arrayy[i+1]:
            #return i+1+transform
            #print(random_num)
            #print(summatut)
            return i+1+ transformm

        summatut=summatut+arrayy[i+1]
    pass

มันทำงานได้เช่นนี้: on_normal_case ([50,50], 0) สิ่งนี้ให้ 0 หรือ 1 มันมีความน่าจะเป็นเหมือนกันทั้งคู่

on_normal_case ([50,50], 1) สิ่งนี้ให้ 1 หรือ 2 มันมีความน่าจะเป็นเหมือนกันทั้งคู่

on_normal_case ([20,80], 1) สิ่งนี้ให้ 1 หรือ 2 มันมีการเปลี่ยนแปลงที่ใหญ่กว่าเพื่อรับสอง

on_normal_case ([20,80,20,20,30], 1) สิ่งนี้ให้ตัวเลขสุ่มในช่วง 1-5 และตัวเลขที่ใหญ่กว่ามีแนวโน้มมากกว่าตัวเลขที่เล็กกว่า

on_normal_case ([20,80,0,0,20,20,30,0,0,0,0,33], 45) สิ่งนี้โยนลูกเต๋าระหว่างหมายเลข 45,46,49,50,51,56 ที่คุณเห็นเมื่อมี เป็นศูนย์มันไม่เคยเกิดขึ้น

ดังนั้นฟังก์ชั่นจะส่งกลับตัวเลขสุ่มเพียงหนึ่งหมายเลขที่ขึ้นอยู่กับความยาวของอาเรย์อาร์เรย์และหมายเลขการแปลงและจำนวน ints ในอาเรย์เป็นค่าน้ำหนักความน่าจะเป็นที่จำนวนอาจเกิดขึ้นโดยที่หมายเลขนั้นคือตำแหน่งบนอาเรย์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.