简体   繁体   中英

suggestions how to keep “calculated” a lot of “dependent” parameters

I have several indicators which need to be "always up to date". Ie when anything is changed I need to recalculate "dependent". I have several levels where each next level should be calculated only when previous level is calculated. Let me explain by this shining picture:

设计

At some point Assume that Franc changed. Then we should:

  1. calc Franc / Dinar
  2. calc Franc / Dinar / Peso

Or, if Peso, Franc and Dinar are changed all at once then we should:

  1. calc Franc / Dinar
  2. calc Franc / Dinar / Peso
  3. calc Peso + Euro / (Euro + Usd)

So whenever anything at the Level 0 is chagned we should recalculate all other levels. But

  • we should calculate only required items. If Euro changed we do not need to recalculate Franc / Dinar
  • we should not calculate anything more than once. if Euro and Usd are changed at once we should calculate Euro + Usd only once (not twice).

The most straightforward solution would be:

  • store each level in array
  • for each item in array track "listeners" from next levels (could be difficulty because for example Peso has listeners from different levels - Franc / Dinar / Peso from Level2 and Peso + Euro / (Euro + Usd) from Level 3, so two-dimmension array is required..)
  • if item is recalculated then mark all it listeners to be recalculated too
  • go from Level 0 to last level and recalculate items that marked to be recalculated (initially updated items are market to be recalculated, for example Peso).

I guess that my problem is kind of well-known and probably you can suggest me general well-known solution. I don't want to reinvent the wheel :) Thanks!

I think the level-based approach is decent, on the assumption that listeners are always on a lower level.

The idea:

Have a 2D array containing your actual data, the first index is the level, the second is the position on the level. Let each element have a willBeRecalculated flag.

Have a toBeRecalculated list for each level (so an array of lists).

For each element, have a list of elements (the listeners) containing 2 integers - one for level and one for index.

For each element to be modified, add the element to toBeRecalculated on the appropriate level and set willBeRecalculated to true.

Then go through toBeRecalculated from the first to the last level, recalculating each element, setting its willBeRecalculated to false and, for each listener, looking up the applicable element, if willBeRecalculated is true, do nothing, otherwise, set willBeRecalculated to true and add it to toBeRecalculated on its (the listener's) level.

This approach doesn't go through all the data to check what needs to be modified / has been modified, it only checks applicable elements, and there are no repeated calculations.

Example:

For this:

(For my abbreviations I simply took the first letter of each word. I'm using 0-indexed arrays)

Actual data:

[[E, U, P, F, D],
 [E+U, F/D],
 [E/E+D, F/D/P],
 [P+E/E+U]
]

Listeners:

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)]

Modifying E and U :

Add E and U to toBeRecalculated[0] and set willBeRecalculated to true for both.

Go through toBeRecalculated[0] .

When modifying E , set willBeRecalculated to false for it and set E+U 's willBeRecalculated to true and add it to toBeRecalculated[1] and set E/E+U 's willBeRecalculated to true and add it to toBeRecalculated[2] .

When modifying U , set willBeRecalculated to false for it and we check E+U 's willBeRecalculated and see it's true so do nothing.

Then go through toBeRecalculated[1] . When modifying E+U , set willBeRecalculated to false for it and check E/E+U 's willBeRecalculated and see it's true so do nothing.

Note:

It might be better to have the listeners be pointers to the elements instead of a level and an index variable.

Well, when you say you are dealing with levels, some sort of tree data structure comes to mind..

But for your problem, I think it would work to model some sort of directed acyclic graph.

Your graph might look something like this (all directions are downward)..

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

If you traverse this like you would a tree data structure, you will update each conversion rate exactly once for each update to the currency.

What you are describing can easily be accomplished in a reactive programming language.

The QML of Qt also provides a property binding mechanism that accomplishes this for a UI.

Looking at the implementations of Qt property bindings and other reactive languages may give you some implementation ideas.

The Wikipedia page identifies libraries for reactive programming in Javascript, .NET, Python, Java, C++, and many other languages as well.

I think you can use polymorphism here. Have a list of Currencies, each of which holds a vector with pointers (to the base class) of all depedant elements.

The base class forces them to include a function update() which is called everytime the current currency is updated.

The depedant elements in turn have pointers for each currency they depend on and use these to update themselves in their update() implementation.

#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();
  }
}

This enables:

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;
}

Printing:

3
9


Perhaps you can have a std::vector<std::weak_ptr> of your currencies while each node holds a std::shared_ptr for each currency so the currencies may not got out of scope/be destroyed unless there are no more nodes refering to them

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM