导语:

模糊匹配可以算是现代编辑器(在选择要打开的文件时)的一个必备特性了,它所做的就是根据用户输入的部分内容,猜测用户想要的文件名,并提供一个推荐列表供用户选择。

样例如下:

Vim (Ctrl-P)

Sublime Text (Cmd-P)

‘模糊匹配’这是一个极为有用的特性,同时也非常易于实现。

问题分析:

我们有一堆字符串(文件名)集合,我们根据用户的输入不断进行过滤,用户的输入可能是字符串的一部分。我们就以下面的集合为例:

>>> collection = ['django_migrations.py',

'django_admin_log.py',

'main_generator.py',

'migrations.py',

'api_user.doc',

'user_group.doc',

'accounts.txt',

]

当用户输入’djm‘字符串时,我们假定是匹配到’django_migrations.py’和’django_admin_log.py’,而最简单的实现方法就是使用正则表达式。

解决方案:

1.常规的正则匹配

将’djm’转换成’d.*j.*m’然后用这个正则尝试匹配集合中的每一个字符串,如果匹配到了就被列为候选。

>>> import re

>>> def fuzzyfinder(user_input, collection):

suggestions = []

pattern = '.*'.join(user_input) # Converts 'djm' to 'd.*j.*m'

regex = re.compile(pattern)     # Compiles a regex.

for item in collection:

match = regex.search(item)  # Checks if the current item matches the regex.

if match:

suggestions.append(item)

return suggestions

>>> print fuzzyfinder('djm', collection)

['django_migrations.py', 'django_admin_log.py']

>>> print fuzzyfinder('mig', collection)

['django_migrations.py', 'django_admin_log.py', 'main_generator.py', 'migrations.py']

这里根据用户的输入我们得到了一个推荐列表,但是推荐列表中的字符串是没有进行重要性区分的。有可能出现最合适的匹配项被放到了最后的情况。

实际上,还是这个例子,当用户输入’mig‘时,最佳选项’migrations.py’就被放到了最后。

2.带有rank排序的匹配列表

这里我们对匹配到的结果按照匹配内容第一次出现的起始位置来进行排序。

'main_generator.py'     - 0

'migrations.py'         - 0

'django_migrations.py'  - 7

'django_admin_log.py'   - 9

下面是相关代码:

>>> import re

>>> def fuzzyfinder(user_input, collection):

suggestions = []

pattern = '.*'.join(user_input) # Converts 'djm' to 'd.*j.*m'

regex = re.compile(pattern)     # Compiles a regex.

for item in collection:

match = regex.search(item)  # Checks if the current item matches the regex.

if match:

suggestions.append((match.start(), item))

return [x for _, x in sorted(suggestions)]

>>> print fuzzyfinder('mig', collection)

['main_generator.py', 'migrations.py', 'django_migrations.py', 'django_admin_log.py']

这次我们生成了一个由二元tuple组成的列表,即列表中的每一个元素为一个二元tuple,而该二元tuple的第一个值为匹配到的起始位置、第二个值为对应的文件名,然后使用列表推导式按照匹配到的位置进行排序并返回文件名列表。

现在我们已经很接近最终的结果了,但还称不上完美——用户想要的是’migration.py’,但我们却把’main_generator.py’作为第一推荐。

3.根据匹配的紧凑程度进行排序

当用户开始输入一个字符串时,他们倾向于输入连续的字符以进行精确匹配。比如当用户输入’mig‘他们更倾向于找的是’migrations.py’或’django_migrations.py’,而不是’main_generator.py’,所以这里我们所做的改变就是查找匹配到的最紧凑的项目。

刚才提到的问题对于Python来说不算什么事,因为当我们使用正则表达式进行字符串匹配时,匹配到的字符串就已经被存放在了match.group()中了。下面假设输入为’mig’,对最初定义的’collection’的匹配结果如下:

regex = '(m.*i.*g)'

'main_generator.py'    ->  'main_g'

'migrations.py'        ->  'mig'

'django_migrations.py' ->  'mig'

'django_admin_log.py'  ->  'min_log'

