一、概述标签是精细化操作必不可少的工具。常见的使用场景包括标签推送和千人广告展示。在实际业务中,标签往往通过交集、并集、差集、非运算来组合使用。比如标签组合是A∪B∩C,需要判断用户是否在这个集合中。以千人千面展示广告为例,我们会有这样一个需求:参加过开店计划的广州用户(美甲师或美甲店老板)展示A广告。参与过开店计划的深圳用户(美甲师或美甲店老板)会展示B广告。标签说明:这里的标签都是用户标签,英文标签:美甲师(identity_1),美甲店老板(identity_2),参与开店计划(shop_setup_user),广州(guangzhou),深圳(shenzhen)。2.实现思路首先根据需求可以推导出广告展示的标签表达式:AdvertisementA:(identity_1∪identity_2)∩shop_setup_user∩guangzhouBAdvertisement:(identity_1∪identity_2)∩shop_setup_user∩Shenzhen为了方便起见,意思是“交叉和difference”对所有操作,“交差”用“*+-!”表示,操作没有优先级差异,所以上面的表达式可以写成:一个广告:(identity_1+identity_2)*shop_setup_user*guangzhouB广告:(identity_1+identity_2)*shop_setup_user*shenzhen分析:一个用户包含多个标签,判断“一组标签操作”中是否存在“一个用户”来展示广告。核心是判断一个并集与另一个集合(多个操作的)的交集关系。1.表达式分析结合表达式的意思和“交差”的意思以及符号左右组合运算的原理(“!”除外),连接左右的标签(表达式)的意思右符号可以明确:由“+”连接的两个标签(表达式)是OR关系,只要其中一个与用户的标签相交,则为真。“*”连接的两个标签(表达式)"相交,左右标签与用户标签相交为真。"-"链接的两个标签(表达式)相交。如果左边与用户标签相交,右边不与用户标签相交用户的标签,它是真的。“!”是特殊的,它使它后面的标签(表达式)反转。将其变成二叉树明确含义后,可以看出只要递归操作就可以得到“用户是否在标签表达式中”集合的结果。一种非常适合左右运算的数据结构是二叉树。大致思路是:将表达式转化为二叉树,递归判断二叉树。含义不同,符号无先后之分。A。中缀表达式和后缀表达式中缀表达式常被称为算术表达式,例如:1+2*3/(2+1)。后缀表达式(也称为反向波兰表示法)是操作数后运算符的表达式。比如上面的表达式写成:12321+/*+。也可以达到去掉括号的效果。在转换过程中,堆栈将用于保存操作符号。将转换过程中读取的字符分解为中缀表达式,查找后缀表达式(输出)栈中的内容111+1+1+21+212+*1+2*??12+*31+2*3123+*/1+2*3/123+*/(1+2*3/(123+*/(21+2*3/(21232+*/(+1+2*3/(2+1232+*/(+11+2*3/(2+112321+*/(+)1+2*3/(2+1)12321++*/(1+2*3/(2+1)12321++*/1+2*3/(2+1)12321+/+*1+2*3/(2+1)12321+/*+1+2*3/(2+1)12321+/*+你可以看转换规则对,按顺序读取字符:遇到操作数,写到output.遇到(+-*/,写到运算符栈。遇到),从非空运算符栈弹出一项;如果如果itemisnot(,writetotheoutput,iftheitemis(,则退出循环。循环读取后,将运算符一个一个弹出栈,放在输出后面。代码实现(PHP)函数expressionToSuffixExpressionArray($expression){$charArray=array_reverse(str_split($expression));$operationArray=[];$输出=[];while(($c=array_pop($charArray))!=''){if(in_array($c,['(','+','-','*','/'])){array_push($operationArray,$c);}elseif(in_array($c,[')'])){while($op=array_pop($operationArray)){if($op=='('){中断;}array_push($output,$op);}}else{array_push($output,$c);}}returnarray_merge($output,$operationArray);}//测试代码$expression='3*(2+1)';$result=expressionToSuffixExpressionArray($expression);echo"expression:{$expression}".PHP_EOL;print_r($result);输出:表达式:3*(2+1)Array([0]=>3[1]=>2[2]=>1[3]=>+[4]=>*)已经实现了解析标签表达式的基本表达式解析,对于我们的标签表达式(多个字符组成一个标签),以及逻辑暂时删除“/”并添加“!”修改:functionexpressionToSuffixExpressionArray($expression){$charArray=array_reverse(str_split($expression));$operationArray=[];$输出=[];$表达式='';while(($c=array_pop($charArray))!=''){if(in_array($c,['(','+','-','*'])){if(!empty($expression)){array_push($output,$expression);$expression='';}array_push($operationArray,$c);}elseif(in_array($c,[')'])){if(!empty($expression)){array_push($output,$expression);$表达式='';}while($op=array_pop($operationArray)){if($op=='('){break;}array_push($output,$op);}}elseif(in_array($c,['!'])){if(!empty($expression)){array_push($output,$expression);$expressi在='';}array_push($output,$c);}else{$expression.=$c;}}returnarray_merge($output,$operationArray);}//测试代码$expression='(identity_1+identity_2)*shop_setup_user*guangzhou';$result=expressionToSuffixExpressionArray($expression);echo"expression:{$expression}".PHP_EOL;print_r($result);输出:表达式:(identity_1+identity_2)*shop_setup_user*guangzhouArray([0]=>identity_1[1]=>identity_2[2]=>+[3]=>shop_setup_user[4]=>guangzhou[5]=>*[6]=>*)b。二叉树分析的后缀表达式:根据后缀表达式的含义,对应的是对前两个元素的操作。所以在遍历的时候可以用一个栈来暂存标签表达式。当符号遍历时,会弹出两个标签作为其运算的左右元素,形成一个新节点放回栈中,这样循环就可以形成一棵完整的二叉树。//转后缀表达式的方法...//基础节点类TreeNode{publicstaticfunctioncreate(string$root=''){return['root'=>$root,'left'=>'','right'=>'','相反'=>false,];}}//将表达式数组后缀成二叉树函数suffixExpressionArrayToBinaryTree($suffixExpressionArray){$stack=[];$suffixExpressionArray=array_reverse($suffixExpressionArray);while($item=array_pop($suffixExpressionArray)){if(in_array($item,['+','-','*'])){$node=TreeNode::create($item);}$node['右']=array_pop($stack);$left=array_pop($stack);如果($left['root']=='!'){$node['right']['opposite']=true;$node['left']=array_pop($stack);}else{$node['left']=$left;}array_push($stack,$node);}else{array_push($stack,TreeNode::create($item));}}return$stack;}//测试代码$expression='(identity_1+identity_2)*shop_setup_user*guangzhou';$result=expressionToSuffixExpressionArray($expression);echo"expression:{$expression}".PHP_EOL;print_r($result);$tree=suffixExpressionArrayToBinaryTree($result);print_r($tree);输出:Array([0]=>Array([root]=>*[left]=>Array([root]=>+[left]=>Array([root]=>identity_1[left]=>[right]=>[opposite]=>)[right]=>Array([root]=>identity_2[left]=>[右]=>[对]=>)[对面]=>)[右]=>数组([根]=>*[左]=>数组([根]=>shop_setup_user[左]=>[右]=>[对面]=>)[right]=>Array([root]=>guangzhou[left]=>[right]=>[opposite]=>)[opposite]=>)[opposite]=>))3.判断标签组是否包含用户评论以下符号的含义:“+”连接的两个标签(表达式)是OR关系,只要其中一个与用户的标签相交,则为真。由“*”连接的两个标签(表达式)是交集左右标签之间的关系只有与用户的标签相交时才为真。用“-”连接的两个标签(表达式)是相交的。如果左侧与用户标签相交,右侧不与用户标签相交,则为真。“!”是特殊的,它使后面的标签(表达式)反转。说明:该函数的传入参数设计为“用户标签”和上一步形成的“树”。“用户标签”是一个数组。判断逻辑首先简单判断是否存在于“用户标签”数组中。实际//接上表面的代码//...functionisContained(array$userTags,array$rootNode):bool{$result=false;if(in_array($rootNode['root'],['+','-','*'])){switch($rootNode['root']){case'+':$result=(isContained($userTags,$rootNode['left'])||isContained($userTags,$rootNode['right']));休息;case'-':$result=((isContained($userTags,$rootNode['left'])===true)&&(isContained($userTags,$rootNode['right'])===false));休息;case'*':$result=(isContained($userTags,$rootNode['left'])&&isContained($userTags,$rootNode['right']));休息;}}else{$result=in_array($rootNode['root'],$userTags);}if($rootNode['opposite']){$result=!$result;}返回$结果;}//测试代码//$tree是上一步得到的树$userTags1=['tag1','tag2','identity_1','guangzhou','shop_setup_user'];$result1=isContained($userTags1,$tree[0]);$userTags2=['tag1','tag2','identity_2','shop_setup_user'];$result2=isContained($userTags2,$tree[0]);$userTags3=['tag1','tag2','identity_3','广州','shop_setup_user'];$result3=isContained($userTags3,$tree[0]);var_dump($result1,$result2,$result3);Output:bool(true)bool(false)bool(false)3.场景扩展在实际业务中,标签的组合会更加复杂。除了“标签”和“标签”的组合,还可能有“标签”和“标签组”、“用户标签”和“设备标签”。让我们谈谈如何支持这些需求。1、标签和标签组相互嵌套标签组实际上是通过标签操作组合起来的,例如:标签组1:Atag1+Atag2*Atag3标签组2:Btag4-[标签组1]结果:Btag4-(Atag1+Atag2*Atag3)2.多种标签组合操作。如果有用户标签和设备标签的组合,目前还没有做过这样的需求。如果要做,可以考虑参数isContained用一个“包含用户标签数组和标签数组的设备对象”代替数组,然后标签表达式中的标签加上前缀:usertag(u|),设备标签(d|)。例如:labelexpression:(u|identity_1+u|identity_2)*u|shop_setup_user*d|guangzhou判断时,根据前缀选择使用用户标签还是设备标签进行判断。4.结束语除了“判断一个标签组是否包含用户”的需求外,还有一个需求也是很常见的:“判断一个标签表达式中包含了多少用户”。这个需求除了逻辑之外,还涉及到数据库的设计、实现方案和实际场景。也有关系,这里就不展开讨论了。以上代码段为精简版,可能存在问题,如有错误或遗漏请指正。
