如何保持许多“相关”参数的“计算”?建议如下:

8
我有几个指标需要“始终保持最新”。即当任何内容发生更改时,我需要重新计算“相关的”指标。我有几个级别,每个下一个级别只能在上一个级别计算完成后才能计算。让我通过这张亮丽的图片来解释一下:
在某些时候,假设法郎发生了变化。然后我们应该:
1. 计算法郎/第纳尔。 2. 计算法郎/第纳尔/比索。
或者,如果比索、法郎和第纳尔同时发生变化,那么我们应该:
1. 计算法郎/第纳尔。 2. 计算法郎/第纳尔/比索。 3. 计算比索+欧元/(欧元+美元)。
因此,无论何时在“0级”上发生任何变化,我们都应该重新计算所有其他级别。但是,
- 我们应该仅计算所需的项目。如果欧元发生变化,我们不需要重新计算法郎/第纳尔。 - 我们不应该计算超过一次。如果欧元和美元同时更改,我们只需要计算一次欧元+美元(而不是两次)。
最直接的解决方案是:
1. 将每个级别存储在数组中。 2. 对于数组中的每个项目,跟踪来自下一级的“侦听器”(可能会有困难,因为例如比索有来自不同级别的侦听器——从2级的法郎/第纳尔/比索和从第3级的比索+欧元/(欧元+美元),因此需要二维数组)。 3. 如果重新计算项目,则标记所有侦听器以进行重新计算。 4. 从第0级到最后一级,重新计算标记为需要重新计算的项目(最初更新的项目被标记为需要重新计算,例如比索)。
我想我的问题是众所周知的,并且您可能会向我提供通用的众所周知的解决方案。我不想重新发明轮子:) 谢谢!

如果所有项目都已经有级别,那么解决方案就很简单,但是它们是否有级别呢? - Dialecticus
是的,它们有级别。保证每个东西都在正确的级别上。这是由人类预先配置的 :) - Oleg Vazhnev
但是“levels”是我发明的。 我可以使用另一种结构。 例如,可能最好使用单维数组而不是双维数组。 这就是我的问题所在,我认为... - Oleg Vazhnev
数据流编程可以自动完成所有这些操作。以Clojure的基座框架为例看看。 - Charles Duffy
4个回答

2
我认为基于层级的方法是不错的,但前提是听众总是处于较低的水平。
思路:
在一个二维数组中包含实际数据,第一个索引是层级,第二个是该层级上的位置。让每个元素都有一个“willBeRecalculated”标志。
为每个层级(因此是列表的数组)设置一个“toBeRecalculated”的列表。
对于每个元素,都有一个包含2个整数的元素列表(即听众),一个用于层级,一个用于索引。
对于要修改的每个元素,请将该元素添加到适当层级的“toBeRecalculated”中,并将“willBeRecalculated”设置为true。
然后从第一个到最后一个层级遍历“toBeRecalculated”,重新计算每个元素,将其“willBeRecalculated”设置为false,并查找适用的元素,如果“willBeRecalculated”为true,则不执行任何操作,否则,将其(听众的)“willBeRecalculated”设置为true,并将其添加到“toBeRecalculated”中。
这种方法不需要遍历所有数据来检查需要修改/已经修改的内容,它仅检查适用的元素,而且没有重复计算。
示例:
[[E, U, P, F, D],
 [E+U, F/D],
 [E/E+D, F/D/P],
 [P+E/E+U]
]

听众:

E:[(1,0), (2,0)] // E+U and E/E+U
U:[(1,0)] // E+U
P:[(2,1), (3,0)]
F:[(1,1)]
D:[(1,1)]
E+U:[(2,0)]
F/D:[(2,1)]
E/E+U:[(3,0)]

修改 EU:

EU添加到toBeRecalculated [0]中,并为两者设置willBeRecalculated为true。

遍历toBeRecalculated [0]

修改E时,将其willBeRecalculated设置为false,并将E+UwillBeRecalculated设置为true并将其添加到toBeRecalculated [1]中,并将E/E+UwillBeRecalculated设置为true并将其添加到toBeRecalculated [2]中。

修改U时,将其willBeRecalculated设置为false,然后我们检查E+UwillBeRecalculated并看到它为true,因此什么也不做。

然后遍历toBeRecalculated [1]。修改E+U时,将其willBeRecalculated设置为false,并检查E/E+UwillBeRecalculated并看到它为true,因此什么也不做。

注意:

