繁体   English   中英

在 python 中处理 list.index(might-not-exist) 的最佳方法?

[英]Best way to handle list.index(might-not-exist) in python?

我有看起来像这样的代码:

thing_index = thing_list.index(thing)
otherfunction(thing_list, thing_index)

好的,这很简单,但你明白了。 现在thing可能实际上不在列表中,在这种情况下,我想将 -1 作为thing_index传递。 在其他语言中,这是您希望index()在找不到元素时返回的内容。 事实上,它会抛出一个ValueError

我可以这样做:

try:
    thing_index = thing_list.index(thing)
except ValueError:
    thing_index = -1
otherfunction(thing_list, thing_index)

但这感觉很脏,而且我不知道是否会因其他原因引发ValueError 我根据生成器函数提出了以下解决方案,但似乎有点复杂:

thing_index = ( [(i for i in xrange(len(thing_list)) if thing_list[i]==thing)] or [-1] )[0]

有没有更干净的方法来实现同样的目标? 让我们假设列表没有排序。

使用 try-except 子句没有任何“肮脏”之处。 这是pythonic的方式。 ValueError只会由.index方法引发,因为它是您在那里唯一的代码!

要回答评论:
在 Python 中,请求宽恕比获得许可更容易的哲学已经确立,并且没有index不会为任何其他问题引发此类错误。 不是我能想到的。

thing_index = thing_list.index(elem) if elem in thing_list else -1

一条线。 简单的。 没有例外。

dict类型有一个get函数,如果字典中不存在键,则get的第二个参数是它应该返回的值。 同样有setdefault ,如果键存在,它返回dict的值,否则它根据您的默认参数设置值,然后返回您的默认参数。

您可以扩展list类型以具有getindexdefault方法。

class SuperDuperList(list):
    def getindexdefault(self, elem, default):
        try:
            thing_index = self.index(elem)
            return thing_index
        except ValueError:
            return default

然后可以像这样使用:

mylist = SuperDuperList([0,1,2])
index = mylist.getindexdefault( 'asdf', -1 )

如果您经常这样做,那么最好将其存放在辅助函数中:

def index_of(val, in_list):
    try:
        return in_list.index(val)
    except ValueError:
        return -1 

您使用ValueError的代码没有任何问题。 如果您想避免例外,这里还有一个单行:

thing_index = next((i for i, x in enumerate(thing_list) if x == thing), -1)

😃这个怎么办:

li = [1,2,3,4,5] # create list 

li = dict(zip(li,range(len(li)))) # convert List To Dict 
print( li ) # {1: 0, 2: 1, 3: 2, 4:3 , 5: 4}
li.get(20) # None 
li.get(1)  # 0 

这个问题是语言哲学问题之一。 例如,在 Java 中一直有一个传统,即异常应该只在“异常情况”中使用,即发生错误时,而不是用于流量控制 一开始这是出于性能原因,因为 Java 异常很慢,但现在这已成为公认的风格。

相比之下,Python 一直使用异常来表示正常的程序流程,比如我们在这里讨论的引发ValueError 在 Python 风格中,这并没有什么“肮脏”的地方,而且还有更多的地方。 一个更常见的例子是StopIteration异常,它由迭代器的next()方法引发,以表示没有其他值。

那这个呢:

otherfunction(thing_collection, thing)

与其在函数接口中公开诸如列表索引之类的依赖于实现的东西,不如传递集合和事物并让其他函数处理“成员资格测试”问题。 如果 otherfunction 被编写为与集合类型无关,那么它可能会以:

if thing in thing_collection:
    ... proceed with operation on thing

如果 thing_collection 是列表、元组、集合或字典,这将起作用。

这可能比:

if thing_index != MAGIC_VALUE_INDICATING_NOT_A_MEMBER:

这是您在其他功能中已有的代码。

像这样怎么样:

temp_inx = (L + [x]).index(x) 
inx = temp_inx if temp_inx < len(L) else -1

已经有一段时间了,但它是 stdlib 的核心部分,并且有许多潜在的方法,所以我认为为不同的建议提供一些基准是很有用的,并包括迄今为止最快的 numpy 方法。

import random
from timeit import timeit
import numpy as np

l = [random.random() for i in range(10**4)]
l[10**4 - 100] = 5

# method 1
def fun1(l:list, x:int, e = -1) -> int:
    return [[i for i,elem in enumerate(l) if elem == x] or [e]][0]

# method 2
def fun2(l:list, x:int, e = -1) -> int:
    for i,elem in enumerate(l):
        if elem == x:
            return i
    else:
        return e

# method 3
def fun3(l:list, x:int, e = -1) -> int:
    try:
        idx = l.index(x)
    except ValueError:
        idx = e
    return idx

# method 4
def fun4(l:list, x:int, e = -1) -> int:
    return l.index(x) if x in l else e

l2 = np.array(l)
# method 5
def fun5(l:list or np.ndarray, x:int, e = -1) -> int:
    res = np.where(np.equal(l, x))
    if res[0].any():
        return res[0][0]
    else:        
        return e


