在python中比较字符串的最快方法

Posted

技术标签:

【中文标题】在python中比较字符串的最快方法【英文标题】:fastest way to compare strings in python 【发布时间】:2011-01-04 11:11:27 【问题描述】:

我正在用 Python 编写一个脚本,该脚本将允许用户输入一个字符串,该字符串将是一个指示脚本执行特定操作的命令。为了争论,我会说我的命令列表是:

lock
read
write
request
log

现在,我希望用户能够输入“日志”这个词,它会执行一个特定的操作,这很简单。但是,我想匹配部分单词。因此,例如,如果用户输入“lo”,它应该匹配“lock”,因为它在列表中较高。我已经尝试使用 libc 中的 strncmp 使用 ctypes 来完成此操作,但还没有做出正面或反面。

【问题讨论】:

速度到底有多重要?假设这是在用户输入命令时运行一次,并运行一小组命令(少于 1000 个),即使是最低效(实用)的实现也会在一毫秒内返回——这对于用户。 这是一个在 Twisted 框架上运行的网络应用程序,最多可能有 50 个用户同时输入命令,因此如果所有 50 个用户都输入命令并且我正在输入命令,则可能会有延迟解析它们的效率低下。 twisted 是线程化的。你仍然不会注意到任何影响。大多数计算机可以在您的手指按下一个键的时间内比较 10,000 个或更多的字符串。这称为过早优化,您是在把时间浪费在琐碎的事情上。 简单构建,然后测量性能问题所在。似乎不太可能是命令解析。 @SpliFF 我同意消息的精神,但是,Twisted 没有线程。至少,除非你这样做(例如 deferToThread)。 【参考方案1】:

如果您接受来自用户的输入,那么您为什么担心比较的速度?即使是最慢的技术也会比用户感知的快得多。尽可能使用最简单、最易理解的代码,并将效率问题留给紧密的内部循环。

cmds = [
    "lock",
    "read",
    "write",
    "request",
    "log",
    ]

def match_cmd(s):
    matched = [c for c in cmds if c.startswith(s)]
    if matched:
        return matched[0]

【讨论】:

希望我能投票 10 倍。这就是我如此热爱 UI 开发的原因——时间尺度绝对(嗯,相对)巨大。您可以悠闲地花 100 毫秒做某事而没有人注意到。【参考方案2】:

这会做你想做的事:

def select_command(commands, user_input):
    user_input = user_input.strip().lower()
    for command in commands:
        if command.startswith(user_input):
            return command
    return None

但是:

你似乎对错误的事情过度担心了。所以 50 个用户意味着 50 毫秒——你不会因为那种“滞后”而跑出城外。担心数据库访问效率低下或用户在认为会收到“请求”时键入“r”并获得“读取”而导致的问题。在 1960 年代将用户击键风险降至最低,这并不好笑。他们在用什么? ASR33 电传打字机?至少你可以坚持一个独特的匹配——“rea”表示读取,“req”表示请求。

【讨论】:

【参考方案3】:

这已按照您的要求在运行时进行了优化...(尽管很可能不需要)

这里有一段简单的代码,它将获取映射到函数的命令输入字典,并生成映射到同一函数的所有非重复子命令的输出字典。

因此,您在启动服务时运行它,然后您将获得 100% 优化的查找。我相信有更聪明的方法可以做到这一点,所以请随意编辑。

commands = 
  'log': log_function,
  'exit': exit_function,
  'foo': foo_function,
  'line': line_function,
  

cmap = 
kill = set()
for command in commands:
  for pos in range(len(1,command)):
    subcommand = command[0:pos]
    if subcommand in cmap:
      kill.add(subcommand)
      del(cmap[subcommand])
    if subcommand not in kill:
      cmap[subcommand] = commands[command]

#cmap now is the following - notice the duplicate prefixes removed?

  'lo': log_function,
  'log': log_function,
  'e': exit_function,
  'ex': exit_function,
  'exi': exit_function,
  'exit': exit_function,
  'f' : foo_function,
  'fo' : foo_function,
  'foo' : foo_function,
  'li' : line_function,
  'lin' : line_function,
  'line' : line_function,

【讨论】:

【参考方案4】:

你可以使用startswith

例如

myword = "lock"
if myword.startswith("lo"):
   print "ok"

或者如果您想在单词中找到“lo”,无论位置如何,只需使用“in”运算符

if "lo" in myword

因此,有一种方法可以做到这一点:

for cmd in ["lock","read","write","request","log"]:
    if cmd.startswith(userinput):
        print cmd
        break

【讨论】:

