1. 程式人生 > 其它 >基於DynamicExpresso的自定義表示式計算

基於DynamicExpresso的自定義表示式計算

基於DynamicExpresso的自定義表示式計算

專案來由

公司某部門要根據原始資料,進行復雜運算。得到一些指標。因為用excel計算需要使用人的excel技能非常高,而且很繁瑣。所以需要It幫忙做一個這樣的演算法庫,可以直接呼叫。但後來因為公司採購了MES,Mes中包含該功能,所以專案終止了。但花了1周研究的東西,還是不希望就這樣流產了。特放到部落格園上,希望各位大牛能有閒的時候,能幫忙一起優化。如果能把 DynamicExpresso 這玩意兒也自己寫了,還是相當有意義的。程式碼我會放到 gitee上  。大家有需要的儘管拿去用。目前已經實現的內容是,單資料來源已經實現了自定義計算的功能。下面會做詳細介紹。

先看看 DynamicExpresso 能幹什麼

DynamicExpresso GitHub專案地址
文件裡面寫的很清楚了。我只闡述我用過的內容,他的執行規則是這樣的:

以下是具體的應用例項

  1. 表示式運算:
    var interpreter = new Interpreter();
    var result = interpreter.Eval("8 / 2 + 2");
  1. 自定義方法:
    Func<double, double, double> pow = (x, y) => Math.Pow(x, y);
    var target = new Interpreter().SetFunction("pow", pow); 
    Assert.AreEqual(9.0, target.Eval("pow(3, 2)"));
  1. 搜尋資料,並運算
class Customer
{
	public string Name { get; set; }
	public int Age { get; set; }
	public char Gender { get; set; }
}

[Test]
public void Linq_Where()
{
	var customers = new List<Customer> {
		new Customer() { Name = "David", Age = 31, Gender = 'M' },
		new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
		new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
		new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
		new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
	};

	string whereExpression = "customer.Age > 18 && customer.Gender == 'F'";

	var interpreter = new Interpreter();
	Func<Customer, bool> dynamicWhere = interpreter.ParseAsDelegate<Func<Customer, bool>>(whereExpression, "customer");

	Assert.AreEqual(1, customers.Where(dynamicWhere).Count());
}

以上例項很具備代表性了。

再看看我們需要幹什麼

以實際需求為例
最終的表示式是這樣的:

    result = 1 - (a - b) / (a1 - b1)
    a,a1 需要通過條件查詢得到。
    b,b1 分別為兩個點的斜率。

a的查詢條件可能是這樣的,下面定義為在專案中定義為Conditianal ,在方法中定義為 filter

        Score == like(u_arg_2,"Score") && Power == u_arg_0
        # u_arg_2 為使用者輸入的引數。  
        # u_arg_0 為使用者輸入的引數。
        # like 是一個內建函式,需要自己實現。比如,使用者輸入 12.4 那麼需要找到在資料來源中,Score欄位跟 12.4最接近的數。
        # 實則上述表示式 只是一個過濾條件,實際上需要根據過濾條件得到指定的變數值。

ba 類似,這裡就不多做介紹了。

表示式計算的結構設計和實現思路

  1. 使用者輸入引數。

  2. 根據表示式,獲取需要的資料,再賦值給相應的變數。

  3. 重複步驟2,直到表示式需要的所有引數都被賦值。

  4. 計算表示式。

定義瞭如下類結構

介面名稱 作用
IArg 所有引數的基類介面
IArgConverter 引數的型別轉換行為
IConditional 查詢條件介面
IAlgorithm 演算法介面
IAlgorithmBehaviours 演算法行為介面
IArg 所有引數的基類介面

實則 IAlgorithmBehaviours 抽象的並不規範。有興趣的朋友希望能一起改進這個專案。可以聯絡我郵箱 [email protected].

整個演算法結構我用json格式定義。前端提交到介面,介面解析資料,並反序列化給 演算法處理程式。計算結果即可。

演算法結構:

{
  "TableName": "tablename",
  "DataFilter": "HoleID == u_arg_13 && ( Temperature == u_arg_0 || Temperature == u_arg_1 ) && TestType = u_arg_14",
  "UserParameters": [
    {
      "name": "u_arg_14",
      "display": "批次",
      "value": 1
    },
    {
      "name": "u_arg_13",
      "display": "孔位",
      "value": 1
    },
    {
      "name": "u_arg_0",
      "display": "溫度1",
      "value": 25
    },
    {
      "name": "u_arg_1",
      "display": "溫度2",
      "value": 85
    },
    {
      "name": "u_arg_2",
      "display": "功率",
      "value": 80
    },
    {
      "name": "u_arg_9",
      "display": "第一個點",
      "value": "(1.3454533,1.0822343)"
    },
    {
      "name": "u_arg_10",
      "display": "第二個點",
      "value": "(1.3454533,1.0822343)"
    },
    {
      "name": "u_arg_11",
      "display": "第三個點",
      "value": "(1.3454533,1.0822343)"
    },
    {
      "name": "u_arg_12",
      "display": "第四個點",
      "value": "(1.3454533,1.0822343)"
    }
  ],
  "Conditions": [
    {
      "assigns": [ "u_arg_3 = current", "u_arg_4 = MDPCurrent" ],
      "condition": "Power == like(u_arg_2,\"Power\",\" Temperature == u_arg_0\") && Temperature == u_arg_0"
    },
    {
      "assigns": [ "u_arg_5 = current" ],
      "condition": "MPDCurrent == u_arg_4 && Temperature == u_arg_2"
    }
  ],
  "Expression": "1 - (u_arg_3 - ith1(u_arg_9,u_arg_10)) / (u_arg_5 - ith1(u_arg_11,u_arg_12))"
}
 

ith1,like 為自定義方法。

實際呼叫情況是這樣的。

呼叫程式碼

    Stopwatch watch = new Stopwatch();
    watch.Start();
    List<IArg> inputs = new List<IArg>();
    inputs.Add(new ArgUser() { Name = "arg0", Display = "溫度1", Value = "25", DataType = "double" });
    inputs.Add(new ArgUser() { Name = "arg1", Display = "溫度2", Value = "85", DataType = "double" });
    inputs.Add(new ArgUser() { Name = "arg2", Display = "功率", Value = "17.3", DataType = "double" });

    List<IConditional> querys = new List<IConditional>();

    Conditional conditional = new Conditional()
    {
        Assign = new List<string>() { "arg3 = Current", "arg4 = MPDCurrent" },
        Condition = "d.Power == like(arg2,\"d.Power\",\"d.Temperature = arg0\") && d.Temperature == arg0"
    };
    querys.Add(conditional);
    Algorithm algorithm = new Algorithm()
    {

        UserParameters = inputs,
        Conditions = null,
        Expression = "arg1 + arg2 + arg0",
        Name = "測試演算法"
    }; 
    AlgorithmProcess<Data> process = new AlgorithmProcess<Data>(datas, algorithm);
    Console.WriteLine($"演算法結果:{process.Execute()}"); 
    watch.Stop();  
    Console.WriteLine($"整體耗時:{watch.Elapsed.TotalSeconds}");

datas是List資料。

專案地址:自定義表示式計算

如果本篇部落格對你有幫助,請幫忙推薦一下,謝謝!