问题
计算公式 f = (base + genconst) * percent * genpercent + const
公式中的运算变量均为长度为 26 的一维向量。 在游戏卡牌中,假如有 20 个养成系统, 会对公式中的某个或多个部分提供一个加成,在 20 个系统加成完成后, 计算出最终的结果。
现有计算方式
在一个函数中, 创建与公式对应的变量, 依次计算 20 个养成系统的加成并将养成累加到对应的变量, 最终得到结果。
这样的计算方式,非常简单直观。 但存在两个的问题:
- 当只有某个养成系统变化时,为了得到结果, 你需要完整的把 20 个都跑一遍
- 当其中某个养成计算结果比较费时时,你需要对特定的养成做缓存来提高计算的效率
优化计算方式
将公式拆解成一颗树的形式,如下所示:
每个养成系统将自己的值放到对应的节点, 当某个养成系统有改动时,去设置对应节点的值,然后改动以增量的形式沿着路径往上走,直到根节点,这样根节点的值就是最新的,同时也不会引起到其他节点的计算。
这样就很好的处理了现有计算方式的两个问题, 计算时间减少 97%, 虽然引入了额外的内存消耗, 但在可接受范围,在python 中不要忘记用 slots 优化内存。
demo 代码:
#!/usr/bin/python
# -*- coding: utf-8 -*-
try:
from game.object import AttrDefs
# for test
except ImportError:
AttrMaxID = 26 + 1
else:
AttrMaxID = AttrDefs.attrTotal + 1
import numpy as np
def dict2attrs(d):
value = np.zeros(AttrMaxID)
for i, attr in enumerate(AttrDefs.attrsEnum):
if attr:
value[i] = d.get(attr, 0)
return np.array(value)
def attrs2dict(attr):
d = {}
for i, v in enumerate(attr):
if v:
d[AttrDefs.attrsEnum[i]] = v
return d
class AttrContext(object):
'''simple context'''
def __init__(self, game, scene=0, **kwargs):
self.game = game
self.scene = scene
self.__dict__.update(kwargs)
# TODO: use statement with to achieve a temp context
class Node(object):
__slots__ = ("name", "ret", "_adds", "_parent", "left", "right", "tag")
def __init__(self, name, tag=None, parent=None, left=None, right=None):
self.name = name
self.ret = np.zeros(AttrMaxID)
self._adds = {}
self._parent = parent
self.left = left
self.right = right
self.tag = tag
def __str__(self):
return '<Node object at 0x%x>
%s: %s
' % (id(self), self.name, tuple(self.ret))
def addLeft(self, node):
self.left = node
node._parent = self
node.tag = 'l'
def addRight(self, node):
self.right = node
node._parent = self
node.tag = 'r'
def set(self, k, v):
d = v - self._adds.get(k, 0)
if any(d):
self._adds[k] = v
self.ret += d
self.onChange(d)
def onChange(self, d):
if self._parent:
self._parent.change(d, self.tag)
def change(self, d, tag):
if tag == 'l':
v = self.right.ret
else:
v = self.left.ret
if self.name == '*':
d1 = v*d
elif self.name == '+':
d1 = d
if any(d1):
self.ret += d1
self.onChange(d1)
class Calculator(object):
def __init__(self, ctx):
self.ctx = ctx
self._nodes = {}
self._ft = self.buildFTree()
self.init()
# set default value
def init(self):
self.percent.set('default', np.ones(AttrMaxID))
self.genpercent.set('default', np.ones(AttrMaxID))
def __getattr__(self, name):
try:
return self._nodes[name]
except KeyError:
raise AttributeError("%s instance has no attribute '%s'" % (self.__class__.__name__, name))
def buildFTree(self, f):
root = None
p = Node('+')
root = p
base = Node('base')
genconst = Node('genconst')
percent = Node('percent')
genpercent = Node('genpercent')
const = Node('const')
self._nodes['base'] = base
self._nodes['genconst'] = genconst
self._nodes['percent'] = percent
self._nodes['genpercent'] = genpercent
self._nodes['const'] = const
p.addRight(const)
tn = Node('*')
p.addLeft(tn)
p = tn
p.addRight(genpercent)
tn = Node('*')
p.addLeft(tn)
p = tn
p.addRight(percent)
tn = Node('+')
p.addLeft(tn)
p = tn
p.addLeft(base)
p.addRight(genconst)
return root
@property
def result(self):
return attrs2dict(self._ft.ret)
# show formula
def showf(self):
f = []
def show(p, f):
if p.name in '+*':
f.append('(')
if p.left:
show(p.left, f)
f.append(p.name)
if p.right:
show(p.right, f)
if p.name in '+*':
f.append(')')
show(self._ft, f)
print(''.join(f))