【问题标题】:Calculating an array of values in C# (Formula convert from Excel)在 C# 中计算值数组(从 Excel 转换公式)
【发布时间】:2010-09-14 21:12:27
【问题描述】:

我目前正在构建一个半复杂的计算器,它基本上是从我提供的 Excel 电子表格转换而来的。

我已经完成了大部分工作,但 Excel 电子表格中有一部分在 6 行和 7 列之间发生了多次计算,但问题是这些计算没有按任何特定顺序进行。

例如,Row0[Column1] 是使用 (Row2[Column4] * Row2[Column5]) 计算的,Row1[Column4] 是使用 (Row4[Column2] / Row5[Column1]) 计算的等等......你明白了。

我曾考虑过使用二维数组,但担心这些值会按特定顺序计算,因此在达到它们时没有任何价值。据我所知,将首先计算 Row1,然后是 Row2Row3 等。

所以,如果不为我的 excel 电子表格中的每个单元格创建一个变量(并对其进行适当的排序),有没有一种方法可以使用 C# 进行计算?

我非常感谢任何您认为可能的帮助、建议、指点 - 我很想听听!

EDIT 实现@dtb提供的Lazy类后,得到如下代码。这是我提供的 Excel 电子表格中内容的直接副本,包括指针和计算。

var sr = new Lazy<decimal>[6, 6];
sr[0, 0] = new Lazy<decimal>(() => sr[1, 0].Value - eNumber);
sr[0, 3] = new Lazy<decimal>(() => sr[0, 4].Value - sr[1, 0].Value - sr[1, 4].Value);
sr[0, 4] = new Lazy<decimal>(() => sr[0, 0].Value * edD);
sr[0, 5] = new Lazy<decimal>(() => sr[0, 0].Value);

sr[1, 0] = new Lazy<decimal>(() => sr[1, 5].Value);
sr[1, 4] = new Lazy<decimal>(() => sr[1, 0].Value * edD);
sr[1, 5] = new Lazy<decimal>(() => sr[2, 0].Value + sr[2, 5].Value);

sr[2, 0] = new Lazy<decimal>(() => eNumber * rRate);
sr[2, 4] = new Lazy<decimal>(() => sr[2, 0].Value * hdD);
sr[2, 5] = new Lazy<decimal>(() => sr[1, 5].Value);

sr[3, 1] = new Lazy<decimal>(() => sr[2, 5].Value);

sr[4, 2] = new Lazy<decimal>(() => eNumber * (ePc / 100) + sr[2, 0].Value * (hlPc / 100) - sr[3, 1].Value);

sr[5, 0] = new Lazy<decimal>(() => (sr[0, 0].Value + sr[1, 0].Value + sr[2, 0].Value) / ePerR);
sr[5, 2] = new Lazy<decimal>(() => sr[5, 0].Value / rLifecycle);
sr[5, 4] = new Lazy<decimal>(() => sr[5, 2].Value);
sr[5, 5] = new Lazy<decimal>(() => sr[5, 0].Value + sr[5, 2].Value - sr[5, 4].Value);

但是我收到以下错误
ValueFactory attempted to access the Value property of this instance.

谷歌搜索错误返回了一堆垃圾搜索类型的网站。

马尔科

【问题讨论】:

  • 这是什么错误?编译器错误还是异常?您是否增强了 Lazy 类以进行循环依赖检测?因为您的定义中有一个: sr[1,5] 是根据 sr[2,5] 定义的,反之亦然。
  • 嗨@dtb - 项目编译但我得到一个异常。我暂时使用.NET 4(直到我让它在原始类中工作)。顺便说一句,我不确定如何扩展循环依赖的类,这(相当)有点超出我的知识范围。
  • 那么你刚刚遇到了.NET 4.0 Lazy 类的循环依赖检测。您可以通过将 valueCreated 设置为 1 before 调用 valueFactory,设置为 2,并在调用 Value 并且 valueCreated 为 1 时抛出异常来自己实现它...仔细检查是否真的有循环定义你的excel表。如果有,请使用 excel 公式发布一个新问题,并询问如何在 C# 中计算定点。但我怀疑你的代码中有错字。
  • 我已经对代码进行了三次检查,这正是电子表格中的内容,所以这意味着我确实需要实现循环依赖?我要问一个新的问题吗?
  • 如果存在循环依赖,那么我使用 Lazy 的方法不起作用。使用精确的 Excel 公式集提出一个新问题。包括“定点”一词和该问题的链接。 :-)

标签: c# excel multidimensional-array lazy-evaluation calculated-columns


【解决方案1】:

