公式计算,分量累加计算优化

问题

计算公式 f = (base + genconst) * percent * genpercent + const
公式中的运算变量均为长度为 26 的一维向量。 在游戏卡牌中,假如有 20 个养成系统, 会对公式中的某个或多个部分提供一个加成,在 20 个系统加成完成后, 计算出最终的结果。

现有计算方式

在一个函数中, 创建与公式对应的变量, 依次计算 20 个养成系统的加成并将养成累加到对应的变量, 最终得到结果。
这样的计算方式,非常简单直观。 但存在两个的问题:

  1. 当只有某个养成系统变化时,为了得到结果, 你需要完整的把 20 个都跑一遍
  2. 当其中某个养成计算结果比较费时时,你需要对特定的养成做缓存来提高计算的效率

优化计算方式

将公式拆解成一颗树的形式,如下所示:
属性优化示意图

每个养成系统将自己的值放到对应的节点, 当某个养成系统有改动时,去设置对应节点的值,然后改动以增量的形式沿着路径往上走,直到根节点,这样根节点的值就是最新的,同时也不会引起到其他节点的计算。

这样就很好的处理了现有计算方式的两个问题, 计算时间减少 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>\n%s: %s\n' % (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))

posted on 2019-03-07 16:52  nowg  阅读(473)  评论(0)    收藏  举报

导航