【问题标题】:Remove last occurrence of element containing substring from a list从列表中删除最后一次出现的包含子字符串的元素
【发布时间】:2019-09-26 15:21:13
【问题描述】:

所以说我有,list1 = ['the dog', 'the cat', 'cat dog', 'the dog ran home']

and sub_string = '狗'

如何返回 list2 = ['the dog', 'the cat', 'cat dog']

即,返回一个删除了最后一次出现的子字符串的列表?

【问题讨论】:

    标签: python python-2.7 string-search


    【解决方案1】:

    这里没有内置函数对您有很大帮助,因为扫描list 中的子字符串不是受支持的功能,并且以相反的顺序执行此操作会加倍困难。列表推导也不会起到太大作用,因为让它们有足够的状态来识别您何时发现您的针将涉及向列表推导添加副作用,这使得它变得神秘并违反了函数式编程工具的目的。所以你被困在自己的循环中:

    list2 = []
    list1iter = reversed(list1)  # Make a reverse iterator over list1
    for item in list1iter:
        if sub_string in item:   # Found item to remove, don't append it, we're done
            break
        list2.append(item)       # Haven't found it yet, keep item
    list2.extend(list1iter)      # Pull all items after removed item
    list2.reverse()              # Put result back in forward order
    

    Try it online!

    另一种方法是按索引扫描,允许您del 它;如果您想就地修改list1,而不是创建一个新的list,这可能是一个更好的解决方案:

    for i, item in enumerate(reversed(list1), 1):
        if sub_string in item:
            del list1[-i]
            break
    

    Try it online!

    该解决方案适用于制作新副本,只需将所有对list1 的引用更改为list2,并在循环前添加list2 = list1[:]

    在这两种情况下,您都可以通过在for 上放置else: 块来检测是否找到了某个项目;如果else 块触发,您没有break,因为在任何地方都找不到sub_string

    【讨论】:

      【解决方案2】:

      问题陈述是:删除带有子字符串的元素作为查询

      所以,我推断它有两个步骤。

      1. 找到带有子字符串的元素。
      2. 移除元素。

      对于模式匹配,我们可以使用re 模块(我们可以使用in 以及ShadowRanger 的答案中提到的)

      import re
      
      pattern = re.compile('the dog') # target pattern 
      my_list = ['the dog', 'the cat', 'cat dog', 'the dog ran home'] # our list
      my_list = enumerate(my_list) # to get indexes corresponding to elemnts i.e. [(0, 'the dog'), (1, 'the cat'), (2, 'cat dog'), (3, 'the dog ran home')]
      elems = list(filter(lambda x: pattern.search(x[1]), my_list) # match the elements in the second place and filter them out, remember filter in python 3.x returns an iterator
      print(elems) # [(0, 'the dog'), (3, 'the dog ran home')]
      del my_list[elems[-1][0]] # get the last element and take the index of it and delete it.
      

      编辑

      正如 ShadowRunner 所建议的,我们可以使用带有 if 语句而不是 filter 函数的列表推导来优化代码。

      elems = [i for i, x in enumerate(my_list) if pattern.search(x)]
      

      【讨论】:

      • 不知道你为什么要在这里打扰re; OP 的用例不需要模式匹配,只需要子字符串检查。另请注意,通过使用match,此代码不符合 OP 规定的要求; match 包含一个隐式的字符串开头锚点,因此这只会在元素 begins 与子字符串时匹配,而不是在它 包含 子字符串时匹配。您需要 search 进行非锚定收容检查。
      • @ShadowRanger 感谢您指出这一点,我编辑了我的答案,是的,我相信这有点矫枉过正,我的错,这就是为什么我赞成你的答案,但我相信我应该离开它在这里,如果用户可以从我的回答中拿走任何东西,那就太好了。
      • 是的,这还不足以保证投反对票或其他任何事情。我建议用elems = [i for i, x in enumerate(my_list) if pattern.search(x)](或不带reif 'the dog' in x)替换filter 行及其前面的行,因为filter 在您需要lambda 来使用时基本上总是更丑/更慢它。当现有函数完全执行您想要的操作时很好(如果所述函数是用 C 实现的内置函数,它通常更快),但如果不存在,则等效的 listcomp 或genexpr 没有在大多数情况下,lambda 总是更快更清晰。
      • 是的,我知道像 filter 这样的方法在与 lambda 一起使用时比较慢,而不是列表理解。我将根据您的建议编辑我的答案。 :)
      【解决方案3】:

      您可以分两步完成:

      1. 查找最后一次出现的索引。
      2. 返回所有与该索引不匹配的元素。

      例子:

      needle = 'the dog'
      haystack = ['the dog', 'the cat', 'cat dog', 'the dog ran home']
      
      last = max(loc for loc, val in enumerate(haystack) if needle in val)
      result = [e for i, e in enumerate(haystack) if i != last]
      
      print(result)
      

      输出

      ['the dog', 'the cat', 'cat dog']
      

      有关查找最后一次出现的索引的更多详细信息,请参阅this

      【讨论】:

      • 注意:您可以通过将 last 计算替换为 last = len(haystack) - next(loc for loc, val in enumerate(reversed(haystack), 1) if needle in val) 之类的东西来避免遍历整个输入。通过以相反的顺序运行,并在生成器表达式上使用next,您将短路,并且只需要检查值直到找到匹配项,而不是检查每个值。我避免将my answer 的第二部分压缩到这个程度,因为它变得有点密集/神奇,但基本逻辑相同。
      • 显然我不是第一个想到这一点的人; a comment 在您链接的问题中的一个答案表明同样的事情。
      【解决方案4】:
      list1 = ['the dog', 'the cat','the dog me', 'cat dog']
      sub_string = 'the dog'
      
      for i in list1[::-1]:
          print(i)
          if sub_string in i:
              list1.remove(i)
              break
      

      输出 ['狗','猫','狗我','猫狗']

      【讨论】:

        【解决方案5】:

        一种解决方案是逆序遍历输入,在逆序列表中找到索引。之后,使用索引对输入list1进行切片。

        idx = next(i for i, s in enumerate(reversed(list1), 1) if sub_string in s)
        list2 = list1[:-idx]  # If in-place updates are intended, use `del list1[-idx:]` instead
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-04-14
          • 2012-06-05
          • 1970-01-01
          • 2016-05-27
          • 1970-01-01
          • 2011-12-19
          • 1970-01-01
          • 2020-04-05
          相关资源
          最近更新 更多