公式验证算法

class Algorithm extends Common
{
public function initialize()
{
parent::_initialize();
$this->actionName = '公式算法验证及计算';
$this->dataName = '公式算法验证及计算';
}

//验证公式合法性(传计算因子参数)
public function verify($str,$arguments)
{
$arguments = json_decode($arguments);
$arguments = (array)$arguments;
//剔除空白符
$str = str_replace(' ', '', $str);
// 错误情况,空字符串
if ("" === $str) {
return ['code'=>400,'msg'=>'不能是空字符串'];
}
//判断连续两个因子
if(substr_count($str,'}{')) {
return ['code'=>400,'msg'=>'不能将两个计算因子写在一起'];
// return $this->resultError('不能将两个计算因子写在一起');
}
//判断是否所有因子都在因子集合范围内(替换后,查看是否还存在{)
$items = [];
foreach ($arguments as $k=>$v){
$items[] = $k;
}
//匹配所有大括号内容
$item = [];
preg_match_all('/\{.*?\}/',$str,$out);

for ($i = 0; $i < count($out[0]); $i++) {
$item = $out[0][$i];
if (!in_array($item,$items)) {
return ['code'=>400,'msg'=>'计算因子不合法'];
// return $this->resultError('计算因子不合法');
}
}

$strNew = preg_replace('/\{.*?\}|if/',1,$str);
if(preg_match('/[a-z]/i',$strNew))
return ['code'=>400,'msg'=>'不能包含非法字符'];
// return $this->resultError('不能包含非法字符');
//判断连续)(
if(substr_count($str,')(')) {
return ['code'=>400,'msg'=>'两个括号之间不能没有运算符'];
// return $this->resultError('两个括号之间不能没有运算符');
}
//判断连续}{
if(substr_count($str,'}{')) {
return ['code'=>400,'msg'=>'两个计算因子之间不能没有运算符'];
// return $this->resultError('两个括号之间不能没有运算符');
}
//判断{}是否成对
$stack = [];
for ($i = 0; $i < strlen($str); $i++) {
$item = $str[$i];
if ('{' === $item) {
array_push($stack,'(');
} elseif ('}' === $item) {
if (count($stack) > 0) {
array_pop($stack);
} else {
return ['code'=>400,'msg'=>'计算因子不合法'];
// return $this->resultError('计算因子不合法');
}
}
}
if (0 !== count($stack)) {
return ['code'=>400,'msg'=>'计算因子不合法'];
// return $this->resultError('计算因子不合法');
}
//判断()是否成对
$stack = [];
for ($i = 0; $i < strlen($str); $i++) {
$item = $str[$i];
if ('(' === $item) {
array_push($stack,'(');
} elseif (')' === $item) {
if (count($stack) > 0) {
array_pop($stack);
} else {
return ['code'=>400,'msg'=>'括号不配对'];
// return $this->resultError('括号不配对');
}
}
}
if (0 !== count($stack)) {
return ['code'=>400,'msg'=>'括号不配对'];
// return $this->resultError('括号不配对');
}
//错误情况,运算符结尾
if(preg_match('/[\x\÷\+\-\*\/]$/',$str)){
return ['code'=>400,'msg'=>'不能以运算符结尾'];
// return $this->resultError('不能以运算符结尾');
}
// 错误情况,( 后面是运算符
if(preg_match('/\([\x\÷\*\/]/',$str)){
return ['code'=>400,'msg'=>'左括号后面不能是运算符'];
// return $this->resultError('左括号后面不能是运算符');
}
// 错误情况,)前面是运算符
if(preg_match('/[\x\÷\+\-\*\/]\)/',$str)){
return ['code'=>400,'msg'=>'右括号前面不能是运算符'];
// return $this->resultError('右括号前面不能是运算符');
}
// 错误情况,运算符连续
if(preg_match('/[\x\÷\+\-\*\/]{2,}/',$str)){
return ['code'=>400,'msg'=>'运算符不能连续'];
// return $this->resultError('运算符不能连续');
}

//空括号
if(substr_count($str,'()')){
return ['code'=>400,'msg'=>'括号中不能没有内容'];
// return $this->resultError('括号中不能没有内容');
}
}
//计算
public function compute($str,$arguments)
{
$res = $this->verify($str,$arguments);
if($res){
return $res;
}
$arguments = json_decode($arguments);

$arguments = (array)$arguments;

$items = [];
foreach ($arguments as $k=>$v){
$items[] = $k;
}
//替换因子为$arguments的值
foreach ($items as $v){
$str = str_replace($v,$arguments[$v]<0?"($arguments[$v])":$arguments[$v],$str);
}
$str = str_replace(' ','',$str);
$res = $this->check($str);
if($res){
return $res;
}
$str = $this->getVal($str);
if($str || $str == '0'){
return ['code'=>200,'msg'=>'','data'=>$str];
}else{
return ['code'=>400,'msg'=>'公式格式不正确','data'=>''];
}
}

//递归计算出最后的结果值
private function getVal($str)
{
// $str = "(2+3)*if(5*6>5,2,5)*5*(2+5)*if(5>3,5,2)*PI";
//获取if(第一次出现的位置
$index = stripos($str,'if(');
//如果没有if
if($index === false){
return eval("return $str;");
}
//有if
//获取最里层if的内容
$strIf = $this->getIf($str);
// echo $strIf,' ';
//计算获取到的if的值
$ifVal = $this->getIfVal($strIf);
// echo $ifVal;die;
//用值替换if内容
$str = str_replace("if($strIf)",$ifVal,$str);
return $this->getVal($str);
}

//递归取出最里层if
private function getIf($str)
{
//获取if(第一次出现的位置
$index = stripos($str,'if(');
//如果没有if
if($index === false){
return $str;
}
//有if
$strr = substr($str,$index+3);
//找出if括号中的内容
$stack = [];
for ($i = 0; $i < strlen($strr); $i++) {
$item = $strr[$i];
if ('(' === $item) {
array_push($stack,'(');
} elseif (')' === $item) {
if (count($stack) > 0) {
array_pop($stack);
} else {
//if右边括号在$str中出现的位置
break;
}
}
}
$str = substr($strr,0,$i);
return $this->getIf($str);
//
}

//递归取出最里层()
private function getBraces($str)
{
//获取(第一次出现的位置
$index = stripos($str,'(');
//如果没有()
if($index === false){
return $str;
}
//有()
$strr = substr($str,$index+1);
//找出括号中的内容
$stack = [];
for ($i = 0; $i < strlen($strr); $i++) {
$item = $strr[$i];
if ('(' === $item) {
array_push($stack,'(');
} elseif (')' === $item) {
if (count($stack) > 0) {
array_pop($stack);
} else {
//右边括号在$str中出现的位置
break;
}
}
}
$str = substr($strr,0,$i);
return $this->getBraces($str);
}

//计算if结果 example:$str = 6>5&&2>3,3,2
private function getIfVal($str)
{

$strArr = explode(',',$str);
//if中关系表达式
$first = $strArr[0];
$rs = $this->getFirstVal($first);
return $rs ? $strArr[1] : $strArr[2];

}

//计算if第一部分的值
private function getFirstVal($str)
{
$index = strpos($str,'(');
if($index === false){
return $this->getBraVal($str);
}
$strBra = $this->getBraces($str);
// echo $strBra;die;
$rs = $this->getBraVal($strBra);
// echo $rs;die;
$str = str_replace("($strBra)",$rs,$str);
// echo $str;die;
return $this->getFirstVal($str);
}
//计算if中第一部分 括号中的值
private function getBraVal($str)
{
//判断表达式中是否有&&或者||
if(!substr_count($str,'&&') && !substr_count($str,'||')){//与 或 都没有的情况
$res = $this->getRes($str);
return $res ;
}
//有&& 或者 ||
preg_match_all('/(&&)|(\|\|)/',$str,$sign);
$str = str_replace('&&',',',$str);
$str = str_replace('||',',',$str); //把表达式中的&&与|| 替换成逗号

//分割成数组
$arr = explode(',',$str);
foreach ($arr as $v){
$res[] = $this->getRes($v);
}
$rs = $res[0];
foreach($sign[0] as $k=>$val){
$rs .= $val.$res[$k+1];
}//$rs 结果 1&&0&&1||1

//计算且与或的结果
// echo $rs,',';
$result = $this->getBool($rs);//返回值为bool
// dump($result);
return $result;
}
//计算表达式结果 example $str:1+5>=4
private function getRes($str)
{
//匹配到运算符号

preg_match('/(\<\=)|(\>\=)|(\!\=)|(\=)|(\<)|(\>)/',$str,$sign);
//根据运算符号分割成数组
if(!empty($sign)){
$strArr = explode($sign[0],$str);
if($sign[0] == '<=')
return eval("return {$strArr[0]};") <= eval("return {$strArr[1]};") ? 1 : 0;
if($sign[0] == '>=')
return eval("return {$strArr[0]};") >= eval("return {$strArr[1]};") ? 1 : 0;
if($sign[0] == '<')
return eval("return {$strArr[0]};") < eval("return {$strArr[1]};") ? 1 : 0;
if($sign[0] == '>')
return eval("return {$strArr[0]};") > eval("return {$strArr[1]};") ? 1 : 0;
if($sign[0] == '!=')
return eval("return {$strArr[0]};") != eval("return {$strArr[1]};") ? 1 : 0;
if($sign[0] == '=')
return eval("return {$strArr[0]};") == eval("return {$strArr[1]};") ? 1 : 0;
}else{
return $str;
}


}

//计算&& 和 || 的结果
private function getBool($str)
{
// $str = str_replace(' ','',$str);
$index = stripos($str,'&&');
if($index === false){
$sub = stripos($str,'1');
if($sub === false){
return 0;
}else{
return 1;
}
}
$left = $str[$index-1];
$right = $str[$index+2];
if($left && $right){
$str = str_replace("$left&&$right",1,$str);
return $this->getBool($str);
}else{
$str = str_replace("$left&&$right",0,$str);
return $this->getBool($str);
}
}

//验证变量替换为常量后的公式合法性
private function check($str)
{

//判断连续)(
if(substr_count($str,')(')) {
return ['code'=>400,'msg'=>'两个括号之间不能没有运算符'];
// return $this->resultError('两个括号之间不能没有运算符');
}
//判断()是否成对
$stack = [];
for ($i = 0; $i < strlen($str); $i++) {
$item = $str[$i];
if ('(' === $item) {
array_push($stack,'(');
} elseif (')' === $item) {
if (count($stack) > 0) {
array_pop($stack);
} else {
return ['code'=>400,'msg'=>'括号不配对'];
// return $this->resultError('括号不配对');
}
}
}
if (0 !== count($stack)) {
return ['code'=>400,'msg'=>'括号不配对'];
// return $this->resultError('括号不配对');
}

//错误情况,运算符结尾
if(preg_match('/[\x\÷\+\-\*\/]$/',$str)){
return ['code'=>400,'msg'=>'不能以运算符结尾'];
// return $this->resultError('不能以运算符结尾');
}
// 错误情况,( 后面是运算符
if(preg_match('/\([\x\÷\*\/]/',$str)){
return ['code'=>400,'msg'=>'左括号后面不能是运算符'];
// return $this->resultError('左括号后面不能是运算符');
}
// 错误情况,)前面是运算符
if(preg_match('/[\x\÷\+\-\*\/]\)/',$str)){
return ['code'=>400,'msg'=>'右括号前面不能是运算符'];
// return $this->resultError('右括号前面不能是运算符');
}
// 错误情况,运算符连续
if(preg_match('/[\x\÷\+\-\*\/]{2,}/',$str)){
return ['code'=>400,'msg'=>'运算符不能连续'];
// return $this->resultError('运算符不能连续');
}
//空括号
if(substr_count($str,'()')){
return ['code'=>400,'msg'=>'括号中不能没有内容'];
// return $this->resultError('括号中不能没有内容');
}
//除了数字,运算符,括号 还有其他的符号桌字母 如引号等
$str = preg_replace('/\n|\r|\s|\d|\+|\-|x|\/|\*|\÷|(\>=)|(\<=)|\>|\<|(\!=)|\=|(\&\&)|(\|\|)|\(|\)|\,|\.|(if)/','',$str);
if($str){
return ['code'=>400,'msg'=>'含有非法符号'];
}
}

//验证if函数的合法性
private function verifyIf($str)
{
//逗号是否是两个
$num = substr_count($str,',');
if($num != 2){
return $this->resultError('if函数格式不正确');
}
//第二部分和第三部分是否是四则运算表达式
$strArr = explode(',',$str);
$this->check($strArr[1]);
$this->check($strArr[2]);
//判断第一部分是否是关系表达式 或者 是逻辑表达式 或者 是二者混合

}

//验证公式合法性(随机数字替换计算因子之后)
public function test($str)
{
if(substr_count($str,'}{')) {
return ['code'=>400,'msg'=>'不能将两个计算因子写在一起'];
// return $this->resultError('不能将两个计算因子写在一起');
}
//判断{}是否成对
$stack = [];
for ($i = 0; $i < strlen($str); $i++) {
$item = $str[$i];
if ('{' === $item) {
array_push($stack,'(');
} elseif ('}' === $item) {
if (count($stack) > 0) {
array_pop($stack);
} else {
return ['code'=>400,'msg'=>'计算因子不合法'];
// return $this->resultError('计算因子不合法');
}
}
}
//判断连续}{
if(substr_count($str,'}{')) {
return ['code'=>400,'msg'=>'两个计算因子之间不能没有运算符'];
// return $this->resultError('两个括号之间不能没有运算符');
}
//替换成数字
preg_match_all('/\{.*?\}/',$str,$out);
$items = $out[0];
foreach ($items as $v){
$str = str_replace($v,rand(1,10),$str);
}
$res = $this->check($str);
if($res){
return $res;
}
return true;
}
}

posted on 2019-10-17 18:38  我却醉的像条狗  阅读(601)  评论(1编辑  收藏  举报

导航