代码之家  ›  专栏  ›  技术社区  ›  Jeff

使用正则表达式进行哈希表/字典/映射查找

  •  19
  • Jeff  · 技术社区  · 17 年前

    我试图找出是否有一种合理有效的方法来执行字典(或散列,或映射,或任何您喜欢的语言调用的)中的查找,其中键是正则表达式,字符串是根据键集进行查找的。例如(在Python语法中):

    >>> regex_dict = { re.compile(r'foo.') : 12, re.compile(r'^FileN.*$') : 35 }
    >>> regex_dict['food']
    12
    >>> regex_dict['foot in my mouth']
    12
    >>> regex_dict['FileNotFoundException: file.x does not exist']
    35
    

    我可以想出一种简单的方法来实现这一点,我迭代字典中的所有键,并尝试将传入的字符串与它们匹配,但随后我失去了哈希映射的O(1)查找时间,取而代之的是O(n),其中n是字典中的键数。这可能是一个大问题,因为我预计这本词典会变得非常庞大,我需要一遍又一遍地搜索它(实际上,我需要对文本文件中读取的每一行进行迭代,文件的大小可以达到数百兆字节)。

    或者,如果您知道一种在数据库中完成这种查找的方法,那也将非常好。

    (任何编程语言都可以——我使用的是Python,但我对这里的数据结构和算法更感兴趣。)

    我认为O(1)在这种情况下是不可能的;不过,我愿意接受任何低于O(n)的东西。此外,底层数据结构可以是任何内容,但我想要的基本行为是我上面写的:查找字符串,并返回与正则表达式键匹配的值。

    19 回复  |  直到 10 年前
        1
  •  4
  •   Edward Kmett    17 年前

    • 单个字符只是成为trie节点。
    • [a-z]范围在该范围内的每个字符下重复插入相同的后续子节点。小心,虽然插入/更新可能有点昂贵,但搜索可以是字符串大小的线性。使用一些占位符可以控制常见的组合爆炸情况。
    • (foo)|(bar)节点成为多个插入

    Perl有两个类似于Text::Trie的模块,您可以通过raid获取想法。(见鬼,我想我甚至在很久以前写过一篇)

        2
  •  4
  •   Adam Rosenfield    17 年前

    您应该选择适合您试图解决的问题的数据结构。如果必须匹配任意正则表达式,我不知道有什么好的解决方案。如果要使用的正则表达式类限制性更强,则可以使用数据结构,例如 trie suffix tree

        3
  •  4
  •   Andru Luvisi    17 年前

    在一般情况下,您需要的是lexer生成器。它需要一组正则表达式并将它们编译成识别器。如果您使用的是C语言,“lex”将起作用。我从未在Python中使用过lexer生成器,但似乎有一些可以选择。谷歌展示 PLY PyGgy PyLexer

    如果正则表达式在某种程度上彼此相似,那么您可以采取一些快捷方式。我们需要更多地了解您试图解决的最终问题,以便提出任何建议。你能分享一些示例正则表达式和一些示例数据吗?

    不会 工作罗布·派克 once said

        4
  •  4
  •   Trevor Strohman Trevor Strohman    17 年前

    这是绝对可能的,只要您使用的是“真正的”正则表达式。教科书中的正则表达式可以被 deterministic finite state machine ,这主要意味着您不能在其中包含反向引用。

    正则语言的一个特性是“两种正则语言的并集是正则的”,这意味着您可以使用单个状态机同时识别任意数量的正则表达式。相对于表达式的数量,状态机以O(1)时间运行(相对于输入字符串的长度,状态机以O(n)时间运行,但哈希表也以O(n)时间运行)。

        5
  •  3
  •   Eli Courtwright    17 年前

    如果你有一本字典,比如

    regex_dict = { re.compile("foo.*"): 5, re.compile("f.*"): 6 }
    

    在这种情况下 regex_dict["food"]

        6
  •  3
  •   Florian Nigsch Florian Nigsch    17 年前

    那么以下内容呢:

    class redict(dict):
    def __init__(self, d):
        dict.__init__(self, d)
    
    def __getitem__(self, regex):
        r = re.compile(regex)
        mkeys = filter(r.match, self.keys())
        for i in mkeys:
            yield dict.__getitem__(self, i)
    

    它基本上是Python中dict类型的一个子类。有了它,您可以提供一个正则表达式作为键,并且使用yield以一种可移植的方式返回与此正则表达式匹配的所有键的值。

    使用此选项,您可以执行以下操作:

    >>> keys = ["a", "b", "c", "ab", "ce", "de"]
    >>> vals = range(0,len(keys))
    >>> red = redict(zip(keys, vals))
    >>> for i in red[r"^.e$"]:
    ...     print i
    ... 
    5
    4
    >>>
    
        7
  •  3
  •   rptb1    13 年前

    这里是一种有效的方法,它将键组合到一个编译的regexp中,因此不需要在键模式上进行任何循环。它滥用法律 lastindex

    如果不重新编译regexp,这个映射是不可扩展的(不能定义新的键),但是在某些情况下它很方便。

    # Regular expression map
    # Abuses match.lastindex to figure out which key was matched
    # (i.e. to emulate extracting the terminal state of the DFA of the regexp engine)
    # Mostly for amusement.
    # Richard Brooksby, Ravenbrook Limited, 2013-06-01
    
    import re
    
    class ReMap(object):
    
        def __init__(self, items):
            if not items:
                items = [(r'epsilon^', None)] # Match nothing
            key_patterns = []
            self.lookup = {}
            index = 1
            for key, value in items:
                # Ensure there are no capturing parens in the key, because
                # that would mess up match.lastindex
                key_patterns.append('(' + re.sub(r'\((?!\?:)', '(?:', key) + ')')
                self.lookup[index] = value
                index += 1
            self.keys_re = re.compile('|'.join(key_patterns))
    
        def __getitem__(self, key):
            m = self.keys_re.match(key)
            if m:
                return self.lookup[m.lastindex]
            raise KeyError(key)
    
    if __name__ == '__main__':
        remap = ReMap([(r'foo.', 12), (r'FileN.*', 35)])
        print remap['food']
        print remap['foot in my mouth']
        print remap['FileNotFoundException: file.x does not exist']
    
        8
  •  2
  •   Brad Gilbert    17 年前

    有一个Perl模块就是这样做的 Tie::Hash::Regex

    use Tie::Hash::Regex;
    my %h;
    
    tie %h, 'Tie::Hash::Regex';
    
    $h{key}   = 'value';
    $h{key2}  = 'another value';
    $h{stuff} = 'something else';
    
    print $h{key};  # prints 'value'
    print $h{2};    # prints 'another value'
    print $h{'^s'}; # prints 'something else'
    
    print tied(%h)->FETCH(k); # prints 'value' and 'another value'
    
    delete $h{k};   # deletes $h{key} and $h{key2};
    
        9
  •  2
  •   Nick Barnes    13 年前

    @rptb1您不必避免捕获组,因为您可以使用re.groups来计算它们。这样地:

    # Regular expression map
    # Abuses match.lastindex to figure out which key was matched
    # (i.e. to emulate extracting the terminal state of the DFA of the regexp engine)
    # Mostly for amusement.
    # Richard Brooksby, Ravenbrook Limited, 2013-06-01
    
    import re
    
    class ReMap(object):
        def __init__(self, items):
            if not items:
                items = [(r'epsilon^', None)] # Match nothing
            self.re = re.compile('|'.join('('+k+')' for (k,v) in items))
            self.lookup = {}
            index = 1
            for key, value in items:
                self.lookup[index] = value
                index += re.compile(key).groups + 1
    
        def __getitem__(self, key):
            m = self.re.match(key)
            if m:
                return self.lookup[m.lastindex]
            raise KeyError(key)
    
    def test():
        remap = ReMap([(r'foo.', 12),
                       (r'.*([0-9]+)', 99),
                       (r'FileN.*', 35),
                       ])
        print remap['food']
        print remap['foot in my mouth']
        print remap['FileNotFoundException: file.x does not exist']
        print remap['there were 99 trombones']
        print remap['food costs $18']
        print remap['bar']
    
    if __name__ == '__main__':
        test()
    

    遗憾的是,很少有RE引擎真正将regexp编译成机器代码,尽管这并不特别困难。我怀疑有一个数量级的性能改进等待着有人制作一个真正好的RE-JIT库。

        10
  •  1
  •   erickson    17 年前

    "n-grams" . 创建一个从单词的n个字符块到整个单词的倒排索引。当给定一个模式时,将其分成n个字符的块,并使用索引计算匹配单词的评分列表。

    即使您不能接受近似值,在大多数情况下,这仍然会提供精确的过滤机制,这样您就不必对每个键应用正则表达式。

        11
  •  1
  •   Darius Bacon    17 年前

    这个问题的一个特例出现在70年代面向演绎数据库的人工智能语言中。这些数据库中的键可以是带有变量的模式——就像没有*或|运算符的正则表达式一样。他们倾向于对索引使用trie结构的奇特扩展。参见诺维格的《克里普》*.lisp Paradigms of AI Programming

        12
  •  1
  •   Aaron Digulla    17 年前

    如果您有一小组可能的输入,您可以缓存第二个dict中出现的匹配项,并为缓存的值获取O(1)。

    如果一组可能的输入太大而无法缓存,但也不是无限的,那么您可以将最后N个匹配项保留在缓存中(查看谷歌的“LRU地图”——最近使用最少的)。

        13
  •  1
  •   mccutchen mccutchen    17 年前

    • 记忆散列查找
    • 在备忘录表中预先设定种子(不确定该称为什么…正在预热缓存?)

    为了避免多个键匹配输入的问题,我给每个regex键一个优先级,并使用最高优先级。

        14
  •  0
  •   Jimmy    17 年前

        15
  •  0
  •   Moe    17 年前

    例如,如果有人这样做会发生什么:

    >>> regex_dict['FileNfoo']
    

    这样的事情怎么可能是O(1)?

        16
  •  0
  •   Alex Coventry    17 年前

    信息技术 通过将搜索表达式连接到一个用“|”分隔的大正则表达式中,可以让正则表达式编译器为您完成大部分工作。在这种情况下,聪明的正则表达式编译器可能会在备选方案中搜索共性,并设计出一种比简单地依次检查每个选项更有效的搜索策略。但我不知道是否有编译器可以做到这一点。

        17
  •  0
  •   ididak    17 年前

    这实际上取决于这些正则表达式的外观。如果你没有太多的正则表达式,它们几乎可以匹配 .* \d+ '的正则表达式,而不是 包含 a*b*c “在 ^\d+a\*b\*c:\s+\w+

    为正则表达式构建反向索引(rabin karp hash(“固定模式”)->包含“固定模式”的正则表达式列表)。然后在匹配时,使用Rabin-Karp散列计算滑动散列并查找反向索引,一次前进一个字符。现在有了O(1)查找反向索引非匹配项和合理的O(k)查找匹配项的时间,k是反向索引中正则表达式列表的平均长度。对于许多应用程序,k可能非常小(小于10)。反转索引的质量(假阳性意味着更大的k,假阴性意味着错过匹配)取决于索引器对正则表达式语法的理解程度。如果正则表达式是由人类专家生成的,那么它们也可以为包含的固定模式提供提示。

        18
  •  0
  •   JMax Dan    14 年前


    我正在使用 C++/CLI 用于开发名为 LanguageProcessor.dll ,此库的核心是一个lex_规则类,它基本上包含:

    • 活动成员

    构造函数加载正则表达式字符串并调用必要的代码,以便使用 DynamicMethod Emit Reflexion ... 程序集中还存在其他类,如meta和object,它们通过发布者和接收者类的简单名称来构造和实例化对象,接收者类为每个匹配的规则提供操作处理程序。

    fasterlex_engine 那就编一本字典 <Regex, action_delegate> 从要运行的数组中加载定义的。

    map_rule[gcnew Regex("[a-zA-Z]")];
    

    下面是我的一些代码片段:

    public ref class lex_rule: ILexRule
    {
    private:
        Exception           ^m_exception;
        Regex               ^m_pattern;
    
        //BACKSTORAGE delegates, esto me lo aprendi asiendo la huella.net de m*e*da JEJE
        yy_lexical_action   ^m_yy_lexical_action; 
        yy_user_action      ^m_yy_user_action;
    
    public: 
        virtual property    String ^short_id; 
    private:
        void init(String ^_short_id, String ^well_formed_regex);
    public:
    
        lex_rule();
        lex_rule(String ^_short_id,String ^well_formed_regex);
        virtual event    yy_lexical_action ^YY_RULE_MATCHED
        {
            virtual void add(yy_lexical_action ^_delegateHandle)
            {
                if(nullptr==m_yy_lexical_action)
                    m_yy_lexical_action=_delegateHandle;
            }
            virtual void remove(yy_lexical_action ^)
            {
                m_yy_lexical_action=nullptr;
            }
    
            virtual long raise(String ^id_rule, String ^input_string, String ^match_string, int index) 
            {
                long lReturn=-1L;
                if(m_yy_lexical_action)
                    lReturn=m_yy_lexical_action(id_rule,input_string, match_string, index);
                return lReturn;
            }
        }
    };
    

    现在是执行大量模式/动作对的fasterlex_引擎类:

    public ref class fasterlex_engine 
    {
    private: 
        Dictionary<String^,ILexRule^> ^m_map_rules;
    public:
        fasterlex_engine();
        fasterlex_engine(array<String ^,2>^defs);
        Dictionary<String ^,Exception ^> ^load_definitions(array<String ^,2> ^defs);
        void run();
    };
    

    此代码通过参数符号创建构造函数调用器

    inline Exception ^object::builder(ConstructorInfo ^target, array<Type^> ^args)
    {
    try
    {
        DynamicMethod ^dm=gcnew DynamicMethod(
            "dyna_method_by_totem_motorist",
            Object::typeid,
            args,
            target->DeclaringType);
        ILGenerator ^il=dm->GetILGenerator();
        il->Emit(OpCodes::Ldarg_0);
        il->Emit(OpCodes::Call,Object::typeid->GetConstructor(Type::EmptyTypes)); //invoca a constructor base
        il->Emit(OpCodes::Ldarg_0);
        il->Emit(OpCodes::Ldarg_1);
        il->Emit(OpCodes::Newobj, target); //NewObj crea el objeto e invoca al constructor definido en target
        il->Emit(OpCodes::Ret);
        method_handler=(method_invoker ^) dm->CreateDelegate(method_invoker::typeid);
    }
    catch (Exception ^e)
    {
        return  e;
    }
    return nullptr;
    

    }

    这段代码附加了一个any处理函数(静态或非静态),用于处理由输入字符串匹配引发的回调

    Delegate ^connection_point::hook(String ^receiver_namespace,String ^receiver_class_name, String ^handler_name)
    {
    Delegate ^d=nullptr;
    if(connection_point::waitfor_hook<=m_state) // si es 0,1,2 o mas => intenta hookear
    { 
        try 
        {
            Type ^tmp=meta::_class(receiver_namespace+"."+receiver_class_name);
            m_handler=tmp->GetMethod(handler_name);
            m_receiver_object=Activator::CreateInstance(tmp,false); 
    
            d=m_handler->IsStatic?
                Delegate::CreateDelegate(m_tdelegate,m_handler):
                Delegate::CreateDelegate(m_tdelegate,m_receiver_object,m_handler);
    
            m_add_handler=m_connection_point->GetAddMethod();
            array<Object^> ^add_handler_args={d};
            m_add_handler->Invoke(m_publisher_object, add_handler_args);
            ++m_state;
            m_exception_flag=false;
        }
        catch(Exception ^e)
        {
            m_exception_flag=true;
            throw gcnew Exception(e->ToString()) ;
        }
    }
    return d;       
    }
    

    array<String ^,2> ^defs=gcnew array<String^,2>  {/*   shortID    pattern         namespc    clase           fun*/
                                                        {"LETRAS",  "[A-Za-z]+"     ,"prueba",  "manejador",    "procesa_directriz"},
                                                        {"INTS",    "[0-9]+"        ,"prueba",  "manejador",    "procesa_comentario"},
                                                        {"REM",     "--[^\\n]*"     ,"prueba",  "manejador",    "nullptr"}
                                                    }; //[3,5]
    
    //USO EL IDENTIFICADOR ESPECIAL "nullptr" para que el sistema asigne el proceso del evento a un default que realice nada
    fasterlex_engine ^lex=gcnew fasterlex_engine();
    Dictionary<String ^,Exception ^> ^map_error_list=lex->load_definitions(defs);
    lex->run();
    
        19
  •  0
  •   DangerMouse    14 年前

    这个问题与正则表达式无关——如果字典中的键与lambdas的函数相同,则会出现同样的问题。因此,您面临的问题是,是否有一种方法可以对函数进行分类,以确定其是否返回真值,这不是搜索问题,因为f(x)在之前通常是未知的。

    --DM