【问题标题】:Simplification of LootTable probability distributionLootTable 概率分布的简化
【发布时间】:2015-09-22 07:46:42
【问题描述】:

我为我正在开发的一个爱好游戏创建了一个简单的 LootTable 类,它运行良好。但是,我很清楚代码中存在一个缺陷。当我说缺陷时,我实际上是指:可以改进/简化以减轻处理/计算成本的实现领域。我将尽我所能解释这一点,在此之前,这是我的 LootTable 类的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using DreamforceFramework.Framework.Game.Logic.Structs;
using DreamforceFramework.Framework.Probability;

namespace DreamforceFramework.Framework.Game.Probability
{
    public class LootTable
    {
        public string Name;
        private readonly List<string> _lootTable;
        private readonly Dictionary<string, int> _cachedLoot;
        private bool _isRebuildRequired;

        public LootTable()
        {
            _cachedLoot = new Dictionary<string, int>();
            _lootTable = new List<string>();
        }

        public LootTable(string name)
        {
            this.Name = name;
            _cachedLoot = new Dictionary<string, int>();
            _lootTable = new List<string>();
        }

        public void Add(string name, int probability)
        {
            this._cachedLoot.Add(name, probability);
            _isRebuildRequired = true;
        }

        public bool Contains(string name)
        {
            return _cachedLoot.ContainsKey(name);
        }

        public void Add(LootTableItem item)
        {
            this._cachedLoot.Add(item.Name, item.Rarity);
            _isRebuildRequired = true;
        }

        public void Add(List<LootTableItem> items)
        {
            foreach (LootTableItem lootTableItem in items)
            {
                this._cachedLoot.Add(lootTableItem.Name, lootTableItem.Rarity);
            }
            _isRebuildRequired = true;
        }

        public void Remove(string name)
        {
            this._cachedLoot.Remove(name);
            _isRebuildRequired = true;
        }

        public double ComputeProbability(string name)
        {
            double total = _cachedLoot.Values.Sum(n => n);
            double percent = _cachedLoot[name] / total;
            return Math.Round(percent * 100, 2);
        }

        public void Edit(string name, int newProbability)
        {
            this._cachedLoot[name] = newProbability;
            _isRebuildRequired = true;
        }

        public void Clear()
        {
            this._cachedLoot.Clear();
            this._isRebuildRequired = true;
        }

        private void Rebuild()
        {
            _lootTable.Clear();
            foreach (KeyValuePair<string, int> pair in _cachedLoot)
            {
                for (int i = 0; i < pair.Value; i++)
                {
                    _lootTable.Add(pair.Key);
                }
            }
            _isRebuildRequired = false;
        }

        public string Next()
        {
            if (_isRebuildRequired)
            {
                this.Rebuild();
            }
            return _lootTable[DreamforceRandom.NextInteger(_lootTable.Count)];
        }

        public List<string> Next(int quantity)
        {
            List<string> returnList = new List<string>();
            if (_isRebuildRequired)
            {
                this.Rebuild();
            }
            for (int i = 0; i < quantity; i++)
            {
                returnList.Add(_lootTable[DreamforceRandom.NextInteger(_lootTable.Count)]);
            }
            return returnList;
        }
    }
}

还有 LootTableItem 结构:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DreamforceFramework.Framework.Game.Logic.Structs
{
    public struct LootTableItem
    {
        public string Name;
        public int Rarity;
        public LootTableItem(string name, int rarity)
        {
            this.Name = name;
            this.Rarity = rarity;
        }
    }
}

对于那些看过上面代码的人来说,你会看到我所说的低效区域。为了生成内部战利品表,我创建了一个字符串列表,该列表等于该项目的稀有度。所以说我在战利品表中放了一把“生锈剑”,稀有度为 20。这意味着当战利品表重建时,它会在表中添加 20 个“生锈剑”字符串。没什么大不了的吧?但是现在可以说我正在添加两个对象。我将值为 100 的“Ruby”和一个值为 100 的“Emerald”添加到战利品表中。好吧,这意味着我将在战利品表中创建 200 个字符串,当它可以简化为添加 1 个 Ruby 字符串和 1 个 Emerald 字符串时,这是非常愚蠢的。这将达到相同的概率,即 50/50。

