整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Python正则表达式一:字符串的模糊匹配

Python正则表达式一:字符串的模糊匹配

则表达式

正则表达式不是Python中专有的,各种语言工具中都有这个正则表达式的体现。

正常情况下

  • 可以使用相等 (==) 运算符测试两个字符串是否相等。
  • 可以使用 in 运算符或内置字符串方法和 来测试一个字符串是否是另一个字符串的子字符串。.find().index()

但是想判断一个字符串是否为邮箱格式,手机格式袁莉其他自己要求的格式时,这种==in就涜不能满足要求了,它是一个格式定义,但是格式中的内容要满足一定要求。


正则表达式是一种用于匹配和操作文本的强大工具,它是由一系列字符和特殊字符组成的模式,用于描述要匹配的文本模式。

正则表达式可以在文本中查找、替换、提取和验证特定的模式。

正则表达式,又称规则表达式,(Regular Expression,在代码中常简写为regex、regexp或RE),是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符"),是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。

许多程序设计语言都支持利用正则表达式进行字符串操作

例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开来的,

后来在广泛运用于Scala 、PHP、C# 、Java、C++ 、Objective-c、Perl 、Swift、VBScript 、Javascript、Ruby 以及Python等等。

正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。

正则表达式在Python中的体现

在Python中需要通过正则表达式对字符串进?匹配的时候,可以使??个python自带的模块,名字为re。

正则表达式的大致匹配过程是:

  • 1.依次拿出表达式和文本中的字符比较,
  • 2.如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。
  • 3.如果表达式中有量词或边界,这个过程会稍微有一些不同。


正则表达式解决的问题:字符串模糊匹配

简单的例子

正则表达式是一个特殊的字符序列,用于定义复杂字符串匹配功能的模式

要判断一个字符串中包含123这几个值是地可以有如下的写法

写法 1

>>> s='foo123bar'
>>> '123' in s
True

写法2

>>> s='foo123bar'
>>> s.find('123')
3
>>> s.index('123')
3

模块re:解决复杂的问题

假设您要确定字符串是否包含任何三个连续的十进制数字字符,而不是搜索像 这样的固定子字符串,如字符串 '123''foo123bar''foo456bar''234baz''qux678'

主要关注一个函数 .re.search()

re.search(<regex>, <string>)

import re
re.search(...)

还刚才 查找123的例子,使用正则表达式如何实现

>>> s='foo123bar'
 2
 3>>> # One last reminder to import!
 4>>> import re
 5
 6>>> re.search('123', s)
 7<_sre.SRE_Match object; span=(3, 6), match='123'> 
>>> if re.search('123', s):
...     print('Found a match.')
... else:
...     print('No match.')
...
Found a match.
  • <_sre.SRE_Match object; span=(3, 6), match='123'>
  • span=(3, 6)指示找到匹配项的部分。这与切片表示法中的含义相同:<string>

这个时候正则表达式已经告诉你了查找到的字符串在哪里

>>> s[3:6]
'123'

正则表达式特殊符号

元字符

说明

.

代表任意字符

\


[ ]

匹配内部的任一字符或子表达式

[^]

对字符集和取非

-

定义一个区间

\

对下一字符取非(通常是普通变特殊,特殊变普通)

*

匹配前面的字符或者子表达式0次或多次

*?

惰性匹配上一个

+

匹配前一个字符或子表达式一次或多次

+?

惰性匹配上一个

?

匹配前一个字符或子表达式0次或1次重复

{n}

匹配前一个字符或子表达式

{m,n}

匹配前一个字符或子表达式至少m次至多n次

{n,}

匹配前一个字符或者子表达式至少n次

{n,}?

前一个的惰性匹配

^

匹配字符串的开头

\A

匹配字符串开头

$

匹配字符串结束

[\b]

退格字符

\c

匹配一个控制字符

\d

匹配任意数字

\D

匹配数字以外的字符

\t

匹配制表符

\w

匹配任意数字字母下划线

\W