看看惰性评估

var table = new Lazy<int>[2, 2];

table[0, 0] = new Lazy<int>(() => table[1, 1].Value * 2);
table[0, 1] = new Lazy<int>(() => 42);
table[1, 0] = new Lazy<int>(() => 100);
table[1, 1] = new Lazy<int>(() => table[0, 1].Value + table[1, 0].Value);

for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
{
    Console.WriteLine("Row = {0}  Column = {1}  Value = {2}",
                             i,            j,           table[i, j].Value);
}

注意表格单元格的内容是如何以任意顺序定义的。只要单元格之间没有循环依赖关系,它就会自行计算顺序。

输出:

行 = 0 列 = 0 值 = 284 行 = 0 列 = 1 值 = 42 行 = 1 列 = 0 值 = 100 行 = 1 列 = 1 值 = 142

使用 LINQ-to-Lazy 会变得更易读:

var table = new Lazy<int>[2, 2];

table[0, 0] = from t in table.AsLazy()
              from x in t[1, 1]
              select 2 * x;
table[0, 1] = 42.AsLazy();
table[1, 0] = 100.AsLazy();
table[1, 1] = from t in table.AsLazy()
              from a in t[0, 1]
              from b in t[1, 0]
              select a + b;

使用

static class LazyExtensions
{
    public static Lazy<TResult> SelectMany<TSource, TCollection, TResult>(this Lazy<TSource> source, Func<TSource, Lazy<TCollection>> collectionSelector, Func<TSource, TCollection, TResult> resultSelector)
    {
        return new Lazy<TResult>(() => resultSelector(source.Value, collectionSelector(source.Value).Value));
    }

    public static Lazy<TSource> AsLazy<TSource>(this TSource value)
    {
        return new Lazy<TSource>(() => value);
    }
}

.NET 4.0 的 Lazy<T> Class 的自定义替换:

sealed class MyLazy<T>
{
    private readonly Func<T> valueFactory;
    private T value;
    private bool valueCreated;

    public MyLazy(Func<T> valueFactory)
    {
        if (valueFactory == null)
        {
            throw new ArgumentNullException("valueFactory");
        }
        this.valueFactory = valueFactory;
    }

    public bool IsValueCreated
    {
        get { return this.valueCreated; }
    }

    public T Value
    {
        get
        {
            if (!this.valueCreated)
            {
                this.value = this.valueFactory();
                this.valueCreated = true;
            }
            return this.value;
        }
    }
}

【讨论】:

  • Lazy 是自定义类吗?我正在使用 WPF (.NET 3.5) 并且 Lazy 不存在。
  • Lazy<T> 是 .NET Framework 4.0 中的新功能。如果您坚持使用 3.5,您应该能够非常轻松地构建自定义替换。
  • 是的,我在这个上坚持使用 3.5 :( 我现在正在尝试使用您提供的自定义版本。
  • 嗨@dtb - 我已经实现了解决方案 - 但是我得到一个错误ValueFactory试图访问这个实例的Value属性。请看我的编辑
【解决方案2】:

Marko,我认为最好的方法是绘制出这些细胞之间的关系。如果这个问题是关于 Excel 执行顺序的问题,我可以向您指出:http://msdn.microsoft.com/en-us/library/bb687891.aspx

【讨论】:

    【解决方案3】:

    上面显示的惰性解决方案是最优雅的,我将在下面提到一个警告。

    A计划

    您可以很容易地编写自己的Lazy&lt;T&gt; 版本(这是未经测试的代码):

    class Lazy<T> {
      private bool IsEvaluated;
      private T Value;
      private Func<T> Suspension;
      public Lazy<T>(Func<T> susp) { Suspension = susp; }
      public static implicit operator T(Lazy<T> thunk) {
        if (thunk.IsEvaluated) {
          return thunk.Value;
        }
        thunk.Value = thunk.Suspension();
        thunk.IsEvaluated = true;
        return thunk.Value;
      }
    }
    

    当然,您还需要定义重载的算术运算符。

    B计划

    解决问题的另一种方法是将单元格按递增的依赖顺序排序(如果 A 包含直接或间接使用 B 的公式,则单元格 A 依赖于单元格 B)并按该顺序评估它们。

    警告

    如果您的依赖项包含一个循环,那么这些方法都不能保证有效,因为您需要评估到一个固定点。在这种情况下,您可能需要像 B 计划这样的东西,但首先将您的依赖关系图分解为强连接的组件(本网站上的 SCC 有一个很好的答案)。

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2022-11-10
      • 2021-07-13
      • 2016-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多