解析和匹配
一,建立对象
(一)键
package com.Demo2;
import java.util.ArrayList;
public class ChemicalKey {
//键的名称
private String key;
//键的指向的对象
private ArrayList<ChemicalElement> chemicalElements;
public static String[] allKeys;
//构造函数
public ChemicalKey(String key) {
this.key = key;
chemicalElements = new ArrayList<ChemicalElement>(2);
}
/**键,两边的元素
* @param
*/
public void setTarget(ChemicalElement target1,ChemicalElement target2) {
try{
chemicalElements.add(target1);
chemicalElements.add(target2);
}catch (Exception e){
System.out.println("键连接的元素超过2个了!");
}
}
//获得键两边元素
public ArrayList<ChemicalElement> getChemicalElements() {
return chemicalElements;
}
public void print(){
for(int i=0;i<2;i++){
System.out.print(chemicalElements.get(i).getLabel());
System.out.print('-');
}
System.out.println();
}
//把一些常见的键,存起来,String形式,不是对象
private static void SetterAllKeys(){
allKeys = new String[6];
allKeys[0]=".";
allKeys[1]="-";
allKeys[2]="=";
allKeys[3]="#";
allKeys[4]="/";
allKeys[5]="\\\\";
}
public String getKey() {
return key;
}
/**获得所有的Key字符
* @return
*/
public static String[] getAllKeys(){
SetterAllKeys();
return allKeys;
}
}
(二)元素
每个元素都是独立的对象,元素有属性:1,元素名字 label 2,元素的临近的元素environments_2 3,临近元素相应的键keys_2
其他为了方便设置的 4,元素的整体序号 mark 5 所有元素的符号,string类型,6,断键
缺少:离子键的带的电荷量
package com.Demo2;
import java.util.ArrayList;
public class ChemicalElement {
//元素名称
private String label;
//元素的整体序号
private int mark;
//周围的元素和相应的键
private ArrayList<ChemicalElement> environments_2;
private ChemicalKey[] Keys;
private ArrayList<ChemicalKey> Keys_2;
//是元素的个数
public int count=0;
private static String[] allElement;
//断开的键的类型
private ChemicalKey disconnectKey;
public ChemicalElement(String label, int mark) {
this.label = label;
this.mark = mark;
this.Keys = new ChemicalKey[4];
this.Keys_2 =new ArrayList<>();
this.environments_2 = new ArrayList<>();
}
//设置断键
public void setDisconnectKey(ChemicalKey disconnectKey) {
this.disconnectKey = disconnectKey;
}
//设置断键的数字,这里假设一个原SMILES表示最多一个断键
public void setMark(int mark) {
this.mark = mark;
}
/**添加key的方法,同时把键连接的元素也添加进去
* @param key
*/
public void addToKeys(ChemicalKey key,ChemicalElement ce){
Keys_2.add(key);
this.environments_2.add(ce);
}
//返回,list,周围的元素
public ArrayList<ChemicalElement> getEnvironments_2() {
return environments_2;
}
/**返回所有的键
* @return
*/
public ArrayList<ChemicalKey> getKeys_2() {
return Keys_2;
}
//获得断键的序号,默认为0
public int getMark() {
return mark;
}
//获得断键信息
public ChemicalKey getDisconnectKey() {
return disconnectKey;
}
/**获得元素名称
* @return
*/
public String getLabel() {
return new String(label);
}
//获得与之相连元素的键对象
public ChemicalKey getKeyy(ChemicalElement ce){
for(ChemicalKey ck:Keys_2){
if(ck.getChemicalElements().contains(ce)){//某个键,包含这个元素,返回这个键的对象
System.out.println("!!!!!检测到元素在键:"+ck.getKey()+"中");
return ck;
}
}
return null;
}
public static void main(String[] args) {
ChemicalElement c1 = new ChemicalElement("H",0);
}
private static void SetAllElement(){
allElement = new String[100];
allElement[0]="C";
allElement[1]="N";
allElement[2]="O";
allElement[3]="F";
allElement[4]="Br";
allElement[5]="Cl";
allElement[6]="H";
allElement[7]="S";
}
public static String[] getAllElement() {
SetAllElement();
return allElement;
}
}
(三)化学式
化学式,所有的元素(键信息在元素中)
package com.Demo2;
import java.util.ArrayList;
public class ChemicalFormula {
private ArrayList<ChemicalElement> Formula;
/**
* @return
*/
public ArrayList<ChemicalElement> getFormula() {
return Formula;
}
/**
* @param formula
*/
public void setFormula(ArrayList<ChemicalElement> formula) {
Formula = formula;
}
}
二,解析
解析没有达到的一些地方:
- Aromaticity 芳香环的结构识别。小写的字母表示 未识别
- 离子键等带 [] 这个结构的
- @符号,立体结构
- 断键合并,默认是没有重复数字的如1212 ,像1111这种不行。
简单说,本程序识别,仅仅是带括号,大些字母的那种
Input: String s, //待解析的字符串
Int count, //当前解析的位置
Parse(S,Position)
While(count<s长度){
判断是不是断开的键——数字
判断是不是键——-=#.\/
判断是不是元素——CNOFSBrCl[]++
判断是不是括号
Parse(s',position')
(出现括号“[”,将括号当成规模小一点的子问题,仍用这个方法。因此,这个方法要返回头元素,或者第一个元素;
出现括号“]”,终结条件,返回头元素)
}
package com.Demo2;
import java.util.ArrayList;
public class ParseChain {
/**
* 待解析的字符串对象
*/
private String smiles;
/**
* 解析字符串的位置标记
*/
private int position;
/**
* 解析后存放的位置
* cmf:化学式存放
* MyElements_2 以前的list存放
*/
private ArrayList<ChemicalElement> MyElements_2;
private ChemicalFormula cmf;
/**MyElement 要删除的
* 构造函数
* @param smiles
*/
public ParseChain(String smiles) {
this.smiles = smiles.toUpperCase();
String[] smilesList =this.smiles.split("");
MyElements_2 = new ArrayList<ChemicalElement>();
cmf = new ChemicalFormula();
}
public ArrayList<ChemicalElement> getMyElements_2() {
return MyElements_2;
}
public ChemicalFormula getCmf() {
return cmf;
}
public ChemicalElement Parsing(){
String[] smilesList =this.smiles.split(""); //将字符串转成数组
//第一步,完成图示解C步骤
ChemicalElement st = subParsing(smilesList,null,null);
//第二步,从C步骤,完成B步骤,对数字相同的进行拼凑;断键合并
Combine();
cmf.setFormula(MyElements_2); //解析完,将元素存到化学键中
return st;
}
private ChemicalElement subParsing(String[] smileList,ChemicalElement formerElement,ChemicalKey formerKey){
//头元素,记录信息
ChemicalElement start = null;
while(position<smileList.length){
System.out.print(smileList[position]);
//是否是数字,表示断键
if((position<smileList.length) && Character.isDigit(smileList[position].charAt(0))){
formerElement.setMark((int)smileList[position].charAt(0)); //旧-将数字加进去
formerElement.setDisconnectKey(formerKey); //将断键的类型加上去
formerKey = new ChemicalKey("-"); //将标记键置为默认
position++;
}
//是键
else if(isKey(smileList[position])){
formerKey = new ChemicalKey(smileList[position]);//将键对象保存
position++;
}
//是元素
else if(isElement(smileList[position])){
ChemicalElement current=null;
if(smileList[position].equals("[")) { //离子情形
ArrayList<String> Ion = new ArrayList<>();
for (int i = position; i < smileList.length; i++) {
if (smileList[position].equals("]")) { //结束的情形
position++;
break;}
if (smileList[position].equals("[") || smileList[position].equals("+") || smileList[position].toUpperCase().equals("H")) {
continue;
}
Ion.add(smileList[position]);
}
current = new ChemicalElement(Ion.toString(), 0);
}else {
current = new ChemicalElement(smileList[position], 0); //旧-将元素添加进去
}
MyElements_2.add(current); //新-将元素添加到MyElements_2
if(start==null){ //第一个元素
start=current;
}
if(formerElement==null){ //旧-前面是空的,第一个元素情形;此时,键也应该是空的
formerElement = current;
formerKey = new ChemicalKey("-");
}else {
//新 添加元素
formerKey.setTarget(current,formerElement); //键 ---(m,n)
formerElement.addToKeys(formerKey,current); //元素---添加键
current.addToKeys(formerKey,formerElement);
//旧-更新前一个元素;更新键值
formerElement = current;
formerKey = new ChemicalKey("-");
}
position++;
}
//括号内() 迭代,返回头元素
else if(smileList[position].equals("(")){
//旧-此时第一个元素应该被外面的吸纳,其他的循环就好;此时前置的元素依然不能变,即使出现了)符号,这个也是不能变的。
position++;
//判断后面的元素是不是键,如果是,要提前存起来,更新。
if(isKey(smileList[position])){
formerKey = new ChemicalKey(smileList[position]);
}
ChemicalElement ce = subParsing(smileList,null,null);
if(formerElement!=null){ //括号前面的部分处理
//新的添加方式
formerKey.setTarget(ce,formerElement);
formerElement.addToKeys(formerKey,ce);
ce.addToKeys(formerKey,formerElement);
}else{
formerElement = ce;
formerKey = new ChemicalKey("-");
}
//括号后面部分的处理
formerKey = new ChemicalKey("-");
position++;
}
else if(smileList[position].equals(")")){ //这里不对位置进行操作 留在109处理
// 但是要对formerkey做处理 留在109 循环出来后
return start;
}
}
return start;
}
//判断是否是键
String[] allKeys = ChemicalKey.getAllKeys();
public boolean isKey(String element){
for(String c:allKeys){
if(element.equals(c)){
// System.out.println("是键");
return true;
}
}
return false;
}
//判断是否是元素
//两位的元素没搞,比如Br Cl
String[] elements = ChemicalElement.getAllElement();
public boolean isElement(String element){
for(String s:elements){
if(element.equals(s)){
// System.out.println("是元素");
return true;
}
if(element.equals("[")){
return true;
}
}
return false;
}
//将断开的键合起来
// 断键信息1111 这种形式标记的 没搞
private void Combine(){
int maxNumber =0;
for(ChemicalElement ce :MyElements_2){
if(ce.getMark()>maxNumber)
maxNumber =ce.getMark();
}
for(int i=0;i<MyElements_2.size();i++){
if(MyElements_2.get(i).getMark()!=0){
for(int j=i+1;j<MyElements_2.size();j++){
if(MyElements_2.get(i).getMark()==MyElements_2.get(j).getMark()){
MyElements_2.get(i).getDisconnectKey().setTarget(MyElements_2.get(i),MyElements_2.get(j));
MyElements_2.get(i).addToKeys(MyElements_2.get(i).getDisconnectKey(),MyElements_2.get(j));
MyElements_2.get(j).addToKeys(MyElements_2.get(i).getDisconnectKey(),MyElements_2.get(i));
break;
}
}
}
}
}
}
三,匹配
经过查资料,这是一个NP问题,属于图的同构问题。采用一个类似深度优先遍历加回溯的方案,算法叫VF算法。算法流程:
中文文章解释:(百度吧)
1,SMILES表达式的子结构关系检测算法_彭彬
2,VF算法在化学结构检索中的应用_李琰
3,基于VF2算法的分子二维子结构检索_李欣
英文原文:
1,An_Improved_Algorithm_for_Matching_Large_Graphs
2,(Sub)Graph Isomorphism Algorithm for Matching Large Graphs Luigi P. Cordella, Pasquale Foggia, Carlo Sansone,and Mario Vento
过程主要设计的概念:
- SSR 状态空间。上面说到这是一个图问题,所以深度遍历过程中会有一个解空间树。这个SSR状态空间,就是真正解的一个子集空间,用来记录路径的。
- 候选状态集。 当进行到某个元素,这个候选状态集就是:接下来的所有的可选路径。
- Feasibility Rules 。回溯的条件,对于问题,不满足一定条件,就回溯。
package com.Demo2;
import java.util.*;
public class VFMatch {
//query和db图 dbG=databaseGraph
private ChemicalFormula quG;
private ChemicalFormula dbG;
//Ms用于存放空间状态点,也应该是个Map才对
//由于匹配过程,一一对应,不会出现一对多,所以mapde 键是元素是可以的
private Map<ChemicalElement,ChemicalElement> Ms;
public VFMatch(ChemicalFormula quG, ChemicalFormula dbG) {
this.quG = quG;
this.dbG = dbG;
}
/**
* @param state 临时的态
* @param quG query
* @param dbG dbG
* @return boolean
*/
public boolean Match(HashMap<ChemicalElement,ChemicalElement> state, ChemicalFormula quG, ChemicalFormula dbG){
boolean flag = false;
//Mqu 已经在state中的query元素 Mdb 已经在state中的db元素
Set<ChemicalElement> quG_Mid0 = state.keySet();
ArrayList<ChemicalElement> Mqu = new ArrayList<>(quG_Mid0);
Collection<ChemicalElement> dbG_Mid0 = state.values();
ArrayList<ChemicalElement> Mdb= new ArrayList<>(dbG_Mid0);
//IF M(S)cover all the nodes of G2
if(Mqu.size()!=0 && Mqu.size()==quG.getFormula().size()){
System.out.println("到了最后+++++++++++++++++++++++++++++++++++++");
flag = true;
}else{
//compute the candidate of inclusion of Ms
Map<Integer,ArrayList<ChemicalElement>> P = new HashMap<>();
CandidateP(P,quG,dbG,Mqu,Mdb);
System.out.println("计算完成候选的结果");
Set<Integer> entry = P.keySet();
//Foreach p in P:
for(int number:entry){
ChemicalElement que = P.get(number).get(0); //query
ChemicalElement dbe = P.get(number).get(1); //db
//IF the feasibility rules succeed for inclusion of p in Ms
if(FeasibilityRules(que,dbe,Mqu,Mdb,state)){
//compute the state s'
state.put(que,dbe); //添加进去
//call match(s')
flag = Match(state,quG,dbG);
state.remove(que); //回溯,要删除之前的状态
}
if(flag==true){
break;//找到一个解,就返回解,不再探索
}
}
//从这里出来,就是说明没有那走到最后,所以返回
}
return flag;
}
/**搜索的空间P Candidate Pair Set
* @param result 所有可能性集合容器
* @param quG query
* @param bgG database
* @param Mqu M_query
* @param Mdb M_database
* @return
* <1,<C,N>> <2,<C,C>> ...
*/
public void CandidateP(Map<Integer,ArrayList<ChemicalElement>> result, ChemicalFormula quG, ChemicalFormula bgG,
ArrayList<ChemicalElement> Mqu, ArrayList<ChemicalElement> Mdb){
int count=0;
for(ChemicalElement ce:quG.getFormula()){
if(!Mqu.contains(ce)) { //query元素没被选的元素
for (ChemicalElement qe : bgG.getFormula()) {
if(!Mdb.contains(qe)){ //db元素,没被选的元素
System.out.print(ce.getLabel()+"-"+qe.getLabel()+" ");
ArrayList<ChemicalElement> res = new ArrayList<>();
res.add(ce);
res.add(qe);
result.put(count,res);
count++;
}
}
}
}
}
/**
* @param quG query
* @param dbG dataBase
* @param Mqu 已经加入的结点集合
* @param Mdb
* @param match 已经匹配好的对象
* @return
*/
public boolean FeasibilityRules(ChemicalElement quG,ChemicalElement dbG,ArrayList<ChemicalElement> Mqu,ArrayList<ChemicalElement> Mdb,
Map<ChemicalElement,ChemicalElement> match){
if(!quG.getLabel().equals(dbG.getLabel())){ //两个元素名称是否一致
return false;
}
if(quG.getEnvironments_2().size()>dbG.getEnvironments_2().size()){ //quG的度要小于等于dbG的度
return false;
}
if(Mqu.size()==0){ //如果是第一对元素,
System.out.println(" 是第一个元素");
return true;
}
//接下来检测,M中是否已经有跟quG相连的元素,如果相连,必须满足条件:
//1)quG_vID和dbG_vID与已经match的那些节点对中的【至少】一对(quVid,dbVid)分别相邻(quG_vID和dbG_vID分别是已经match的节点quVid和dbVid的“neighbor节点”)
//2)如果存在多个相邻对(quVid,dbVid),则必须要求【所有的】邻接边对( edge(quG_vID,quVid), edge(dbG_vID,dbVid) )的label一样
for(ChemicalElement quG_Mid:Mqu){
if(quG_Mid.getEnvironments_2().contains(quG)){ //quG_Mid是quG的连接元素
ChemicalElement dbG_Mid = match.get(quG_Mid); //映射的元素
if(!dbG_Mid.getEnvironments_2().contains(dbG)){ //映射元素dbG_Mid与dbG之间没有连接
return false;
}
/***
* 下面是需要改正的,键对象创建的太多了。
* 需要把键当成元素的属性,目前是元素是键的属性,用一个map比较好
*/
if(!quG.getKeyy(quG_Mid).getKey().equals( dbG.getKeyy(dbG_Mid).getKey())){ //同一个键对象
return false;
}
}
}
//从这里出来,
//1)可能是不存在与Mqu相连接的情况,因此 return true
//2) 存在于Mqu相连接的情况,且验证符合
return true;
}
}