NFA解决正则匹配

2021-11-10周三下午面试,遇到一道题挺有意思,哪家不能说 反正是个外企。

给你一个字符串 str 和一个字符规则 reg,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

"."表示匹配任意单个字符

"*"表示匹配零个或多个前面的那一个元素

当时我表示这应该用不确定自动机来做,但是我短时间写不出来。

就给他们画了一个状态图

类似这种

 

这种图比较好理解但是 程序实现比较麻烦,然后我就去翻了一下编译原理。

应该添加epsilon 程序才容易 实现

 好那么说一下怎么构造NFA

先定义节点

   public class Node
    {
        public bool IsEnd { get; set; }
        public Dictionary<char, List<Node>> Transform { get; set; }
        public Node()
        {
            Transform = new Dictionary<char, List<Node>>();
        }
    }

 

public class NFA
{
private char epsilon = (char)1;
private char dot = '.';

}

 

Transform 表示图上面的箭头连接到的节点,对应的key表示字母
构造最难的地方在于类似a*的构造,可以参考上面的图片
        public Node CreateNFA(string reg)
        {
            Node n = new Node();
            Node current = n;
            int index = 0;
            while (index < reg.Length)
            {
                Node next = new Node();
                //类似a*实现
                if (index + 1 < reg.Length && reg[index + 1] == '*')
                {
                    Node n1 = new Node();
                    Node n2 = new Node();
                    //对应上图0节点到1节点,0节点到3节点
                    current.Transform.Add(epsilon, new List<Node>() { n1, next });
                    //对应上图1节点到2节点
                    n1.Transform.Add(reg[index], new List<Node>() { n2 });
                    //2节点到3节点,对应上图2节点到1节点
                    n2.Transform.Add(epsilon, new List<Node>() { next, n1 });

                    index += 2;
                }
                else
                {
                    current.Transform.Add(reg[index], new List<Node>() { next });
                    index++;
                }
                current = next;
            }
            current.IsEnd = true;
            return n;
        }

 

  接下来是验证的部分,简单说就是从左往右扫字符串,然后根据NFA状态机来匹配,主要特殊处理一下"."和epsilon

        public bool isMatch(string str, int currentIndex, Node n)
        {
            //终结判断
            if (currentIndex == str.Length)
            {
                return IsEnd(n);
            }
            //单个字符匹配
            if (n.Transform.ContainsKey(str[currentIndex]))
            {
                foreach (var item in n.Transform[str[currentIndex]])
                {
                    if (isMatch(str, currentIndex + 1, item))
                    {
                        return true;
                    }
                }
            }
            //匹配 .
            if (n.Transform.ContainsKey(dot))
            {
                foreach (var item in n.Transform[dot])
                {
                    if (isMatch(str, currentIndex + 1, item))
                    {
                        return true;
                    }
                }
            }
            //处理epsilon跳转,不消耗字符
            if (n.Transform.ContainsKey(epsilon))
            {
                foreach (var item in n.Transform[epsilon])
                {
                    if (isMatch(str, currentIndex, item))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        /// <summary>
        /// 分两种 已经到末尾了和通过epsilon到末尾
        /// </summary>
        /// <returns></returns>
        private bool IsEnd(Node n)
        {
            if (n.IsEnd)
            {
                return true;
            }else 
            {
                if (n.Transform.ContainsKey(epsilon))
                {
                    foreach (var item in n.Transform[epsilon])
                    {
                        return IsEnd(item);
                    }
                }
            }
            return false;
        }

后面简单加了个单元测试

[Test]
        public void Test1()
        {
            NFA nfa = new NFA();
            var nfaNode=nfa.CreateNFA("ab*c");
            var result=nfa.isMatch("abbbbbc", 0, nfaNode);
            Assert.AreEqual(true,result);
        }

        [Test]
        public void Test2()
        {
            NFA nfa = new NFA();
            var nfaNode = nfa.CreateNFA("ab*c.");
            var result = nfa.isMatch("abbbbbce", 0, nfaNode);
            Assert.AreEqual(true, result);
        }

        [Test]
        public void Test3()
        {
            NFA nfa = new NFA();
            var nfaNode = nfa.CreateNFA("ab*c");
            var result = nfa.isMatch("ac", 0, nfaNode);
            Assert.AreEqual(true, result);
        }
        [Test]
        public void Test4()
        {
            NFA nfa = new NFA();
            var nfaNode = nfa.CreateNFA("ab*c");
            var result = nfa.isMatch("ad", 0, nfaNode);
            Assert.AreEqual(false, result);
        }

下面把完整的代码 附上来

    public class NFA
    {
        private char epsilon = (char)1;
        private char dot = '.';
        public Node CreateNFA(string reg)
        {
            Node n = new Node();
            Node current = n;
            int index = 0;
            while (index < reg.Length)
            {
                Node next = new Node();
                //类似a*实现
                if (index + 1 < reg.Length && reg[index + 1] == '*')
                {
                    Node n1 = new Node();
                    Node n2 = new Node();
                    //对应上图0节点到1节点,0节点到3节点
                    current.Transform.Add(epsilon, new List<Node>() { n1, next });
                    //对应上图1节点到2节点
                    n1.Transform.Add(reg[index], new List<Node>() { n2 });
                    //2节点到3节点,对应上图2节点到1节点
                    n2.Transform.Add(epsilon, new List<Node>() { next, n1 });

                    index += 2;
                }
                else
                {
                    current.Transform.Add(reg[index], new List<Node>() { next });
                    index++;
                }
                current = next;
            }
            current.IsEnd = true;
            return n;
        }

        public bool isMatch(string str, int currentIndex, Node n)
        {
            //终结判断
            if (currentIndex == str.Length)
            {
                return IsEnd(n);
            }
            //单个字符匹配
            if (n.Transform.ContainsKey(str[currentIndex]))
            {
                foreach (var item in n.Transform[str[currentIndex]])
                {
                    if (isMatch(str, currentIndex + 1, item))
                    {
                        return true;
                    }
                }
            }
            //匹配 .
            if (n.Transform.ContainsKey(dot))
            {
                foreach (var item in n.Transform[dot])
                {
                    if (isMatch(str, currentIndex + 1, item))
                    {
                        return true;
                    }
                }
            }
            //处理epsilon跳转,不消耗字符
            if (n.Transform.ContainsKey(epsilon))
            {
                foreach (var item in n.Transform[epsilon])
                {
                    if (isMatch(str, currentIndex, item))
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        /// <summary>
        /// 分两种 已经到末尾了和通过epsilon到末尾
        /// </summary>
        /// <returns></returns>
        private bool IsEnd(Node n)
        {
            if (n.IsEnd)
            {
                return true;
            }else 
            {
                if (n.Transform.ContainsKey(epsilon))
                {
                    foreach (var item in n.Transform[epsilon])
                    {
                        return IsEnd(item);
                    }
                }
            }
            return false;
        }
    }
    public class Node
    {
        public bool IsEnd { get; set; }
        public Dictionary<char, List<Node>> Transform { get; set; }
        public Node()
        {
            Transform = new Dictionary<char, List<Node>>();
        }
    }
完整代码NFA

总结一下,NFA其实效率不高,如果要优化的话,可以用MYT算法转化成DFA,最小DFA怎么求,emm留给后面吧。

 

posted @ 2021-11-12 12:36  脉脉星辰  阅读(218)  评论(1)    收藏  举报