所以我的问题是:如何简化项目被添加到 LootTable 的概率,以便它自动优化数据,而不是创建一个庞大的字符串列表。

我希望我解释得足够清楚,我有时在书面表达方面相当缺乏。

编辑:

这是所选答案提出的可行解决方案: http://pastebin.com/4w0B0V6y

【问题讨论】:

    标签: c# optimization probability


    【解决方案1】:

    您的解决方案对于检索操作(Next 方法)具有最佳的 O(1) 时间复杂度,但正如您所提到的,它占用了大量空间。正如您还提到的,空间最终可以通过查找和消除最大公约数来优化,但这是一项复杂的任务,如果物品稀有度相对较高,也不起作用。因此,我将向您展示一个具有最佳 O(N) 空间复杂度(其中 N 是表中项目数)和 的解决方案检索操作的 O(log2(N)) 时间复杂度。

    假设我们在表格中有以下项目:

    Name         Rarity  
    ============ ======  
    Rusty Sword      20  
    Ruby            100  
    Emerald         100  
    

    我们可以这样看:

    Name         Total Range
    ============ ===== ========
    Rusty Sword     20 [0-19]
    Ruby           120 [20-119]
    Emerald        220 [120-219]
    ------------ -----
    Total          220
    

    底部的总数代表您实现中的_lootTable.Count,而对于该项目,它是您当时添加的计数的运行总和。因此,在 [0, Total-1] 范围内有一个随机数,我们需要找到包含该数字的项目的索引,这可以使用二分搜索轻松完成(因此在 Log2 时间)。

    你可以这样做:

    首先,将_lootTable成员替换为以下成员

    private List<string> _lootName = new List<string>();
    private List<int> _lootTotal = new List<int>();
    private int _total;
    

    然后改Rebuild方法

    private void Rebuild()
    {
        _lootName.Clear();
        _lootTotal.Clear();
        _total = 0;
        foreach (var item in _cachedLoot)
        {
            _total += item.Value;
            _lootName.Add(item.Key);
            _lootTotal.Add(_total);
        }
        _isRebuildRequired = false;
    }
    

    添加一个辅助函数来封装逻辑并相应地更新Next方法

    private string NextCore()
    {
        Debug.Assert(_cachedLoot.Count > 0 && !_isRebuildRequired); // Preconditions
        int total = DreamforceRandom.NextInteger(_total);
        int index = _lootTotal.BinarySearch(total);
        if (index < 0)
            index = ~index;
        else
            index++;
        return _lootName[index];
    }
    
    public string Next()
    {
        if (_cachedLoot.Count == 0) return null; // Sanity check
        if (_isRebuildRequired)
        {
            this.Rebuild();
        }
        return NextCore();
    }
    
    public List<string> Next(int quantity)
    {
        var returnList = new List<string>();
        if (_cachedLoot.Count == 0) return returnList; // Sanity check
        if (_isRebuildRequired)
        {
            this.Rebuild();
        }
        for (int i = 0; i < quantity; i++)
        {
            returnList.Add(NextCore());
        }
        return returnList;
    }
    

    然后就可以了。希望对您有所帮助。

    【讨论】:

    • 看起来很棒,而且解释得很好。
    • 您的示例代码给我带来了很多错误。我今天尝试全部实现;似乎您可能混淆了一些类型和成员变量,例如在您提出的 Rebuild() 方法的新建议中。我现在正试图弄清楚你的意图。不过,我们将不胜感激。
    • 呃,没关系。我想到了。使用工人阶级解决方案更新原始帖子,以防将来有人偶然发现此线程。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-30
    相关资源
    最近更新 更多