我发现an answer on SO解释了如何为游戏编写随机加权掉落系统。我会更喜欢以更多功能编程风格编写这段代码,但我无法想办法为这段代码做到这一点。我将在这里内联伪代码:
R = (some random int);
T = 0;
for o in os
T = T + o.weight;
if T > R
return o;
如何以更实用的风格编写?我正在使用CoffeeScript和underscore.js,但我更喜欢这个答案是语言不可知的,因为我无法以功能的方式思考。
答案 0 :(得分:3)
以下是Clojure和JavaScript中的两个功能版本,但这里的想法应该适用于任何支持闭包的语言。基本上,我们使用递归而不是迭代来完成同样的事情,而不是在中间断开,我们只返回一个值并停止递归。
原始伪代码:
R = (some random int);
T = 0;
for o in os
T = T + o.weight;
if T > R
return o;
Clojure版本(对象只被视为clojure贴图):
(defn recursive-version
[r objects]
(loop [t 0
others objects]
(let [obj (first others)
new_t (+ t (:weight obj))]
(if (> new_t r)
obj
(recur new_t (rest others))))))
JavaScript版本(为方便起见使用下划线)。 小心,因为这可能会炸掉堆栈。 这在概念上与clojure版本相同。
var js_recursive_version = function(objects, r) {
var main_helper = function(t, others) {
var obj = _.first(others);
var new_t = t + obj.weight;
if (new_t > r) {
return obj;
} else {
return main_helper(new_t, _.rest(others));
}
};
return main_helper(0, objects);
};
答案 1 :(得分:2)
您可以使用折叠(aka Array#reduce
或Underscore的_.reduce
)实现此目的:
SSCCE:
items = [
{item: 'foo', weight: 50}
{item: 'bar', weight: 35}
{item: 'baz', weight: 15}
]
r = Math.random() * 100
{item} = items.reduce (memo, {item, weight}) ->
if memo.sum > r
memo
else
{item, sum: memo.sum + weight}
, {sum: 0}
console.log 'r:', r, 'item:', item
你可以多次at coffeescript.org运行它,看看结果是否有意义:)
话虽这么说,我发现折叠有点人为,因为你必须记住所选项目和迭代之间的累积权重,并且当项目为时它不会短路找到。
可以考虑在纯FP和重新实现查找算法的繁琐之间进行折衷解决(使用_.find
):
total = 0
{item} = _.find items, ({weight}) ->
total += weight
total > r
我发现(没有双关语)这个算法比第一个算法更容易访问(并且它应该表现得更好,因为它不会创建中间对象,并且它会短路)。
更新/侧注:第二个算法不是“纯”,因为传递给_.find
的函数不是referentially transparent(它具有修改外部total
变量的副作用),但是整个算法是引用透明的。如果要将其封装在findItem = (items, r) ->
函数中,则该函数将是纯函数,并且将始终为同一输入返回相同的输出。这是一件非常重要的事情,因为这意味着你可以在使用一些非FP构造(性能,可读性或任何原因)的同时获得FP的好处:D
答案 2 :(得分:0)
我认为基础任务是从数组os
中随机选择“事件”(对象),其频率由各自的weight
定义。该方法是将随机数(具有均匀分布)映射(即搜索)到阶梯累积概率分布函数上。
对于正权重,它们的累积总和从0增加到1.您给我们的代码只是从0结尾开始搜索。要通过重复调用最大化速度,请预先计算总和,并对事件进行排序,以便最大权重为第一。
无论您是使用迭代(循环)还是递归进行搜索,都无关紧要。递归在一种试图“纯功能”的语言中是很好的,但却无助于理解潜在的数学问题。它并没有帮助您将任务打包成一个干净的功能。 underscore
函数是打包迭代的另一种方法,但不会更改基本功能。 any只有在找到目标时才会提前退出。
对于小型all
数组,此简单搜索就足够了。但是对于大型数组,二进制搜索会更快。查看os
我发现underscore
使用此策略。从sortedIndex
(Lo-Dash
dropin),“使用二进制搜索来确定应将值插入数组的最小索引,以便维护已排序数组的排序顺序”
underscore
的基本用法是:
sortedIndex
您可以使用嵌套函数隐藏累积和计算,例如:
os = [{name:'one',weight:.7},
{name:'two',weight:.25},
{name:'three',weight:.05}]
t=0; cumweights = (t+=o.weight for o in os)
i = _.sortedIndex(cumweights, R)
os[i]
在coffeescript中,Jed Clinger的递归搜索可以这样写:
osEventGen = (os)->
t=0; xw = (t+=y.weight for y in os)
return (R) ->
i = __.sortedIndex(xw, R)
return os[i]
osEvent = osEventGen(os)
osEvent(.3)
# { name: 'one', weight: 0.7 }
osEvent(.8)
# { name: 'two', weight: 0.25 }
osEvent(.99)
# { name: 'three', weight: 0.05 }
使用相同基本思想的循环版本是:
foo = (x, r, t=0)->
[y, x...] = x
t += y
return [y, t] if x.length==0 or t>r
return foo(x, r, t)
测试jsPerf http://jsperf.com/sortedindex
建议foo=(x,r)->
t=0
while x.length and t<=r
[y,x...]=x # the [first, rest] split
t+=y
y
在sortedIndex
大约为1000时更快,但在长度更接近30时比简单循环慢。