10 行 Python 代码写的模糊查询
导语:
模糊匹配可以算是现代编辑器(在选择要打开的文件时)的一个必备特性了,它所做的就是根据用户输入的部分内容,猜测用户想要的文件名,并提供一个推荐列表供用户选择。
样例如下:
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 代码写的模糊查询的更多相关文章
- 10 行 Python 代码实现模糊查询/智能提示
10 行 Python 代码实现模糊查询/智能提示 1.导语: 模糊匹配可以算是现代编辑器(如 Eclipse 等各种 IDE)的一个必备特性了,它所做的就是根据用户输入的部分内容,猜测用户想要的 ...
- 10行Python代码计算汽车数量
当你还是个孩子坐车旅行的时候,你玩过数经过的汽车的数目的游戏吗? 在这篇文章中,我将教你如何使用10行Python代码构建自己的汽车计数程序. 以下是环境及相应的版本库: Python版本 3.6.9 ...
- 10行Python代码实现人脸定位
10行python机器学习全卷机网,实现100+张人脸同时定位! 发表评论 1,049 游览 A+ 所属分类:未分类 收 藏 今天介绍一个快速定位人脸的深度学习算法MTCNN,全称是:Multi-t ...
- 10 行 Python 代码,批量压缩图片 500 张,简直太强大了
本文原创并首发于公众号[Python猫],未经授权,请勿转载. 原文地址:https://mp.weixin.qq.com/s/5hpFDgjCpfb0O1Jg-ycACw 熟悉 "Pyth ...
- python 之路,200行Python代码写了个打飞机游戏!
早就知道pygame模块,就是没怎么深入研究过,恰逢这周未没约到妹子,只能自己在家玩自己啦,一时兴起,花了几个小时写了个打飞机程序. 很有意思,跟大家分享下. 先看一下项目结构 "" ...
- 10行Python代码实现目标检测
要知道图像中的目标是什么? 或者你想数一幅图里有多少个苹果? 在本文中,我将向你展示如何使用Python在不到10行代码中创建自己的目标检测程序. 如果尚未安装python库,你需要安装以下pytho ...
- 用 150 行 Python 代码写的量子计算模拟器
简评:让你更轻松地明白,量子计算机如何遵循线性代数计算的. 这是个 GItHub 项目,可以简单了解一下. qusim.py 是一个多量子位的量子计算机模拟器(玩具?),用 150 行的 python ...
- 10行Python代码自动清理电脑内重复文件,解放双手!
大家好,又到了Python办公自动化系列. 今天分享一个系统层面的自动化案例: 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做 ...
- 10行Python代码解决约瑟夫环(模拟)
http://blog.csdn.net/dengyaolongacmblog/article/details/39208675 #!/usr/bin/env python # coding: utf ...
随机推荐
- [DOM Event Learning] Section 2 概念梳理 什么是事件 DOM Event
[DOM Event Learning] Section 2 概念梳理 什么是事件 DOM Event 事件 事件(Event)是用来通知代码,一些有趣的事情发生了. 每一个Event都会被一个E ...
- java设计模式--策略模式
策略模式属于对象的行为模式.其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换.策略模式使得算法可以在不影响到客户端的情况下发生变化. 本文地址:http:// ...
- IOS开发基础知识--碎片1
一:NSString与NSInteger的互换 NSInteger转化NSString类型:[NSString stringWithFormat: @"%d", NSInteger ...
- iOS---设置输入框的光标位置
//这里设置光标位置,让光标位置后移10 textField.leftView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 10, 0)]; tex ...
- [windows]win10家庭版切换到管理员账户
背景:很多时候,在安装或者运行某些程序时会需要到管理员账户运行.而在win10家庭版却没有明显的位置可以让用户简单的进行切换.因此,有了以下的方法. 方法: 1.在搜索框中输入CMD,右键以管理员方式 ...
- 深入理解Linux修改hostname
当我觉得对Linux系统下修改hostname已经非常熟悉的时候,今天碰到了几个个问题,这几个问题给我好好上了一课,很多知识点,当你觉得你已经掌握的时候,其实你了解的还只是皮毛.技术活,切勿浅尝则止! ...
- form表单取消按钮自动提交
默认写在form表单里的按钮可以自动提交表单,现在要实现的效果是点击button按钮调用js函数,再有ajax提交 <button type="button" class=& ...
- Ubuntu 网络参数设置
修改网络设置 sudo vim /etc/network/interfaces 修改计算机名 sudo vim /etc/hostname sudo vim /etc/hosts
- 从人类社会的角度看OO(独家视角)
引言 在OO的工作中,我们一定会涉及到类,抽象类和接口.那么类和抽象类以及接口到底扮演的什么角色? 本文主要是从人类社会的角度阐述类与抽象类以及接口的"社会"关系,从而让我们抛弃书 ...
- JS 阶段小练习~ 无缝滚动
结合下学了的知识,做个模拟的综合性扩展练习~~ 大致功能如下: 1.点开html后,图片自动移动展示 2.点击左右方向,可以改变 图片移动的方向(改变left的值,正负) 3.鼠标移入移出图片后,图 ...