这里我们将推荐列表做成了三元tuple的列表的形式,即推荐列表中的每一个元素为一个三元tuple,而该三元tuple的第一个值为匹配到的内容的长度、第二个值为匹配到的起始位置、第三个值为对应的文件名,然后按照匹配长度和起始位置进行排序并返回。

>>> import re

>>> def fuzzyfinder(user_input, collection):

suggestions = []

pattern = '.*'.join(user_input) # Converts 'djm' to 'd.*j.*m'

regex = re.compile(pattern)     # Compiles a regex.

for item in collection:

match = regex.search(item)  # Checks if the current item matches the regex.

if match:

suggestions.append((len(match.group()), match.start(), item))

return [x for _, _, x in sorted(suggestions)]

>>> print fuzzyfinder('mig', collection)

['migrations.py', 'django_migrations.py', 'main_generator.py', 'django_admin_log.py']

针对我们的输入,这时候的匹配结果已经趋向于完美了,不过还没完。

4.非贪婪匹配

由 Daniel Rocco 发现了这一微妙的问题:当集合中有[‘api_user’, ‘user_group’]这两个元素存在,用户输入’user‘时,预期的匹配结果(相对顺序)应该为[‘user_group’, ‘api_user‘],但实际上的结果为:

>>> print fuzzyfinder('user', collection)

['api_user.doc', 'user_group.doc']

上面的测试结果中:’api_user’要排在’user_group’前面。深入一点,我们发现这是因为在搜索’user’时,正则被扩展成了’u.*s.*e.*r’,考虑到’user_group’有2个’r’,因此该模式匹配到了’user_gr‘而不是我们预期的’user‘。更长的匹配导致在最后的匹配rank排序时名次下降这一违反直觉的结果,不过这问题也容易解决,将正则修改为’非贪婪匹配’即可。

>>> import re

>>> def fuzzyfinder(user_input, collection):

suggestions = []

pattern = '.*?'.join(user_input)    # Converts 'djm' to 'd.*?j.*?m'

regex = re.compile(pattern)         # Compiles a regex.

for item in collection:

match = regex.search(item)      # Checks if the current item matches the regex.

if match:

suggestions.append((len(match.group()), match.start(), item))

return [x for _, _, x in sorted(suggestions)]

>>> fuzzyfinder('user', collection)

['user_group.doc', 'api_user.doc']

>>> print fuzzyfinder('mig', collection)

['migrations.py', 'django_migrations.py', 'main_generator.py', 'django_admin_log.py']

现在,fuzzyfinder已经可以(在上面的情况中)正常工作了,而我们不过只写了10行代码就实现了一个 fuzzy finder。

结论:

以上就是我在我的 pgcli 项目(一个有自动补全功能的Postgresql命令行实现)中设计实现’fuzzy matching’的过程记录。

我已经将 fuzzyfinder 提取成一个独立的Python包,你可以使用命令’pip install fuzzyfinder’在你的项目中进行安装和使用。

感谢 Micah Zoltu 和 Daniel Rocco 对算法的检查和问题修复。

