【问题标题】:String replacement with dictionary, complications with punctuation用字典替换字符串,用标点符号替换
【发布时间】:2012-12-11 05:43:29
【问题描述】:

我正在尝试编写一个函数 process(s,d) 以使用字典将字符串中的缩写替换为它们的完整含义。其中 s 是字符串输入,d 是字典。例如:

>>>d = {'ASAP':'as soon as possible'}
>>>s = "I will do this ASAP.  Regards, X"
>>>process(s,d)
>>>"I will do this as soon as possible.  Regards, X"

我尝试过使用 split 函数来分隔字符串并将每个部分与字典进行比较。

def process(s):
    return ''.join(d[ch] if ch in d else ch for ch in s)

但是,它会返回相同的字符串。我怀疑代码不起作用,因为原始字符串中 ASAP 后面的句号。如果是这样,我该如何忽略标点符号并尽快替换?

【问题讨论】:

    标签: python dictionary replace punctuation


    【解决方案1】:

    这是一种使用单个正则表达式的方法:

    In [24]: d = {'ASAP':'as soon as possible', 'AFAIK': 'as far as I know'}
    
    In [25]: s = 'I will do this ASAP, AFAIK.  Regards, X'
    
    In [26]: re.sub(r'\b' + '|'.join(d.keys()) + r'\b', lambda m: d[m.group(0)], s)
    Out[26]: 'I will do this as soon as possible, as far as I know.  Regards, X'
    

    与基于 str.replace() 的版本不同,这会观察单词边界,因此不会替换碰巧出现在其他单词中间的缩写词(例如,“fetch”中的“etc”)。

    此外,与迄今为止提出的大多数(全部?)其他解决方案不同,它只迭代输入字符串一次,而不管字典中有多少搜索词。

    【讨论】:

    • +1 - 换句话说,缩写很重要(没想到:))
    • 这个解决方案的一个问题是它仍然具有 O( dictLength * stringLength ) 复杂性,因为它在正则表达式中使用了所有字典的键。通过遍历字符串中的单词并检查它们是否存在于字典中,您只需遍历字符串一次,尊重输入边界中的单词,并且具有 O( stringLength ) 复杂性(请参阅下面的答案)
    • @RyanMadsen:这不正确。使用任何半体面的正则表达式引擎,匹配这种形式的正则表达式的成本与OR 术语的数量无关。我推荐en.wikipedia.org/wiki/Formal_grammar#Regular_grammars 和其中的链接。
    • @NPE 你是对的。但是,还有其他成本需要考虑。将正则表达式编译成 DFA 有其自身的复杂性,并且生成的结构(可能模仿 trie)会产生额外的空间需求。
    【解决方案2】:

    你可以这样做:

    def process(s,d):
        for key in d:
            s = s.replace(key,d[key])
        return s
    

    【讨论】:

    • 一个问题 - 如果 'AS' 和 'ASAP' 都是 d 中的键,那么 'AS' 可能首先被评估,但错误地替换了源文本中 'ASAP' 的前导 'AS' .我认为您需要稍微修改此代码以按密钥长度的相反顺序遍历 d,只需将 for key in d 更改为 for key in sorted(d, key=lambda x:len(x), reverse=True): 即可轻松完成。
    【解决方案3】:

    这是一个可行的解决方案:使用re.split(),并按字边界分割(保留间隙字符):

    ''.join( d.get( word, word ) for word in re.split( '(\W+)', s ) )
    

    此代码与 Vaughn 或 Sheena 的答案的一个显着区别是,此代码利用了字典的 O(1) 查找时间,而他们的解决方案查看字典中的每个键。这意味着当s 很短而d 很大时,它们的代码将需要更长的时间才能运行。此外,部分单词在他们的解决方案中仍然会被替换:如果d = { "lol": "laugh out loud" }s="lollipop" 他们的解决方案将错误地产生"laugh out loudlipop"

    【讨论】:

      【解决方案4】:

      使用正则表达式:

      re.sub(pattern,replacement,s)
      

      在您的应用程序中:

      ret = s
      for key in d:
          ret = re.sub(r'\b'+key+r'\b',d[key],ret)
      return ret
      

      \b 匹配单词的开头或结尾。感谢保罗的评论

      【讨论】:

      • 查看我对 Vaughn Cato 的评论,了解仅迭代 d 的弱点。或者,由于您使用的是正则表达式,您可以跳过排序并将代码更改为 ret = re.sub(r'\b'+key+r'\b', d[key],ret),这样您就不会意外替换较大的首字母缩写词中的较小首字母缩写词。
      【解决方案5】:

      不要用空格分割,而是使用:

      split("\W")
      

      它会被任何不是单词一部分的字符分割。

      【讨论】:

        【解决方案6】:
            python 3.2
        
            [s.replace(i,v) for i,v in d.items()]
        

        【讨论】:

          【解决方案7】:

          这也是字符串替换(+1 到@VaughnCato)。这使用reduce 函数来遍历您的字典,用值替换字符串中键的任何实例。 s 在这种情况下是累加器,在每次迭代时都会减少(即馈送到替换函数),保持所有过去的替换(同样,根据上面@PaulMcGuire 的观点,这会替换以最长开始并以最短结束的键)。

          In [1]: d = {'ASAP':'as soon as possible', 'AFAIK': 'as far as I know'}
          
          In [2]: s = 'I will do this ASAP, AFAIK.  Regards, X'
          
          In [3]: reduce(lambda x, y: x.replace(y, d[y]), sorted(d, key=lambda i: len(i), reverse=True), s)
          Out[3]: 'I will do this as soon as possible, as far as I know.  Regards, X'
          

          至于为什么你的函数没有返回你所期望的——当你迭代s时,你实际上是在迭代字符串的字符——而不是单词。您的版本可以通过迭代s.split()(这将是一个单词列表)来调整,但是您会遇到标点符号导致单词与您的字典不匹配的问题。您可以通过导入string 并从每个单词中删除string.punctuation 来使其匹配,但这将从最终字符串中删除标点符号(因此,如果替换不起作用,则正则表达式可能是最佳选择)。

          【讨论】:

          • 你可以和word.strip(string.punctuation)比较,但是用word操作。
          • @BrendenBrown 这就是我一开始在玩弄的东西,但我找不到“知道”什么被剥离的好方法(因为你必须从字典,但需要以某种方式将它与您剥离的标点符号重新加入)。但是我不是专家,所以我肯定会忽略一些东西:)
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-07-15
          • 1970-01-01
          • 2013-09-23
          • 1970-01-01
          • 1970-01-01
          • 2019-08-13
          相关资源
          最近更新 更多