Free cookie consent management tool by TermsFeed Policy Generator

Changeset 6687


Ignore:
Timestamp:
08/31/11 17:42:00 (11 years ago)
Author:
epitzer
Message:

Extend calculator to support other data types, literals, stack manipulations and conditionals (#1622)

Location:
trunk/sources/HeuristicLab.Optimization/3.3
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/sources/HeuristicLab.Optimization/3.3/Calculator.cs

    r6663 r6687  
    11using System;
    22using System.Collections.Generic;
    3 using System.Drawing;
    43using System.Linq;
    54using System.Text.RegularExpressions;
     
    76using HeuristicLab.Core;
    87using HeuristicLab.Data;
    9 using HeuristicLab.Parameters;
    108using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
    119using System.Globalization;
    1210
    13 namespace HeuristicLab.Optimization { 
    14    
     11namespace HeuristicLab.Optimization {
     12
    1513  [StorableClass]
    1614  public class Calculator : IDeepCloneable {
    1715
    1816    #region Fields & Properties
    19    
     17
    2018    private List<string> tokens;
    2119
     
    2624    }
    2725
    28     private static readonly Regex TokenRegex = new Regex(@"[a-zA-Z0-9._]+|""([^""]|\"")+""|[-+*/^]|log");   
     26    private static readonly Regex TokenRegex =
     27      new Regex(@"[a-zA-Z0-9._]+|""([^""]|\\"")*""|'([^']|\\')*'|[-+*/<>^]|dup|swap|drop|log|true|false|if|==|not|isnull|null|ismatch|rename");
    2928
    3029    #endregion
     
    3534    protected Calculator(bool deserializing) { }
    3635    public Calculator() { }
    37     public Calculator(Calculator original, Cloner cloner) {
    38       this.tokens = original.tokens.ToList();
     36    protected Calculator(Calculator original, Cloner cloner) {
     37      cloner.RegisterClonedObject(original, this);
     38      tokens = original.tokens.ToList();
    3939    }
    4040    public IDeepCloneable Clone(Cloner cloner) {
     
    4646    #endregion
    4747
    48     public IEnumerable<string> Tokenize(string s) {
     48    private static IEnumerable<string> Tokenize(string s) {
    4949      return TokenRegex.Matches(s).Cast<Match>().Select(m => m.Value);
    5050    }
    5151
    52     private double GetVariableValue(IDictionary<string, IItem> variables, string name) {
    53       if (variables.ContainsKey(name)) {
    54         var item = variables[name];
    55         var intValue = item as IntValue;
    56         if (intValue != null) {
    57           return intValue.Value;
    58         } else {
    59           var doubleValue = item as DoubleValue;
    60           if (doubleValue != null)
    61             return doubleValue.Value;
    62           else
    63             throw new InvalidOperationException("Non numerical argument");
    64         }
    65       } else {
    66         throw new InvalidOperationException(string.Format("variable \"{0}\" not found", name));
    67       }
    68     }
    69 
    7052    public IItem GetValue(IDictionary<string, IItem> variables) {
    71       var stack = new Stack<double>();
    72       Action<Func<double, double, double>> binf = op => {
    73         var b = stack.Pop();
    74         stack.Push(op(stack.Pop(), b));
    75       };
    76       try {
    77         foreach (var token in tokens) {
     53      var stack = new Stack<object>();
     54      int i = 0;
     55      try {
     56        for (; i<tokens.Count; i++) {
     57          var token = tokens[i];
    7858          double d;
    79           if (double.TryParse(token,
    80                 NumberStyles.AllowDecimalPoint |
    81               NumberStyles.AllowExponent |
    82               NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out d)) {
     59          if (TryParse(token, out d)) {
    8360            stack.Push(d);
    8461          } else if (token.StartsWith("\"")) {
    8562            stack.Push(GetVariableValue(variables, token.Substring(1, token.Length - 2).Replace(@"\""", @"""")));
     63          } else if (token.StartsWith("'")) {
     64            stack.Push(token.Substring(1, token.Length-2).Replace("\'", "'"));
    8665          } else {
    87             switch (token) {
    88               case "log": stack.Push(Math.Log(stack.Pop())); break;
    89               case "+": binf((x, y) => x + y); break;
    90               case "-": binf((x, y) => x - y); break;
    91               case "*": binf((x, y) => x * y); break;
    92               case "/": binf((x, y) => x / y); break;
    93               case "^": binf(Math.Pow); break;
    94               default: stack.Push(GetVariableValue(variables, token)); break;
    95             }
     66            Apply(token, stack, variables);
    9667          }
    9768        }
    98       } catch (InvalidOperationException x) {
    99         return new StringValue(string.Format("Calculation Failed: {0}", x.Message));
    100       }
    101       if (stack.Count != 1)
    102         return new StringValue("Invalid final evaluation stack size != 1");
    103       return new DoubleValue(stack.Pop());     
    104     }   
     69      } catch (Exception x) {
     70        throw new Exception(string.Format(
     71          "Calculation Failed at token #{1}: '{2}' {0}current stack is: {0}{3}", Environment.NewLine,
     72          i, tokens[i], string.Join(Environment.NewLine, stack.Select(AsString))),
     73          x);
     74      }
     75      if (stack.Count != 1) throw new Exception(string.Format("Invalid final evaluation stack size {0} (should be 1)", stack.Count));
     76      var result = stack.Pop();
     77      if (result is string) return new StringValue((string)result);
     78      if (result is double) return new DoubleValue((double)result);
     79      if (result is bool) return new BoolValue((bool)result);
     80      return null;
     81    }
     82
     83    private static void Apply(string token, Stack<object> stack, IDictionary<string, IItem> variables) {
     84      switch (token) {
     85        case "null":  stack.Push(null); break;
     86        case "true":  stack.Push(true); break;
     87        case "false": stack.Push(false); break;
     88
     89        case "drop": stack.Pop(); break;
     90        case "dup": stack.Push(stack.Peek()); break;
     91        case "swap":
     92          var top = stack.Pop();
     93          var next = stack.Pop();
     94          stack.Push(top);
     95          stack.Push(next);
     96          break;
     97
     98        case "log": Apply(stack, x => Math.Log((double)x)); break;
     99        case "+": Apply(stack, (x, y) => (double)x + (double)y); break;
     100        case "-": Apply(stack, (x, y) => (double)x - (double)y); break;
     101        case "*": Apply(stack, (x, y) => (double)x * (double)y); break;
     102        case "/": Apply(stack, (x, y) => (double)x / (double)y); break;
     103        case "^": Apply(stack, (x, y) => Math.Pow((double)x, (double)y)); break;
     104        case "<": Apply(stack, (x, y) => (double)x < (double)y); break;
     105        case ">": Apply(stack, (x, y) => (double)x > (double)y); break;
     106
     107        case "==": Apply(stack, (x, y) => Equal(x, y)); break;
     108        case "not": Apply(stack, x => !(bool)x); break;
     109        case "isnull": Apply(stack, x => x == null); break;
     110        case "if": Apply(stack, (then, else_, cond) => (bool)cond ? then : else_); break;
     111
     112        case "ismatch": Apply(stack, (s, p) => new Regex((string)p).IsMatch((string)s)); break;
     113        case "rename": Apply(stack, (s, p, r) => new Regex((string)p).Replace((string)s, (string)r)); break;
     114
     115        default: stack.Push(GetVariableValue(variables, token)); break;
     116      }
     117    }
     118
     119    #region Auxiliary Functions
     120
     121    #region IItem value conversion
     122    private static object GetIntValue(IItem value) {
     123      var v = value as IntValue;
     124      if (v != null) return (double)v.Value;
     125      return null;
     126    }
     127
     128    private static object GetDoubleValue(IItem value) {
     129      var v = value as DoubleValue;
     130      if (v != null) return v.Value;
     131      return null;
     132    }
     133
     134    private static object GetBoolValue(IItem value) {
     135      var v = value as BoolValue;
     136      if (v != null) return v.Value;
     137      return null;
     138    }
     139
     140    private static object GetVariableValue(IDictionary<string, IItem> variables, string name) {
     141      if (variables.ContainsKey(name)) {
     142        var item = variables[name];
     143        return
     144          GetIntValue(item) ??
     145          GetDoubleValue(item) ??
     146          GetBoolValue(item) ??
     147          item.ToString();
     148      }
     149      return null;
     150    }
     151    #endregion
     152
     153    #region variadic equality
     154    private static bool Equal(object a, object b) { return EqualNumber(a, b) || EqualBool(a, b) || EqualString(a, b) || a == b; }
     155    private static bool EqualNumber(object a, object b) { return a is double && b is double && (double)a == (double)b; }
     156    private static bool EqualBool(object a, object b) { return a is bool && b is bool && (bool)a == (bool)b; }
     157    private static bool EqualString(object a, object b) { return a is string && b is string && ((string)a).Equals((string)b); }
     158    #endregion
     159
     160    #region stack calculation
     161    private static void Apply(Stack<object> stack, Func<object, object> func) {
     162      if (stack.Count < 1)
     163        throw new InvalidOperationException("Stack is empty");
     164      var a = stack.Pop();
     165      try {
     166        stack.Push(func(a));
     167      } catch (Exception) {
     168        stack.Push(a);
     169        throw;
     170      }
     171    }
     172
     173    private static void Apply(Stack<object> stack, Func<object, object, object> func) {
     174      if (stack.Count < 2)
     175        throw new InvalidOperationException("Stack contains less than two elements");
     176      var b = stack.Pop();
     177      var a = stack.Pop();
     178      try {
     179        stack.Push(func(a, b));
     180      } catch (Exception) {
     181        stack.Push(b);
     182        stack.Push(a);
     183        throw;
     184      }
     185    }
     186
     187    private static void Apply(Stack<object> stack, Func<object, object, object, object> func) {
     188      if (stack.Count < 3)
     189        throw new InvalidOperationException("Stack contains less than three elements");
     190      var c = stack.Pop();
     191      var b = stack.Pop();
     192      var a = stack.Pop();
     193      try {
     194        stack.Push(func(a, b, c));
     195      } catch (Exception) {
     196        stack.Push(a);
     197        stack.Push(b);
     198        stack.Push(c);
     199        throw;
     200      }
     201    }
     202    #endregion
     203
     204    private static bool TryParse(string token, out double d) {
     205      return double.TryParse(token,
     206                             NumberStyles.AllowDecimalPoint |
     207                             NumberStyles.AllowExponent |
     208                             NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out d);
     209    }
     210
     211    private static string AsString(object o) {
     212      if (o == null) return "null";
     213      if (o is string) return string.Format("'{0}'", o);
     214      return o.ToString();
     215    }
     216
     217    #endregion
    105218  }
    106219}
  • trunk/sources/HeuristicLab.Optimization/3.3/RunCollectionFormulaModifer.cs

    r6670 r6687  
    2020    public ValueParameter<StringValue> ResultNameParameter {
    2121      get { return (ValueParameter<StringValue>)Parameters["ResultName"]; }
    22     }   
    23    
     22    }
     23
    2424    public ValueParameter<StringValue> FormulaParameter {
    2525      get { return (ValueParameter<StringValue>)Parameters["Formula"]; }
    26     }   
     26    }
    2727
    28     #region Construction & Cloning   
     28    private string ResultName { get { return ResultNameParameter.Value.Value; } }
     29    private string Formula { get { return FormulaParameter.Value.Value; } }
     30
     31    #region Construction & Cloning
    2932    [StorableConstructor]
    3033    protected RunCollectionFormulaModifer(bool deserializing) : base(deserializing) { }
     
    3538    public RunCollectionFormulaModifer() {
    3639      Parameters.Add(new ValueParameter<StringValue>("ResultName", "The name of the result to be generated by this formula.", new StringValue("Calc.Value")));
    37       Parameters.Add(new ValueParameter<StringValue>("Formula", "RPN formula for new value. This can contain existing run parameters or results (optionally in quotes if they contain whitespace), numbers and arithmetic operators in postfix notation.", new StringValue("1 1 +")));
     40      Parameters.Add(new ValueParameter<StringValue>("Formula",
     41@"RPN formula for new value in postfix notation.
     42
     43This can contain the following elements:
     44
     45literals:
     46  numbers, true, false, null and strings in single quotes
     47variables (run parameters or results):
     48  unquoted or in double quotes if they contain special characters or whitespace
     49mathematical functions:
     50  +, -, /, ^ (power), log
     51predicates:
     52  ==, <, >, isnull, not
     53stack manipulation:
     54  drop swap dup
     55string matching:
     56  <string> <pattern> ismatch
     57string replacing:
     58  <string> <pattern> <replacement> rename
     59conditionals:
     60  <then> <else> <condition> if
     61
     62If the final value is null, the result variable is removed if it exists.",
     63        new StringValue("1 1 +")));
    3864      UpdateName();
    3965      RegisterEvents();
     
    5884
    5985    private void UpdateName() {
    60       name = string.Format("{0} := {1}", ResultNameParameter.Value.Value, FormulaParameter.Value.Value);
     86      name = string.Format("{0} := {1}", ResultName, Formula);
    6187      OnNameChanged();
    6288    }
    6389
    6490    public void Modify(List<IRun> runs) {
    65       var calc = new Calculator {Formula = FormulaParameter.Value.Value};     
     91      var calc = new Calculator { Formula = Formula };
    6692      foreach (var run in runs) {
    6793        var variables = new Dictionary<string, IItem>();
     
    7096        foreach (var result in run.Results)
    7197          variables[result.Key] = result.Value;
    72         run.Results[ResultNameParameter.Value.Value] = calc.GetValue(variables);       
    73       }     
     98        var value = calc.GetValue(variables);
     99        if (value != null)
     100          run.Results[ResultName] = value;
     101        else
     102          run.Results.Remove(ResultName);
     103      }
    74104    }
    75    
     105
    76106  }
    77107}
Note: See TracChangeset for help on using the changeset viewer.