views:

132

answers:

3

Hi all,

I'm currently building a semi-complicated calculator which is basically a conversion from an Excel spreadsheet I've been provided.

I've nailed most of it but there's a part in the Excel spreadsheet where multiple calculations occur between 6 rows and 7 columns, but the issue is that the calculations happen in no particular order what-so-ever.

So for example, Row0[Column1] is calculated using (Row2[Column4] * Row2[Column5]) and Row1[Column4] is calculated using (Row4[Column2] / Row5[Column1]) and so forth.. you get the idea.

I've thought about using a 2D array, but am afraid that the values will calculate in a particular order, thus having no value when they are reached. As far as I'm aware, Row1 will be calculated first, then Row2, Row3, etc.

So, without creating a variable for each cell in my excel spreadsheet (and ordering it appropriately), is there a way I can calculate this using C#?

I would really appreciate any help, advice, pointers, whatever you think may be possible - I'd love to hear it!

EDIT After implementing the Lazy class provided by @dtb, I've got the following code. It's a straight copy of what's in the Excel spreadsheet I've been provided, including pointers & calculations.

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

However I get the following error
ValueFactory attempted to access the Value property of this instance.

Googling the error has returned a bunch of spammy search type websites.

Marko

+6  A: 

Have a look at Lazy Evaluation:

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

Note how the content of the table cells are defined in arbitrary order. It figure out the order itself, as long as there are no circular dependencies between cells.

Output:

Row = 0  Column = 0  Value = 284
Row = 0  Column = 1  Value = 42
Row = 1  Column = 0  Value = 100
Row = 1  Column = 1  Value = 142

It becomes slightly more readable with 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;

using

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

Custom replacement for .NET 4.0's 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;
        }
    }
}
dtb
@dtb - is Lazy a custom class? I'm using WPF (.NET 3.5) and Lazy doesn't exist.
Marko
[Lazy<T>](http://msdn.microsoft.com/en-us/library/dd642331.aspx) is new in the .NET Framework 4.0. You should be able to build a custom replacement pretty easily if you're stuck with 3.5.
dtb
Yeah I'm stuck with 3.5 on this one :( I'm trying now with the custom one you've provided.
Marko
Hi @dtb - I've implemented the solution - however I get an error *ValueFactory attempted to access the Value property of this instance*. Please see my edit
Marko
A: 

Marko, I think the best way is for you to map out the relationships between these cells. If this question is about the order in which Excel would do it, I can point you to here: http://msdn.microsoft.com/en-us/library/bb687891.aspx

mkoistinen
A: 

The lazy solution shown above is the most elegant, with one caveat which I'll mention below.

Plan A

You can code up your own version of Lazy<T> pretty easily (this is untested code):

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

Of course, you'll need to define overloaded arithmetic operators as well.

Plan B

Another way of tackling your problem is to sort your cells into increasing dependency order (where cell A depends on cell B if A contains a formula that uses B, directly or indirectly) and evaluate them in that order.

Caveat

If your dependencies contain a cycle then neither of these approaches is guaranteed to work since you will need to evaluate to a fixed point. In that case you probably need something like Plan B, but first break your dependency graph into strongly connected components (there is a good answer on SCCs on this site).

Hope this helps.

Rafe