如果你对这个感兴趣的话,你可以来 twitter (https://twitter.com/amjithr)上找我。

结语:

当我第一次考虑用Python实现“fuzzy matching”的时候,我就知道一个叫做 fuzzywuzzy 的优秀库,但是 fuzzywuzzy 的做法和这里的不太一样,它使用的是 “levenshtein distance” 来从集合中找到最匹配的字符串。”levenshtein distance“是一个非常适合用来做自动更正拼写错误的技术,但在从部分子串匹配长文件名时表现的不太好(所以这里没有使用)。

10 行 Python 代码写的模糊查询的更多相关文章

  1. 10 行 Python 代码实现模糊查询/智能提示

    10 行 Python 代码实现模糊查询/智能提示   1.导语: 模糊匹配可以算是现代编辑器(如 Eclipse 等各种 IDE)的一个必备特性了,它所做的就是根据用户输入的部分内容,猜测用户想要的 ...

  2. 10行Python代码计算汽车数量

    当你还是个孩子坐车旅行的时候,你玩过数经过的汽车的数目的游戏吗? 在这篇文章中,我将教你如何使用10行Python代码构建自己的汽车计数程序. 以下是环境及相应的版本库: Python版本 3.6.9 ...

  3. 10行Python代码实现人脸定位

    10行python机器学习全卷机网,实现100+张人脸同时定位! 发表评论 1,049 游览 A+ 所属分类:未分类 收  藏 今天介绍一个快速定位人脸的深度学习算法MTCNN,全称是:Multi-t ...

  4. 10 行 Python 代码,批量压缩图片 500 张,简直太强大了

    本文原创并首发于公众号[Python猫],未经授权,请勿转载. 原文地址:https://mp.weixin.qq.com/s/5hpFDgjCpfb0O1Jg-ycACw 熟悉 "Pyth ...

  5. python 之路,200行Python代码写了个打飞机游戏!

    早就知道pygame模块,就是没怎么深入研究过,恰逢这周未没约到妹子,只能自己在家玩自己啦,一时兴起,花了几个小时写了个打飞机程序. 很有意思,跟大家分享下. 先看一下项目结构 "" ...

  6. 10行Python代码实现目标检测

    要知道图像中的目标是什么? 或者你想数一幅图里有多少个苹果? 在本文中,我将向你展示如何使用Python在不到10行代码中创建自己的目标检测程序. 如果尚未安装python库,你需要安装以下pytho ...

  7. 用 150 行 Python 代码写的量子计算模拟器

    简评:让你更轻松地明白,量子计算机如何遵循线性代数计算的. 这是个 GItHub 项目,可以简单了解一下. qusim.py 是一个多量子位的量子计算机模拟器(玩具?),用 150 行的 python ...

  8. 10行Python代码自动清理电脑内重复文件,解放双手!

    大家好,又到了Python办公自动化系列. 今天分享一个系统层面的自动化案例: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做 ...

  9. 10行Python代码解决约瑟夫环(模拟)

    http://blog.csdn.net/dengyaolongacmblog/article/details/39208675 #!/usr/bin/env python # coding: utf ...

随机推荐

  1. IBatis.Net项目数据库SqlServer迁移至Oracle经验

    最近完成了一个(IBatis.Net+MVC)项目的数据库+代码迁移工作,可把我折腾得~~~ IBatis.Net是一个ORM框架,具体介绍可以问度娘.我之前没用ORM框架使用经验,所以这一路我不是走 ...

  2. JAVA 设计模式 桥接模式

    用途 桥接模式 (Bridge) 将抽象部分与实现部分分离,使它们都可以独立的变化. 桥接模式是一种结构式模式. 结构

  3. BitSet构造函数的两种特例

    C++11之后,bitset的构造函数新加了两种形式: bitset<bits>::bitset (const string& str, string::size_type str ...

  4. Servlet、Filter、Listener、Interceptor

    首先,JSP/Servlet规范中定义了Servlet.Filter.Listener这三种角色,并没有定义Interceptor这个角 色,Interceptor是某些MVC框架中的角色,比如Str ...

  5. JavaWeb工程中web.xml基本配置

    一.理论准备 先说下我记得xml规则,必须有且只有一个根节点,大小写敏感,标签不嵌套,必须配对. web.xml是不是必须的呢?不是的,只要你不用到里面的配置信息就好了,不过在大型web工程下使用该文 ...

  6. [转载]Linux 线程实现机制分析

    本文转自http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 支持原创.尊重原创,分享知识! 自从多线程编程的概念出现在 Linux ...

  7. 业务监控-指标监控(v1)

    最近做了指标监控系统的后台,包括需求调研.代码coding.调试调优测试等,穿插其他杂事等前后花了一个月左右. 指标监控指的是用户通过接口上传某些指标信息,并且通过配置阈值公式和告警规则等信息监测自己 ...

  8. C++中的也能使用正则表达式

    正则表达式Regex(regular expression)是一种强大的描述字符序列的工具.在许多语言中都存在着正则表达式,C++11中也将正则表达式纳入了新标准的一部分,不仅如此,它还支持了6种不同 ...

  9. spring-test测试demo

    如果是maven项目,pom中增加如下依赖: <dependency>            <groupId>org.springframework</groupId& ...

  10. HDU 4622 Reincarnation (查询一段字符串的不同子串个数,后缀自动机)

    Reincarnation Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)To ...