不匹配数字字母下划线

应该场景

  • 数字:^[0-9]*$
  • n位的数字:^\d{n}$
  • 至少n位的数字:^\d{n,}$
  • m-n位的数字:^\d{m,n}$
  • 零和非零开头的数字:^(0|[1-9][0-9]*)$

最简单的例子,手机号13位校验

  • \d表示数字
  • {13}1 个数字



>>> import re
>>> s="^\d{13}$"
>>> re.search(s,"11111")
>>> rs=re.search(s,"11111")
>>> rs
>>> re.search(s,"1234567890123")
<re.Match object; span=(0, 13), match='1234567890123'>
  
>>> re.search(s,"a234567890123")
>>> 

判断字符串中出现紧挨着的三个数字

>>> s='foo123bar'
>>> re.search('[0-9][0-9][0-9]', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>
  • [0-9][这个位置表示聘0-9中任意一个数字
  • [0-9][0-9][0-9]表示出现三个数字位置紧挨着

你也可以换一种办法 [0-9]{3}:

  • [0-9]一个数字
  • {3}:出现三次
>>> s='foo123bar'
>>> re.search('[0-9][0-9][0-9]', s)
<re.Match object; span=(3, 6), match='123'>
>>> re.search('[0-9]{3}', s)
<re.Match object; span=(3, 6), match='123'>
>>>

查找123出现在字符串中 1.3其实代理的就是123

>>> s='foo123bar'
>>> re.search('1.3', s)
<_sre.SRE_Match object; span=(3, 6), match='123'>

>>> s='foo13bar'
>>> print(re.search('1.3', s))
None


[] 与单个字符匹配的元字符

[a-z]   表示 a-z 26个小写字母中的其中一个
[A-Z]   表示 A-Z 26个大写字母中的其中一个
>>> re.search('[a-z]', 'FOObar')
<_sre.SRE_Match object; span=(3, 4), match='b'>
>>> re.search('[0-9][0-9]', 'foo123bar')
<_sre.SRE_Match object; span=(3, 5), match='12'>
  • [0-9]匹配任意数字字符:
  • [0-9a-fA-F]匹配任何十六进制数字字符:
>>> re.search('[0-9a-fA-f]', '--- a0 ---')
<_sre.SRE_Match object; span=(4, 5), match='a'>


匹配任何不是数字的字符:^[^0-9]

>>> re.search('[^0-9]', '12345foo')
<_sre.SRE_Match object; span=(5, 6), match='f'>

如果字符出现在字符类中,但不是第一个字符,则它没有特殊含义,并且与文本字符匹配:

>>> re.search('[#:^]', 'foo^bar:baz#qux')
<_sre.SRE_Match object; span=(3, 4), match='^'>

间断性匹配例如座机号码有一个 -

>>> re.search('[-abc]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
>>> re.search('[abc-]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>
>>> re.search('[ab\-c]', '123-456')
<_sre.SRE_Match object; span=(3, 4), match='-'>

英文点 .

英文的 . 字符匹配除换行符之外的任何单个字符:.

>>> re.search('foo.bar', 'fooxbar')
<_sre.SRE_Match object; span=(0, 7), match='fooxbar'>

>>> print(re.search('foo.bar', 'foobar'))
None
>>> print(re.search('foo.bar', 'foo\nbar'))
None

\w \W

\w匹配任何字母数字单词字符。

单词字符是大写和小写字母、数字和下划线 () 字符,

因此本质上是:\w 等价于[a-zA-Z0-9_]:

\W是与\w相反

[^a-zA-Z0-9_] 
>>> re.search('\W', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>
>>> re.search('[^a-zA-Z0-9_]', 'a_1*3Qb')
<_sre.SRE_Match object; span=(3, 4), match='*'>

\d 匹配任何十进制数字字符 等价于 [0-9]

\D 匹配非数字 [^0-9]

>>> re.search('\d', 'abc4def')
<_sre.SRE_Match object; span=(3, 4), match='4'>

>>> re.search('\D', '234Q678')
<_sre.SRE_Match object; span=(3, 4), match='Q'>

\s 匹配任何空格字符:

\S 是 的反义词。它匹配任何空格字符:\s

>>> re.search('\s', 'foo\nbar baz')
<_sre.SRE_Match object; span=(3, 4), match='\n'>
>>> re.search('\S', '  \n foo  \n  ')
<_sre.SRE_Match object; span=(4, 5), match='f'>


未完待续

转义元字符

下一篇文章介绍

网络图片

头条创作挑战赛#

#醉鱼Java#


代码地址: https://github.com/zuiyu-main/EncryptDemo

在个别特殊领域中,数据的安全问题是非常的重要的,所以需要数据库存储的数据是需要加密存储的。所以也就引申出来本文这个问题,加密之后的密文,还能模糊检索吗,如果能检查,如何做模糊检索呢?

现在的系统设计中,常见的加密字段有、密码、身份证号、手机号、住址信息、银行卡、信用卡以及个别行业的敏感信息。这些信息对加密的要求也不一样,对于密码来说,一般使用不可逆的加密算法就可以,一般不会用到检索。但是对于身份证号或者个别领域中的中文信息,我们是需要支持密文模糊匹配的,下面我们就来看看有哪些实现方式。

本来主要讲两种常规的简单加密做法,主要目标为能实现密文的模糊查询。下面来跟我看第一种。

常规分词加密

常规加密的密文检索功能根据4位英文字符(半角)2个中文字符(全角)作为一个检索条件,将一个字段拆分为多个字段。

比如:zuiyu123

使用4个字符为一组的加密方式。

第一组 zuiy,第二组uiyu,第三组iyu1,第四组yu12,第五组u123...如果字符串很长,依次类推下去。

如果需要检索所有包含检索条件 uiyu 的数据,加密字符后通过 key like ‘%加密uiyu的密文%’查询。

所以这种实现方式就会有一种问题就是,随着加密字符串的增加,密文也会变的越大,所以一般用此处方式需要注意数据库中的字段长度限制

需要注意的是,使用此处方式有一定的限制:

1、支持模糊检索加密,但是加密的密文随原文长度增长

2、支持的模糊检索条件必须大于等于4个英文数字或者2个汉字,不支持短的查询(自定义该局限性,业界常用的就是4个英文数字或者2个汉字,再短的长度不建议支持,因为分词组合会增多从而导致存储的成本增加,反而安全性降低。)。

3、返回的列表不是很精确,需要二次筛选,先解密在进一步筛选。

字符串拆分的代码如下:

protected List<String> loopEncryptString(String input, int chunkSize) {
        int length=input.length();
        List<String> strList=new LinkedList<>();
        for (int i=0; i < length; i++) {
            StringBuilder chunkBuilder=new StringBuilder();
            for (int j=0; j < chunkSize; j++) {
                int index=(i + j) % length;
                chunkBuilder.append(input.charAt(index));
            }
            strList.add(chunkBuilder.toString());

            log.info("第 {} 组:[{}]",i+1,chunkBuilder);
            // 如果到了最后一个分组,则不再循环第一个字符
            if (i + chunkSize >=length) {
                break;
            }
        }
        log.info("分词结果:[{}]",strList);
        return strList;
    }

对于上述文本zuiyu123分词效果如下

下面来看下中文的分词效果:

检索一下,只要我们使用的是包含上述分词结果的条件我们就可以检索的到。

比如我们检索个蛋白质

search result:[[{ID=8dac4d97-f05f-472e-94b2-02828aa235d6, CONTENT=ELYJBkZbfiVaJgTdlgglDg==UYwxxmEMQ9hq1jOax+r5rg==WwCBtglEf6clcWajP9sK+A==4sEGCqZ4P8Osr0dW84zFEA==c2AZejHeUp/5gpPkexfNcg==pvh/TcZRO4zwD+kwbE9lHw==1g30dxyz7z+8TQq+8jYH1A==AsWZOeiprypfrzSK3FtOuw==01vpoSuCXOpKCgcPsNlXyQ==79BPmIhSwMaA7hjN3ENDxA==}]]

可以看到,上述的content字段的内容长度非常的长,所以我们要注意数据库字段长度限制。

除了上面这个方式外,发散一下思维,如果你用过 Elasticsearch 的话,会不会有点想法呢?

因为在中文的场景中,中文既然要分词,选择专业的分词器应该是更合理的啊,所以我们可以使用???

对的,你没猜错,既然是要分词,对于特殊的中文业务场景,直接使用 Elasticsearch 的分词器分词不就好了吗,然后再用 Elasticsearch 的强大检索能力,不就可以满足我们的模糊检索需求了吗,想到就去做,下面就跟着我一起来看下如果用 Elasticsearch 的分词实现密文模糊检索。

分词器分词检索

使用分词器分词进行密文检索的原理:

1、使用 Elasticsearch 自带的正则分词器对加密后的密文进行分词。

2、检索时使用 Elasticsearch 的match进行检索。

本文演示使用AES进行加解密,所以分词器我就直接使用正则匹配,将密文中的内容按照==进行拆分。

下面我们一起进入代码时间,跟随着我的脚本来看看分词密文检索是什么样的。

也欢迎你来实操体验一下,有什么问题欢迎评论区留言告诉我,也可以关注《醉鱼Java》,私信我。

  • 创建一个使用pattern分词器的索引encrypt如下创建索引语句为 Elasticsearch 6.8 的语句

如果使用 7+、8+ 的需要修改为对应的版本。mappings 中的 _doc

put 127.0.0.1:9200/encrypt
{
    "settings": {
        "analysis": {
            "analyzer": {
                "my_analyzer": {
                    "tokenizer": "my_tokenizer"
                }
            },
            "tokenizer": {
                "my_tokenizer": {
                    "type": "pattern",
                    "pattern": "=="
                }
            }
        }
    },
    "mappings": {
        "_doc": {
            "properties": {
                "content": {
                    "type": "text"
                }
            }
        }
    }
}
  • 随便对于一个密文进行分词,可以看到,已经按照我们的语气进行==拆分为多个词语了

其实不难发现,我们使用 AES 加密,就是对分词之后的每个词语进行加密,然后组成一个新的字符串。

还是上面那句话鱼肉的蛋白质含量真的高,我们看一下分词结果。

所以我们按照==拆分之后,检索式再通过加密之后的密文进行检索,也就相当于分词检索了。

检索结果如下:

search result:[{"hits":[{"_index":"encrypt","_type":"_doc","_source":{"content":"ELYJBkZbfiVaJgTdlgglDg==9hF4g5NErtZNS9qFJGYeZA==uH9W7jvdoLIKq5gOpFjhWg==4sEGCqZ4P8Osr0dW84zFEA==c2AZejHeUp/5gpPkexfNcg==1g30dxyz7z+8TQq+8jYH1A==01vpoSuCXOpKCgcPsNlXyQ==kIzJL/y/pnUbkZGjIkz4tw=="},"_id":"1713343285459","_score":2.8951092}],"total":1,"max_score":2.8951092}]

总结

密文的模糊查询就是以空间成本换取的。相比于存储原文,密文比原文增长了好几倍。

所以根据你的业务场景,选择一个合适的加密算法才是最优解。

参考

https://open.taobao.com/docV3.htm?docId=106213&docType=1

https://ningyu1.github.io/20201230/encrypted-data-fuzzy-query.html

正在参加一场关键的技术面试,对面坐着一位经验丰富的面试官。他微笑着提出一个问题:“能否实现一个模糊搜索功能,用JavaScript来写?”这个问题看似简单,但它考验的不仅是你的编程技巧,还考察你在实际场景中解决问题的能力和思维方式。

为了帮助你在这种场景下表现出色,我将带你一起实现一个简单但有效的模糊搜索功能,并详细解释其中的关键点。掌握这项技术,不仅能让你在面试中脱颖而出,还能在实际工作中为用户提供更好的搜索体验。

什么是模糊搜索?

面试官首先解释了“模糊搜索”的概念。模糊搜索是一种技术,它允许你在文本中找到与用户输入接近的结果,即使输入中存在小的错误或字符顺序不完全匹配。这在处理用户可能拼错字或键入字符顺序不一致时特别有用。

实现步骤

接下来,面试官给出了一组字符串数组,要求你在这个数组中实现模糊搜索。你开始思考,决定使用“滑动窗口”技术来解决这个问题。你在面试官的注视下开始编写代码:

const arr=[
    "JavaScript",
    "TypeScript",
    "Python",
    "Java",
    "Ruby on Rails",
    "ReactJS",
    "Angular",
    "Vue.js",
    "Node.js",
    "Django",
    "Spring Boot",
    "Flask",
    "Express.js",
];

面试官点头示意你继续。你明白,要实现这个功能,关键在于编写一个能逐字符检查匹配的函数。于是你写下了如下代码:

const fuzzySearch=(str, query)=> 
{
    str=str.toLowerCase(); // 将字符串转换为小写,确保不区分大小写
    query=query.toLowerCase(); // 同样转换查询字符串

    let i=0, lastSearched=-1, current=query[i];

    while (current) 
    {
        // 使用 !~ 来判断当前字符是否在目标字符串中按顺序出现
        if (!~(lastSearched=str.indexOf(current, lastSearched + 1))) 
        {
            return false; // 如果没找到,则返回 false
        };
        current=query[++i]; // 查找下一个字符
    }
    return true; // 如果所有字符都找到,则返回 true
};

什么是滑动窗口?

在编写代码的过程中,你停下来向面试官解释道,滑动窗口是一种常见的算法技巧,特别适用于字符串和数组的处理问题。滑动窗口的核心思想是在数据结构内保持一个“窗口”,逐步滑动窗口的位置进行检查或计算。

fuzzySearch 函数中,滑动窗口的概念被用来逐字符地在目标字符串中查找查询字符串中的字符。每次找到一个字符后,搜索的起始位置会向前移动,确保后续字符的匹配不会回到已经匹配过的位置,从而保证字符匹配的顺序性。

代码解释

接下来,你向面试官逐步解释了每一行代码的逻辑:

  • 大小写转换:为了确保搜索时不受大小写影响,你将 str 和 query 都转换为小写。这是为了在比较时忽略大小写的差异。
  • 滑动窗口检查:通过一个循环,你逐个字符检查 query 中的字符是否按顺序出现在 str 中。每次匹配成功后,窗口(即搜索起点)向前滑动,以避免重复匹配之前的字符。
  • 关键操作符 !~:你解释了 !~ 操作符组合的作用。indexOf 返回字符的索引,如果未找到,则返回 -1。~ 操作符将 -1 转为 0,而 ! 操作符将 0 转为 true。这一行代码简洁地判断了字符是否存在于字符串中,并在未找到时直接返回 false。

面试官显然对你的解释感到满意,你继续编写用于过滤整个数组的函数:

const search=function(arr, query)
{
    return arr.filter((e)=> fuzzySearch(e, query)); // 使用 fuzzySearch 过滤数组
};

然后你运行了代码,并向面试官展示了模糊搜索的效果:

console.log(search(arr, 'Java')); // 输出 [ 'JavaScript', 'Java' ]

面试官看了输出结果,点头称赞。他认可了你如何通过这个方法在字符串数组中实现了模糊搜索,并展示了实际效果。

结束

在这个面试场景中,你不仅展示了扎实的JavaScript基础,还通过简洁而高效的代码,解决了一个实际问题。你的表现让面试官印象深刻,这也证明了你在面对挑战时的思维方式和解决问题的能力。

面试不仅是展示你掌握多少知识,更是展示你解决问题的能力和思维方式。愿你在每一场面试中都能从容应对,拿下心仪的Offer!