1

Je suis en train de construire une calculatrice semi-compliquée qui est essentiellement une conversion à partir d'une feuille de calcul Excel qui m'a été fournie. Je l'ai clouée en grande partie, mais il y a une partie dans la feuille de calcul Excel où plusieurs calculs ont lieu entre 6 lignes et 7 colonnes, mais le problème est que les calculs ne se font pas dans un ordre particulier.Calcul d'un tableau de valeurs en C# (Formule de conversion à partir d'Excel)

Ainsi, par exemple, Row0[Column1] est calculé à l'aide (Row2[Column4] * Row2[Column5]) et Row1[Column4] est calculée à l'aide (Row4[Column2]/Row5[Column1]) et ainsi de suite .. vous voyez l'idée.

J'ai pensé à utiliser un tableau 2D, mais j'ai peur que les valeurs se calculent dans un ordre particulier, n'ayant donc aucune valeur quand elles sont atteintes. Pour autant que je sache, Row1 sera calculé d'abord, puis Row2, Row3, etc.

Ainsi, sans créer une variable pour chaque cellule dans ma feuille de calcul Excel (et le commander de façon appropriée) , y a-t-il un moyen de calculer cela en utilisant C#?

J'apprécierais vraiment n'importe quelle aide, conseil, pointeurs, tout ce que vous pensez être possible - j'aimerais l'entendre!

EDIT Après la mise en œuvre de la classe Lazy fournies par @dtb, j'ai le code suivant. C'est une copie directe de ce qui est dans la feuille de calcul Excel que j'ai reçue, y compris les pointeurs & calculs.

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

Cependant je reçois l'erreur suivante
ValueFactory attempted to access the Value property of this instance.

recherche sur Google l'erreur a retourné un groupe de sites de type spam de recherche.

Marko

+0

De quel type d'erreur s'agit-il? Une erreur de compilation ou une exception? Avez-vous amélioré la classe Lazy pour faire une détection de dépendance circulaire par hasard? Parce qu'il y en a un dans vos définitions: sr [1,5] est défini en termes de sr [2,5] et vice versa. – dtb

+0

Salut @dtb - le projet compile mais je reçois une exception. J'ai eu recours à .NET 4 pour le moment (jusqu'à ce que je l'ai fonctionné dans la classe originale). Au fait, je ne sais pas trop comment étendre la classe pour les dépendances circulaires, c'est (tout à fait) un peu en dehors de mon champ de connaissance. – Marko

+0

Ensuite, vous venez de rencontrer la détection de dépendance circulaire de la classe .NET 4.0 Lazy. Vous pouvez l'implémenter vous-même en affectant valueCreated à 1 * avant d'appeler valueFactory, à 2 after, et en lançant une exception si Value est appelée et valueCreated vaut 1 ... Vérifiez s'il y a vraiment une définition circulaire dans votre feuille Excel. Si c'est le cas, postez une nouvelle question avec les formules Excel et demandez comment calculer le point fixe en C#. Mais je soupçonne que vous avez une faute de frappe dans votre code. – dtb

Répondre

6

Jetez un oeil à Évaluation Lazy:

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

Notez comment le contenu des cellules du tableau sont définies dans un ordre arbitraire. Il détermine l'ordre lui-même, tant qu'il n'y a pas de dépendances circulaires entre les cellules.

Sortie:

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

Il devient un peu plus lisible avec LINQ à 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; 

utilisant

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

Remplacement personnalisé pour .NET 4.0 de 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; 
     } 
    } 
} 
+0

est Lazy une classe personnalisée? J'utilise WPF (.NET 3.5) et Lazy n'existe pas. – Marko

+0

[Lazy ] (http://msdn.microsoft.com/en-us/library/dd642331.aspx) est nouveau dans .NET Framework 4.0. Vous devriez être en mesure de construire un remplacement personnalisé assez facilement si vous êtes coincé avec 3.5. – dtb

+0

Ouais je suis coincé avec 3.5 sur celui-ci :(J'essaie maintenant avec le personnalisé que vous avez fourni – Marko

0

La solution paresseuse montrée ci-dessus est la plus élégante, avec une mise en garde que je mentionnerai ci-dessous.

Plan A

Vous pouvez coder votre propre version de Lazy<T> assez facilement (ce qui est un code non testé):

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

Bien sûr, vous aurez besoin de définir des opérateurs arithmétiques surchargées comme bien.

Plan B

Une autre façon d'aborder votre problème est de trier vos cellules dans l'ordre croissant de dépendance (où une cellule dépend de la cellule B si A contient une formule qui utilise B, directement ou indirectement) et évaluer eux dans cet ordre.

caveat

Si vos dépendances contiennent un cycle alors aucune de ces approches est garanti pour fonctionner puisque vous aurez besoin d'évaluer à un point fixe. Dans ce cas, vous avez probablement besoin de quelque chose comme Plan B, mais d'abord casser votre graphe de dépendance en composants fortement connectés (il y a une bonne réponse sur SCCs sur ce site).

Espérons que cela aide.