将监听器指针指向元素而不是级别和索引变量可能更好。


这是一个“直截了当”的解决方案。如果我找不到更好的东西,我会使用它。 - Oleg Vazhnev
是的,我想这与直接解决方案非常相似,只是在“从0级到最后一级…”步骤中,它不需要遍历所有节点来检查哪些需要重新计算,因为它会保留一个单独的列表,用于记录需要重新计算的节点。它提供了处理多个级别上的跟踪侦听器的替代解决方案,并使结构和过程更加具体。 - Bernhard Barker

2

当你说你正在处理级别时,脑海中会想到某种树形数据结构。

但是对于你的问题,我认为建模一种有向无环图可能会起作用。

你的图可能看起来像这样(所有方向都向下):

                             root 
                   /     /     |     \     \
                 E      U      P     F      D
                 \     /
                  \   /
              (Euro + Usd)

如果你像遍历树型数据结构一样遍历这个数据,那么你每次更新货币时都会准确地更新每个转换率一次。


1
我认为它不是“树形结构”,因为还有一个“根节点”(请参见问题陈述中的图片)。 - Annie Kim
1
@AnnieKim 一棵树仍然可以有效地使用。您始终可以创建一个没有值的根节点。然后,所有0级条目将成为根节点的子节点。请参见编辑。 - Joel
1
但是你如何处理 Franc / Dinar / Peso,它有两个父母,不像一棵 只有一个。 - Annie Kim
1
@Joel 这个问题让我想到了汇率 - 有第纳尔和法郎,然后两者之间有一个转换率。第纳尔和法郎的价值并不相互依赖,但是转换率却取决于两者的价值。 - Bernhard Barker
@Joel,我能理解你的大部分想法,但我仍然认为你的解决方案或解释还是太粗糙了。而且你也没有说如何避免一个节点的多次更新。 - Annie Kim
显示剩余5条评论

0

我认为你可以在这里使用多态性。 有一个货币列表,每个货币都持有一个指向所有依赖元素(基类)的指针向量。

基类强制它们包含一个函数update(),每次更新当前货币时都会调用该函数。

依赖元素反过来又有每种货币的指针,并使用这些指针在其update()实现中更新自己。

#include<iostream>
#include <vector>
class c_node_combi_base;
class currency
{
  std::vector<c_node_combi_base*> m_dependant;
  double m_val;
public:
  double value (void) const { return m_val; }
  void reg (c_node_combi_base * p) { m_dependant.push_back(p); }
  void update (double val);
};
class c_node_combi_base
{
  std::vector<currency*> currencies;
public:
  virtual void update (void) = 0;
};

template<size_t N, typename OP> // templated to differentiate types of nodes
class currency_node : public c_node_combi_base 
{ 
};

struct divide_d 
{
  double operator() (const double x, const double y) const {return x/y;}
};

template<typename OPT> // node type 2
class currency_node<2u, OPT> 
  : public c_node_combi_base
{
  currency *A, *B;
  OPT _op;
  double m_val;
public:
  currency_node (currency * a, currency * b)
    : A(a), B(b), _op(), m_val(_op(A->value(), B->value())) 
  {
    A->reg(this);
    B->reg(this);
  }
  void update (void) 
  { 
    m_val = _op(A->value(), B->value());
  }

  double value (void) { return m_val; }

};

void currency::update (double value) 
{
  m_val = value; 
  for (size_t i=0; i<m_dependant.size(); ++i)
  {
    m_dependant[i]->update();
  }
}

这使得:

int main (void)
{
  currency franc, dinar;
  franc.update(9.9);
  dinar.update(3.3);
  currency_node<2, divide_d> franc_dinar(&franc, &dinar);
  std::cout << franc_dinar.value() << std::endl;
  dinar.update(1.1); // updates franc_dinar automatically
  std::cout << franc_dinar.value() << std::endl;
}

打印:

3
9


也许你可以使用一个 std::vector<std::weak_ptr> 来存储你的货币,而每个节点则持有每种货币的 std::shared_ptr,这样只有在没有节点引用它们时,这些货币才会超出作用域/被销毁。

0

你所描述的内容可以在响应式编程语言中轻松实现。

Qt的QML还提供了一种属性绑定机制,可用于UI。

查看Qt属性绑定和其他响应式语言的实现可能会给您一些实现思路。

Wikipedia页面列出了许多语言中用于响应式编程的库,包括Javascript、.NET、Python、Java、C++等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接