假设我需要从序列seq
的末尾到给定项x
(包括)的第一次出现的切片。编写seq[-1:seq.index(x)-1:-1]
的天真尝试会产生一个微妙的错误:
seq = 'abc'
seq[-1:seq.index('b')-1:-1] # 'cb' as expected
seq[-1:seq.index('a')-1:-1] # '' because -1 is interpreted as end of seq
有没有惯用的方式来写这个?
seq[seq.index(x):][::-1]
工作正常,但对于大型序列来说它可能效率低,因为它会创建一个额外的副本。 (我确实需要一个序列,所以需要一个副本;我只是不想创建第二个副本。)
另一方面,这是一个非常容易引入的错误,它可以通过许多测试,并且对于任何静态分析器都无法检测到(除非它警告每个切片都有一个负步骤)。
更新
似乎没有完美/惯用的解决方案。我同意它可能不像我想象的那样经常出现瓶颈,因此在大多数情况下我会使用[pos:][::-1]
。当性能很重要时,我会使用正常的if
检查。但是,我会接受我觉得有趣的解决方案,即使它很难阅读;它可能在某些极少数情况下可用(我真的需要将整个事物放入表达式中,我不想定义新函数)。
另外,我试过计时。对于列表,即使它们短至2个项目,额外切片也总是会有2倍的惩罚。对于字符串,结果非常不一致,我不能说什么:
import timeit
for n in (2, 5, 10, 100, 1000, 10000, 100000, 1000000):
c = list(range(n))
# c = 'x' * n
pos = n // 2 # pretend the item was found in the middle
exprs = 'c[pos:][::-1]', 'c[:pos:-1] if pos else c[::-1]'
results = [timeit.Timer(expr, globals=globals()).autorange() for expr in exprs]
times = [t/loops for loops, t in results]
print(n, times[0]/times[1])
列表的结果(额外切片的比率/没有额外切片时间):
2 2.667782437753884
5 2.2672817613246914
10 1.4275235266754878
100 1.6167102119737584
1000 1.7309116253903338
10000 3.606259720606781
100000 2.636049703318956
1000000 1.9915776615090277
当然,这忽略了这样一个事实:无论我们对结果切片做什么,相对而言,当切片很短时,成本要高得多。所以,我同意对于小尺寸的序列,[::-1]
通常都很好。
答案 0 :(得分:3)
如果迭代器结果没问题,请使用转发片并在其上调用reversed
:
reversed(seq[seq.index(whatever):])
如果不是,则从端点减去额外的len(seq)
:
seq[:seq.index(whatever)-len(seq)-1:-1]
或者只是采取前向切片,再次将其切片以反转它,并吃掉额外副本的成本。这可能不是你的瓶颈。
无论你做什么,都要留下评论解释它,这样人们就不会在编辑时重新引入错误,并为此案例编写单元测试。
答案 1 :(得分:3)
恕我直言,seq[seq.index(x):][::-1]
是最具可读性的解决方案,但这是一种效率更高的方式。
def sliceback(seq, key):
pos = seq.index(key)
return seq[:pos-1 if pos else None:-1]
seq = 'abc'
for k in seq:
print(k, sliceback(seq, k))
<强>输出强>
a cba
b cb
c c
正如Budo Zindovic在评论中提到的,如果在字符串中找不到字符,.index
将引发异常。根据上下文,可能不会使用不在seq
中的char调用代码,但如果可能,我们需要处理它。最简单的方法是捕获异常:
def sliceback(seq, key):
try:
pos = seq.index(key)
except ValueError:
return ''
return seq[:pos-1 if pos else None:-1]
seq = 'abc'
for k in 'abcd':
print(k, sliceback(seq, k))
<强>输出强>
a cba
b cb
c c
d
Python异常处理非常有效。当实际上没有引发异常时,它比基于if
的等效代码更快,但如果异常提升超过5-10%的时间,则使用if
会更快。
在调用key
之前,不是测试seq.index
,而是使用find
更有效。当然,只有当seq
是一个字符串时才会有效;如果seq
是一个列表,它将无效,因为(令人讨厌的)列表没有.find
方法。
def sliceback(seq, key):
pos = seq.find(key)
return '' if pos < 0 else seq[:pos-1 if pos else None:-1]
答案 2 :(得分:0)
您可以在分配字符串时检查pos
,例如:
result = seq[-1:pos-1:-1] if pos > 0 else seq[::-1]
输入:
pos = seq.index('a')
输出:
cba