找出所有可能的小写化学式的排列

时间:2012-09-19 19:24:31

标签: regex chemistry

我正试图解决小写化学式中的歧义。由于某些元素名称是其他元素名称的子字符串,并且它们都在一起运行,因此对于相同的模式可以存在多个全局匹配。

针对字符串/^((h)|(s)|(hg)|(ga)|(as))+$/考虑正则表达式hgas。有两种可能的匹配。 hg, ash, s, ga(与输入相比无序,但不是问题)。显然,所有可能符号的正则表达式会更长,但这个例子是为了简单起见。

正则表达式强大的前瞻和外观让它能够最终确定即使很长的字符串是否与此模式匹配,或者没有可能的字母排列。它将努力尝试所有可能的匹配排列,例如,如果它使用剩余的g到达字符串的末尾,请返回并重试其他组合。

我正在寻找正则表达式或具有某种扩展名的语言,这增加了在找到匹配项后继续查找匹配项的能力,在这种情况下,查找h, s, ga以及{ {1}}。

为这个问题重建正则表达式的复杂前瞻和后视功能似乎不是一个合理的解决方案,特别是考虑到最终的正则表达式在每个符号后面还包含一个\ d *。

我考虑过颠倒正则表达式hg, as的顺序,以找到其他映射,但最多只能找到一个额外的匹配,而我在正则表达式中没有理论背景知道它是否是甚至是合理的尝试。

我使用现有的正则表达式创建了一个示例页面,它为给定的小写字符串找到1或0个匹配项,并正确地将其返回大写(并且不按顺序)。它在匹配中使用前100个化学符号。

http://www.ptable.com/Script/lowercase_formula.php?formula=hgas

tl; dr:我有一个正则表达式,用于匹配字符串中0或1个可能的化学式排列。如何找到超过1场比赛?

4 个答案:

答案 0 :(得分:3)

我很清楚这个答案可能是偏离主题的(如在方法中),但我认为这很有趣,它解决了OP的问题。

如果您不介意学习新语言(Prolog),那么它可能会帮助您生成所有可能的组合:

name(X) :- member(X, ['h', 's', 'hg', 'ga', 'as']).

parse_([], []).
parse_(InList, [HeadAtom | OutTail]) :-
    atom_chars(InAtom, InList),
    name(HeadAtom),
    atom_concat(HeadAtom, TailAtom, InAtom),
    atom_chars(TailAtom, TailList),
    parse_(TailList, OutTail).

parse(In, Out) :- atom_chars(In, List), parse_(List, Out).

示例运行:

?- parse('hgas', Out).
Out = [h, ga, s] ;
Out = [hg, as] ;
false.

改进版本,包括对数字的处理有点长:

isName(X) :- member(X, ['h', 's', 'hg', 'ga', 'as', 'o', 'c']).

% Collect all numbers, since it will not be part of element name.
collect([],[],[]).
collect([H|T], [], [H|T]) :-
    \+ char_type(H, digit), !.
collect([H|T], [H|OT], L) :-
    char_type(H, digit), !, collect(T, OT, L).

parse_([], []).
parse_(InputChars, [Token | RestTokens]) :-
    atom_chars(InputAtom, InputChars),
    isName(Token),
    atom_concat(Token, TailAtom, InputAtom),
    atom_chars(TailAtom, TailChars),
    parse_(TailChars, RestTokens).

parse_(InputChars, [Token | RestTokens]) :-
    InputChars = [H|_], char_type(H, digit),
    collect(InputChars, NumberChars, TailChars),
    atom_chars(Token, NumberChars),
    parse_(TailChars, RestTokens).

parse(In, Out) :- atom_chars(In, List), parse_(List, Out).

示例运行:

?- parse('hgassc20h245o', X).
X = [h, ga, s, s, c, '20', h, '245', o] ;
X = [hg, as, s, c, '20', h, '245', o] ;
false.

?- parse('h2so4', X).
X = [h, '2', s, o, '4'] ;
false.

