【问题标题】:fastest way to compare strings in python在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)。

标签: python regex parsing


【解决方案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 个命令,测试字符串匹配每个命令,只是为了表明即使在极端情况下,您仍然可以有数百名用户执行此操作,而且他们永远不会看到任何延迟。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-03-14
              • 2019-03-06
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-11-30
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多