我有下表
我需要计算X,Y和Z列中的值。
规则1:代码仅具有X,Y或Z子级。
让我们从简单的第12行开始,代码“ E2”有一个名为“ Z”的子代,值是3.30,因此单元格[F12](Z列)应该有3.30。我们可以表示为f(12)= 3.30Z,类似地,单元格[E11]应该为2.10,或者f(11)= 2.10Y。对于第6行,cell [D6]应该为1.14,表示为f(6)= 1.14X。
请注意,代码X,Y,Z本身没有孩子。但是,其他孩子可以递归生下大孩子。
规则2:如果代码中有子代码,则该值将为其自身值乘以子代码值。
例如第10行,它有一个子级'E2',因此该值将是其子项(E2)值的3.10倍,即f(10)= 3.10 * f(12)= 3.10 * 3.30Z = 10.23Z,单元格[F10]应该是10.23
规则3,如果一个代码中有多个子代,则该值将按照规则2按类型对所有子代分组求和。
例如第5行C1有一个孩子D,D有三个孩子(X,E1,E2),这3个孩子值需要加在一起。 f(5)= 1.70 *(f(8)+ f(9)+ f(10))。当然,有些子项具有X值,有些子项具有Y值,有些子项具有Z值,所以在将它们求和时,需要按值类型将它们分组,然后填写相应的列Cell [D5],Cell [E5] ,单元格[F5]
希望如此。
这是原始更复杂问题的简化版本,其中包含数千行和其他一些计算。但是不是一个巨大的表,因此性能或速度不是优先考虑的问题。
更多的是算法问题,但我更喜欢用C#实现
答案 0 :(得分:3)
您所描述的是一个树状结构,您可以轻松地构建它,然后进行递归遍历。您已经知道父母/孩子的关系;您只需要创建一个模型即可在代码中表示它们。
从一个Element
类开始,如下所示:
class Element
{
string Code;
Element Parent; // null if no parent
double Value;
double CalculatedValue;
List<string> Children;
}
并创建一个这些字典,以代码为键:
Dictionary<string, Element> Elements;
遍历列表,添加为每个唯一元素创建一个Element
并将其添加到字典中:
(注意:代码示例使用伪C#。我不想在这里陷入语法困境。)
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);
}
}
您现在有了一个包含子关系的元素字典。您需要创建父级关系。最简单的方法是扫描字典:
for each e in Elements
for each child in e.Children
if (child != "X" or "Y" or "Z")
Elements[child].Parent = e;
现在您拥有的是森林:一棵或多棵无根树木。您可以递归扫描它以计算您的值:
double totalValue = 0;
for each e in Elements
if (e.Parent == null)
{
totalValue += calculateElementValue(e);
}
}
// at this point, totalValue should contain the total value
如果我正确理解了您的规则,则此方法应计算单个元素的值。我假设一个元素只有X,Y或Z中的一个。这是对元素树的简单的深度优先遍历。
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;
}
您可以在构建元素的初始循环中包括父代分配,但是这会使事情变得有些复杂。但是除非表中有很多行,否则您可能不会注意到速度的差异。
答案 1 :(得分:1)
以下是一些示例数据结构,可用于表示数据并递归计算值。
请注意,此示例使用了间接递归。即Node.Evaluate()
不会直接调用自身,而是会调用WeightedChild.Evaluate()
,后者又会返回到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;
}
}
然后您可以使用这些类来构建数据集:
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));
然后您可以评估每个节点:
Console.WriteLine($"f(E2) = {E2.Evaluate()}");
Console.WriteLine($"f(D) = {D.Evaluate()}");
Console.WriteLine($"f(C1) = {C1.Evaluate()}");
但是请注意,在规则2的示例中,您是从第10行中删除单个“ D”,此答案中定义的集合捕获了D有多个子代的事实,因此将给出不同的答案。您可以通过将D重新定义为D1
,D2
和D3
来解决此问题,然后将它们全部添加为C1等的子代。