?- parse('hgas', X).
X = [h, ga, s] ;
X = [hg, as] ;
false.

答案 1 :(得分:1)

您没有找到执行此操作的通用正则表达式库的原因是因为所有正则表达式都无法执行此操作。有正则表达式不会终止。

想象一下你的例子,你刚刚将空字符串添加到术语列表中,然后

'hgas'可能是:

['hg', 'as']
['hg', '', 'as']
['hg', '', '', 'as']

你可能只需要自己动手。

在伪代码中:

def findall(term, possible):
    out = []

    # for all the terms
    for pos in possible:

        # if it is a candidate
        if term.startswith(pos):

            # combine it with all possible endings
            for combo in findall(term.removefrombegining(pos), possible):
                newCombo = combo.prepend(out)
                out.append(newCombo)
    return out


findall('hgas', ['h', 'as', ...])

以上内容将以指数时间运行,因此动态编程将成为一个指数级大问题。胜利Memoization

最后值得注意的是上面的代码没有检查它是否完全匹配。

即。 'hga'可能会返回['hg']作为一种可能性。

我会留下实际的编码,记忆和最后的打嗝,正如我的教授所说的那样,“对读者的锻炼”

答案 2 :(得分:0)

不要使用正则表达式。正如你所说,正则表达式只匹配1个元素,而你需要找到字符串的所有可能的“含义”。鉴于每个元素的长度为1-2个字符,我会选择这个算法(原谅伪代码):

string[][] results;



void formulas(string[] elements, string formula){
    string[] elements2=elements;


    if(checkSymbol(formula.substring(0,1))){
        elements.append(formula.substring(0,1));
        if(formula.length-1 ==0){
            results.append(elements);
        } else {
            formulas(elements,formula.substring(1,formula.length);
        }
    }
    if(checkSymbol(formula.substring(0,2))){
        elements2.append(formula.substring(0,2));
        if(formula.length-2 ==0){
            results.append(elements2);
        } else {
            formulas(elements2,formula.substring(2,formula.length);
        }
    }


}

bool checkSymbol(string symbol){
    // verifies if symbol is the name of an element
}

输入"hgas"(让我们先深入了解)

第一步: checkSymbol(formula.substring(0,1))

属于"h"
elements2 = [h]

递归调用,if(checkSymbol(formula.substring(0,1))) false

然后测试ga =>真

elements2 = [h, ga]

第三次递归调用

测试schecksymbol返回true,然后元素[h, ga, s]。子字符串的长度为0,因此它会将结果附加到第一个数组:[h, ga, s]

- 让我们回到第一步的第二个“分支”

测试checkSymbol(formula.substring(0,2)发现"hg"也是一个元素

elements2 = [hg]

然后我们致电formulas([hg],"as")

"a"的测试失败(它不是一个元素),"as"的测试工作,长度完全消耗,结果[hg,as]附加到{{1} }

这个algorythm应该在最坏的情况下在O(n ^ 2)时间内运行,n是字符串的长度。

答案 3 :(得分:0)

这不是regexp的工作,你需要更像状态机的东西。

你需要解析弹出所有已知符号的字符串,如果没有则停止,然后继续。如果在一个分支上消耗了整个字符串,那么您已经找到了可能性。

在PHP中,类似于:

<?php
    $Elements = array('h','he','li','s','ga','hg','as',
         // "...and there may be many others, but they haven't been discovered".
    );

    function check($string, $found = array(), $path = '')
    {
        GLOBAL $Elements, $calls;
        $calls++;
        if ('' == $string)
        {
            if (!empty($path))
                $found[] = $path;
            return $found;
        }
        $es = array_unique(array(
                  substr($string, 0, 1), // Element of 1 letter ("H")
                  substr($string, 0, 2), // Element of 2 letter ("He")
        ));
        foreach($es as $e)
            if (in_array($e, $Elements))
                    $found  = check(substr($string, strlen($e)), $found, $path.$e.',');
        return $found;
    }

    print_r(check('hgas'));
    print "in $calls calls.\n";
?>