简体   繁体   中英

C# recursively calculate value

I have following table

在此处输入图片说明

I need to work out the values in columns X, Y and Z.

Rule 1: code only has children as X, Y, or Z.

Let's start from a simple one, row 12, code 'E2' has a child called 'Z' and value is 3.30, so cell [F12] (column Z) should have 3.30. We can denote as f(12)= 3.30Z, Similarly, cell [E11] should be 2.10, or f(11)=2.10Y. For row 6, cell[D6] should be 1.14, denote as f(6)=1.14X.

note, code X,Y,Z do not have children themselves. However, other children can have grand children, recursively.

Rule 2: If a code has a child, then the value will be its own value times whatever the child code value.

eg row 10, it has a child 'E2', so the value will be its value 3.10 times whatever the children (E2) value, ie f(10)=3.10*f(12)=3.10*3.30Z=10.23Z, cell[F10] should be 10.23

Rule 3, if a code has multiple children, then the value will be sum for all the children group by type, following Rule 2

eg row 5 C1 has a child D, and D has three children (X, E1, E2), the 3 children values needs to add together. f(5)=1.70*(f(8)+f(9)+f(10)). Of course, some children has X values, some has Y values, some has Z values, so when adding them up, they need to be grouped by value type, and then fill in the corresponding columns Cell[D5], Cell[E5], cell[F5]

Hope that makes sense.

This is a simplified version of the original more complicated question which has thousands of rows, and some other calculations. But not a huge table, so performance or speed is not priority consideration.

It is more of an algorithm issue, but I prefer to implement in C#

What you've described is a tree structure, which you can build easily enough, and then traverse recursively. You already know the parent/child relationships; you just have to create a model to represent them in code.

Start with an Element class that looks like this:

class Element
{
    string Code;
    Element Parent;  // null if no parent
    double Value;
    double CalculatedValue;
    List<string> Children;
}

And create a dictionary of these, keyed by code:

Dictionary<string, Element> Elements;

Go through the list, adding creating an Element for each unique element, and adding it to the dictionary:

(Note: code examples are in pseudo-C#. I don't want to get bogged down in syntax here.)

for each row
{
    Element e;

    // if the element doesn't exist in the dictionary, create and add it
    if (!Elements.TryGetValue(code, out e))
    {
        e = new Element(code, value);
        Elements.Add(code, e);
    }
    if (child != null)
    {
        e.Children.Add(child);
    }
}

You now have a dictionary of elements, with the child relationships. You need to create the parent relationships. Easiest way is with a scan of the dictionary:

for each e in Elements
    for each child in e.Children
        if (child != "X" or "Y" or "Z")
            Elements[child].Parent = e;

Now what you have is a forest: one or more unrooted trees. You can scan it recursively to compute your value:

double totalValue = 0;
for each e in Elements
    if (e.Parent == null)
    {
        totalValue += calculateElementValue(e);
    }
}
// at this point, totalValue should contain the total value

If I understood your rules correctly, this method should compute an individual element's value. I assumed that an element has only one of X, Y, or Z. This is a simple depth-first traversal of an element tree.

double CalculateElementValue(Element e)
{
    double eValue = 0;
    double childrenValue = 0;
    for each child in e.Children
    {
        switch (child)
        {
            case "X": eValue = e.Value * xValue; break;
            case "Y": eValue = e.Value * yValue; break;
            case "Z": eValue = e.Value * zValue; break;
            else
            {
                childrenValue += CalculateElementValue(Elements[child]);
            }
        }
    }
    return eValue * childrenValue;
}

You could include the parent assignment in the initial loop that builds the elements, but it complicates things a little bit. But unless the table has lots of rows, you probably won't notice a difference in speed.

Here are some example data structures you can use to represent your data, and recursively calculate values.

Note that this example uses indirect recursion . Ie Node.Evaluate() does not call itself directly, but instead calls WeightedChild.Evaluate() which in turn calls back to Node.Evaluate() .

public class Node
{
    public Node(params WeightedChild[] weightedChildren)
    {
        this.WeightedChildren = weightedChildren ?? Enumerable.Empty<WeightedChild>();
    }

    IEnumerable<WeightedChild> WeightedChildren { get; }

    public double Evaluate()
    {
        return this.WeightedChildren.Any()
            ? this.WeightedChildren.Select(child => child.Evaluate()).Sum()
            : 1;
    }
}

public class WeightedChild
{
    public WeightedChild(double weight, Node child)
    {
        this.Weight = weight;
        this.Child = child;
    }

    public double Weight { get; }
    Node Child { get; }

    public double Evaluate()
    {
        return this.Child.Evaluate() * this.Weight;
    }
}

You can then use these classes to build your data set:

var X = new Node();
var Y = new Node();
var Z = new Node();
var E2 = new Node(new WeightedChild(3.3, Z));
var E1 = new Node(new WeightedChild(2.1, Y));
var D = new Node(
    new WeightedChild(0.7, X),
    new WeightedChild(1.8, E1),
    new WeightedChild(3.1, E2));
var C2 = new Node(new WeightedChild(0.9, X));
var C1 = new Node(
    new WeightedChild(1.14, X),
    new WeightedChild(1.7, D));
var B = new Node(
    new WeightedChild(1.3, C1),
    new WeightedChild(1.5, C2));
var A = new Node(new WeightedChild(1.1, C1));

You can then evaluate each node:

Console.WriteLine($"f(E2) = {E2.Evaluate()}");
Console.WriteLine($"f(D) = {D.Evaluate()}");
Console.WriteLine($"f(C1) = {C1.Evaluate()}");

Note however, that while in your example for rule 2, you are evaulating a single "D" from row 10, the set defined in this answer captures the fact that D has multiple children and therefore will give a different answer. You could address this by redefining the Ds as D1 , D2 and D3 , and then add them all as children of C1 etc.

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