如何从列表末尾删除所有的 None 实例?

3

Python中有一个字符串方法叫做 rstrip()

>>> s = "hello world!!!"
>>> s.rstrip("!")
'hello world'

我希望实现类似的功能,应用于Python列表。也就是说,我想从列表末尾删除给定值的所有实例。在这种情况下,该值是None
以下是一些起始示例:
[1, 2, 3, None]
[1, 2, 3, None, None, None]
[1, 2, 3, None, 4, 5]
[1, 2, 3, None, None, 4, 5, None, None]

我希望最终结果如下:
[1, 2, 3]
[1, 2, 3]
[1, 2, 3, None, 4, 5]
[1, 2, 3, None, None, 4, 5]

以下是我的解决方案:

while l[-1] is None:
    l.pop()

2
那似乎是一个足够合理的解决方案... - deceze
6
如果列表为空或仅包含 None,则您的解决方案将抛出 IndexError,因此请为此添加一个特殊情况处理。 - Alexandru Dinu
你的解决方案看起来非常不错。从列表末尾弹出是O(1),所以我认为你不能做得更好,除非有一个专门用于此目的的内置函数(据我所知并没有)。 - MB-F
为了解决@AlexandruDinu的问题,请更改while条件以在尝试访问l[-1]之前检查空列表。 - Barmar
2个回答

1
如果您想就地修改列表,则您的解决方案很好,只需确保处理列表为空的情况:
while l and l[-1] is None:
    l.pop()

如果您想计算一个新的列表,您可以将您的解决方案改进为:
def stripNone(l):
    if not l:
        return []
    
    rlim = 0
    for x in reversed(l):
        if x is None:
            rlim += 1
        else:
            break
    
    return l[: len(l) - rlim]

还有itertools.dropwhile,但是你需要执行两次反转操作:

def stripNone(l):
    return list(dropwhile(lambda x: x is None, l[::-1]))[::-1]

0

另外还有两个版本适用于仅包含None的列表:

while None in l[-1:]:
    l.pop()

for x in reversed(l):
    if x is not None:
        break
    l.pop()

l = [None] * 10**3 进行一些解决方案的基准测试:

 83 us  stripNone1
137 us  stripNone2
 60 us  stripNone3
 42 us  stripNone3b
 53 us  stripNone4
 34 us  stripNone5
 19 us  stripNone6

请注意,stripNone2和stripNone6存在一个小缺陷:如果在None的尾随中有一个对象,它不是None但声称等于None,则会被删除。虽然这样的对象非常不寻常,但也许人们确实希望删除这样的对象。
基准代码:
def stripNone1(l):
    while l and l[-1] is None:
        l.pop()

def stripNone2(l):
    while None in l[-1:]:
        l.pop()

def stripNone3(l):
    for x in reversed(l):
        if x is not None:
            break
        l.pop()

def stripNone3b(l):
    pop = l.pop
    for x in reversed(l):
        if x is not None:
            break
        pop()

def stripNone4(l):
    for i, x in enumerate(reversed(l), 1):
        if x is not None:
            del l[-i:]
            break

def stripNone5(l):
    pop = l.pop
    try:
        while (last := pop()) is None:
            pass
        l.append(last)
    except IndexError:
        pass

def stripNone6(l):
    while l:
        chunk = l[-32:]
        if chunk.count(None) < len(chunk):
            while l[-1] is None:
                l.pop()
            break
        del l[-32:]

from timeit import repeat
solutions = stripNone1, stripNone2, stripNone3, stripNone3b, stripNone4, stripNone5, stripNone6
for i in range(3):
    print(f'Round {i+1}:')
    for sol in solutions:
        ls = [[42] * head + [None] * tail
              for _ in range(5)
              for head in range(0, 2001, 200)
              for tail in range(0, 2001, 200)]
        number = len(ls) // 5
        ls = iter(ls)
        time = min(repeat(lambda: sol(next(ls)), number=number)) / number
        print(f'{int(time * 10**6):3d} us  {sol.__name__}')

@martineau 不确定为什么您将单元素列表的搜索称为“线性”。 - Pychopath
@martineau谁会把O(1)称为“线性的”呢? - Pychopath
@martineau 添加了一个基准测试。我猜你现在应该向Alexandru抱怨他们的解决方案有点慢了? - Pychopath
@martineau 我缩短了测试列表,几乎没有影响相对速度。你说“很多次”当你声称我的第一个解决方案是“非常慢”的时候,因此我使用了一个测试用例,其中那个“很多次”实际上发生了。由于所有的解决方案都只通过许多尾随的None并查看最多一个非None,所以没有包括非None值,因为它们并不重要。顺便说一句,我添加了一个新的最快的解决方案,比Alexandru的快两倍以上。 - Pychopath
@martineau,“equal numbers of every kind of possibility”是什么意思?实际上有无限多的可能性。不可能做到所有的可能性都尝试。而且,听起来好像会得到与我已经展示的相同的相对速度。但随便你证明我错了,写些代码来展示它。到目前为止,看起来你只是在这里抱怨、进行虚假指控和模糊得没有用的建议,而我正在做所有的工作……只是为了让你回来声称我还是做得不对。我感觉我被人玩弄了。 - Pychopath
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接