@ghostdog74,最好阅读更多详细信息:“我想匹配部分单词。因此,例如,如果用户输入“lo”,它应该匹配“lock”,因为它在列表。” 彼得汉森是正确的。需要匹配部分单词以使系统更易于使用。我将(最终)有一些复杂的命令,并且能够将它们缩写为单个字母非常方便。【参考方案5】:

我建议你看看使用 readline python 库,而不是重新发明***。 用户必须点击 tab 才能完成单词,但您可以设置 readline 以便 tab 尽可能匹配或循环遍历所有以当前存根开头的单词。

这似乎是对 python http://www.doughellmann.com/PyMOTW/readline/index.html 中 readline 的一个相当不错的介绍

【讨论】:

【参考方案6】:

python-Levenshtein 中的jaro_winkler() 可能就是您要查找的内容。

【讨论】:

【参考方案7】:

这是改编自J.Tauber's Trie implementation in Python,您可以比较和/或重新调整您需要的任何额外功能。另请参阅Wikipedia entry on tries。

class Trie:
    def __init__(self):
        self.root = [None, ]

    def add(self, key):
        curr_node = self.root
        for ch in key:
            curr_node = curr_node[1].setdefault(ch, [key, ])
        curr_node[0] = key

    def find(self, key):
        curr_node = self.root
        for ch in key:
            try:
                curr_node = curr_node[1][ch]
            except KeyError:
                return None
        return curr_node[0]

设置(添加顺序很重要!):

t = Trie()
for word in [
   'lock',
   'read',
   'write',
   'request',
   'log']:
   t.add(word)

然后这样调用:

>>> t.find('lo')
'lock'
>>> t.find('log')
'log'
>>> t.find('req')
'request'
>>> t.find('requiem')
>>>

【讨论】:

不开玩笑,但另一个“使用startswith() 遍历列表”似乎真的无聊。 ;-) 至少我的 Stratswith 努力解决了 OP 的(毫无意义的)效率问题,通过在第一场比赛中退出 :-) @John,啊,所以你认为如果“lo”也出现在列表中,但在“lock”之后,输入“lo”将返回“lock”作为匹配项是可以接受的吗?我没想到。 @Peter Hansen:本月 +1 sequitur。你没有理由认为我认为这是可以接受的。这显然是不能接受的。然而,这是OP所说的他想要的自然结果。他显然知道列表中的顺序很重要。人们需要相信(a)他不会在“lock”或“log”之后放置“lo”(b)如果失败,他会测试他的代码并发现用户无法访问“lo”功能。 @John,当我的解决方案没有遇到问题时,这几乎不是“自然结果”。我还会注意到,“在第一场比赛中保释”虽然整体解决方案仍然较慢,因为它遍历一长串命令,这并不是解决性能问题的好方法。 (当然,在我们通过讨论完成任何事情之前,我们需要更多关于性能需求和实际列表大小的背景知识。)【参考方案8】:

如果我正确理解您的 Q,您需要一个 sn-p,它会在得到答案后立即返回,而无需进一步遍历您的“命令列表”。这应该做你想做的:

from itertools import ifilter

def check_input(some_string, code_book) :
    for q in ifilter(code_book.__contains__, some_string) :
        return True
    return False

【讨论】:

【参考方案9】:

替换为您最喜欢的字符串比较功能。相当快,而且切中要害。

matches = ( x for x in list if x[:len(stringToSearchFor)] == stringToSearchFor )
print matches[0]

【讨论】:

(1) 见http://docs.python.org/library/stdtypes.html#str.startswith (2) 不要使用list;它会影响内置的list()【参考方案10】:
import timeit

cmds = []
for i in range(1,10000):
    cmds.append("test")

def get_cmds(user_input):
    return [c for c in cmds if c.startswith(user_input)]

if __name__=='__main__':
    t = timeit.Timer("get_cmds('te')", "from __main__ import get_cmds")
    print "%0.3f seconds" % (t.timeit(number=1))

#>>> 0.008 seconds

所以基本上,根据我的评论,您问的是如何优化不需要可测量时间或 CPU 的操作。我在这里使用了 10,000 个命令,测试字符串匹配每个命令,只是为了表明即使在极端情况下,您仍然可以有数百名用户执行此操作,而且他们永远不会看到任何延迟。

【讨论】:

以上是关于在python中比较字符串的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中增加日期字符串 YYYY-MM-DD 的最快方法是啥?

比较多边形的最快方法

在 Python 中从字节字符串中删除前 20 个字节的最快方法是啥?

python 比较python中各种字符串连接方法的性能

在 F# 中比较两个字节数组的最快方法

比较两个通用列表差异的最快方法