nowg

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

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


问题

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

现有计算方式

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

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

优化计算方式

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

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

这样就很好的处理了现有计算方式的两个问题, 但引入了额外的内存消耗, 但在可接受范围,在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 阅读(...) 评论(...) 编辑 收藏

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-10-16
  • 2022-12-23
  • 2021-06-26
  • 2021-08-22
  • 2022-12-23
猜你喜欢
  • 2021-10-09
  • 2021-09-30
  • 2021-10-31
  • 2022-12-23
  • 2021-10-14
相关资源
相似解决方案