if __name__ == "__main__":
    print("Method 1:")
    print(timeit(stmt = "fun1(l, 5)", number = 1000, globals = globals()))
    print("")
    print("Method 2:")
    print(timeit(stmt = "fun2(l, 5)", number = 1000, globals = globals()))
    print("")
    print("Method 3:")
    print(timeit(stmt = "fun3(l, 5)", number = 1000, globals = globals()))
    print("")
    print("Method 4:")
    print(timeit(stmt = "fun4(l, 5)", number = 1000, globals = globals()))
    print("")
    print("Method 5, numpy given list:")
    print(timeit(stmt = "fun5(l, 5)", number = 1000, globals = globals()))
    print("")
    print("Method 6, numpy given np.ndarray:")
    print(timeit(stmt = "fun5(l2, 5)", number = 1000, globals = globals()))
    print("")

当作为 main 运行时,这会在我的机器上产生以下打印输出,以秒为单位指示完成每个功能的 1000 次试验的时间:

方法一:0.7502102799990098

方法二:0.7291318440002215

方法三:0.24142152300009911

方法四:0.5253471979995084

方法五、numpy给定列表:0.5045417560013448

方法六,numpy给定np.ndarray:0.011147511999297421

当然,这个问题专门询问列表,因此最好的解决方案是使用 try-except 方法,但是通过使用 numpy 数据结构和运算符而不是 python 数据结构提供了速度改进(与 try-except 相比,这里至少提高了 20 倍)很重要,如果在许多对性能至关重要的数据数组上构建某些东西,那么作者应该尝试始终使用 numpy 来利用超快的 C 绑定。 (CPython 解释器,其他解释器性能可能有所不同)

顺便说一句,方法 5 比方法 6 慢得多的原因是因为 numpy 首先必须将给定的列表转换为它自己的 numpy 数组,所以给它一个列表并不会破坏它,它只是没有充分利用可能的速度。

我对列表中的“.index()”方法有同样的问题。 我对它抛出异常这一事实没有异议,但我强烈不同意它是一个非描述性的 ValueError 这一事实。 不过,我可以理解它是否是一个 IndexError。

我可以理解为什么返回“-1”也是一个问题,因为它是 Python 中的有效索引。 但实际上,我从不期望“.index()”方法返回负数。

这里有一个单行(好吧,这是一个相当长的行......),只遍历列表一次,如果未找到该项目,则返回“无”。 如果您愿意,重写它以返回 -1 将是微不足道的。

indexOf = lambda list, thing: \
            reduce(lambda acc, (idx, elem): \
                   idx if (acc is None) and elem == thing else acc, list, None)

如何使用:

>>> indexOf([1,2,3], 4)
>>>
>>> indexOf([1,2,3], 1)
0
>>>

实现比较

Python 3.8 上的简单比较

TL;DR maybeidx2通常更快,除了有很多未命中的数组 (n<100)

def maybeidx1(l, v):
    return l.index(v) if v in l else None

def maybeidx2(l, v):
    try:
        return l.index(v)
    except ValueError:
        return None

测试用例:

a = [*range(100_000)]
# Case 1: index in list
maybeidx1(a, 50_000)
Out[20]: 50000
maybeidx2(a, 50_000)
Out[21]: 50000
# Case 2: index not in list
maybeidx1(a, 100_000) is None
Out[23]: True
maybeidx2(a, 100_000) is None
Out[24]: True

时序案例 1

%timeit maybeidx1(a, 50_000)
1.06 ms ± 15.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit maybeidx2(a, 50_000)
530 µs ± 8.47 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

时序案例 2

%timeit maybeidx1(a, 100_000)
1.07 ms ± 21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit maybeidx2(a, 100_000)
1.07 ms ± 16.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

结果

对更大的数组使用maybeidx2方法。 由于maybeidx1对数组进行了两次扫描以搜索该值,因此速度更快 - 这仍然是 O(n) 时间,但乘数为 2,因此在实践中较慢。 这适用于列表中存在值的情况。 当该值不存在时,这些时间将大致相等; 他们都必须只扫描一次完整的数组,然后返回None try-except的开销可以忽略不计,即使数组大小为 10 -除非发生第二种情况。 然后try-except开销很明显。 例子:

a = [*range(10)]
%timeit maybeidx1(a, 10)
191 ns ± 2.61 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit maybeidx2(a, 10)
566 ns ± 5.93 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

a超过 100 个元素时,这种开销变得可以忽略不计(在我的机器上)。

我会直言不讳:这里的答案非常糟糕,并且具有疯狂的时间复杂度。

这里有一个简单的方法。

使用dict().get('key', 'some_value') ,将返回'key'处的值,如果该键不在字典中,将返回'some_value'

您可以使用列表及其索引创建这样的字典。

mylist = ['cat' 'dog', 'bunny']

mapping = [value: index for index, value in enumerate(mylist)]

然后,如果找到, mapping.get('key', 0)将返回索引,或者None

mapping.get('penguin', 0)  # returns 0

从 Python 3.6 开始, indexrindexfindrfind方法,它们返回-1而不是抛出异常。

我建议:

if thing in thing_list:
  list_index = -1
else:
  list_index = thing_list.index(thing)

我不知道为什么你应该认为它很脏......因为例外? 如果你想要一个oneliner,这里是:

thing_index = thing_list.index(elem) if thing_list.count(elem) else -1

但我建议不要使用它; 我认为 Ross Rogers 的解决方案是最好的,使用一个对象来封装你想要的行为,不要试图以可读性为代价将语言推向极限。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM