在一个列表的列表中查找某个项目的索引

23

我有一个列表的列表:

colours = [["#660000","#863030","#ba4a4a","#de7e7e","#ffaaaa"],["#a34b00","#d46200","#ff7a04","#ff9b42","#fec28d"],["#dfd248","#fff224","#eefd5d","#f5ff92","#f9ffbf"],["#006600","#308630","#4aba4a","#7ede7e","#aaffaa"]]

最干净的搜索列表并返回其中一项的位置的方法是什么,例如"#660000"?

我查看了 index 方法,但似乎无法解开列表中的列表。

postion = colours.index("#660000")

输出结果为:ValueError: ['#660000'] is not in list,而不是我期望的[0][0]

6个回答

22

我会这样做:

[(i, colour.index(c))
 for i, colour in enumerate(colours)
 if c in colour]
这将返回一个元组的列表,其中第一个索引是第一个列表中的位置,第二个索引是第二个列表中的位置(注意:c 是您要查找的颜色,即 "#660000")。
对于问题中的示例,返回值为:
[(0, 0)]

如果你只需要以一种简单的方式找到颜色第一次出现的位置,你可以使用以下代码:

next(((i, colour.index(c))
      for i, colour in enumerate(colours)
      if c in colour),
     None)

如果找到第一个元素,将返回元组。如果没有找到元素,则返回None(您还可以在上面删除None参数,这样如果没有找到元素,它将引发StopIteration异常)。

编辑:正如@RikPoggi正确指出的那样,如果匹配的数量很高,则此方法将引入一些开销,因为会对colour进行两次迭代以查找c。我假设对于低数量的匹配来说这是合理的,并且可以得到一个单一表达式的答案。然而,为了避免这种情况,您也可以使用相同的思路定义一个方法,如下所示:

def find(c):
    for i, colour in enumerate(colours):
        try:
            j = colour.index(c)
        except ValueError:
            continue
        yield i, j

matches = [match for match in find('#660000')]
请注意,由于find是一个生成器,因此您可以像上面的示例一样使用它与next一起停止在第一个匹配项并跳过进一步查找。

@JayGattuso 你可以使用索引符号访问元组中的每个项目。这是你的意思吗? - jcollado
我并不太喜欢检查 if c in colour 然后调用 colour.index(c) 的想法,对我来说有点浪费。 - Rik Poggi
如果你不这样做,那么Python会抛出一个ValueError: list.index(x): x not in list异常。 - Burhan Khalid
@RikPoggi 我想另一种方法是尝试/捕获,然后忽略异常。 - Burhan Khalid
@RikPoggi 感谢您的评论。我同意做更多的工作可能很重要,特别是当匹配数量很高时。我更新了我的答案,并提供了一些稍微复杂的代码,以应对单个表达式无法胜任的情况。 - jcollado
显示剩余5条评论

9

使用enumerate(),您可以编写以下函数:

def find(target):
    for i,lst in enumerate(colours):
        for j,color in enumerate(lst):
            if color == "#660000":
                return (i, j)
    return (None, None)

8

也许使用numpy会更简单:

>>> import numpy
>>> ar = numpy.array(colours)
>>> numpy.where(ar=="#fff224")
(array([2]), array([1]))

正如您所看到的,您将获得一个包含所有行和列索引的元组。


4
如果您想避免两次迭代目标子列表,则最好(也是最Pythonic的)方法似乎是使用循环:
def find_in_sublists(lst, value):
    for sub_i, sublist in enumerate(lst):
        try:
            return (sub_i, sublist.index(value))
        except ValueError:
            pass

    raise ValueError('%s is not in lists' % value)

1
(1) raise语句缩进错误,应该放在for循环之外。 (2) 我不喜欢使用ValueError来抛出异常,一个简单的(None, None)(-1, -1)可能是更好的设计。 - Rik Poggi
(1) 已修复,谢谢。 (2) 我只是想让它与list.index保持一致,但使用元组可能更好。 - bereal
好的,也许你希望函数的行为类似于list.index()(会引发错误)或者类似于str.find()(不会引发错误)。如果没有更多的细节说明,这是一个完全随意的选择,用户在任何情况下都需要查看文档。 - bereal
保持与list.index一致性正是我不喜欢的。 我看不出那有什么意义。 当您调用例如 str.find()或使用比较运算符in时,您不会引发ValueError。 find_in_sublists()的内部实现对用户未知,他不在乎您在内部使用索引或其他内容。*(很抱歉,我搞乱了注释的顺序)* 无论如何,是的,我同意在某个点之后这是一个设计品味的问题。 - Rik Poggi
1
选择 str.find 合约而不是 list.index 没有任何理由,无论函数如何实现。但另一方面,你遍历嵌套序列的解决方案现在对我来说更好,因为它适用于任何序列,而我的实现仅适用于列表。 - bereal

2
在Python 3中,我使用了这种模式:
CATEGORIES = [   
    [1, 'New', 'Sub-Issue', '', 1],
    [2, 'Replace', 'Sub-Issue', '', 5],
    [3, 'Move', 'Sub-Issue', '', 7],
]

# return single item by indexing the sub list
next(c for c in CATEGORIES if c[0] == 2)

只有当搜索键在每个子列表的第一个元素中时,此方法才有效;从问题中并不完全清楚,但我认为假定它可以出现在任何地方。换句话说:这将适用于查找#660000,但不适用于#863030 - supervacuo

-2

另一件你可以做的事情是选择你想要的列表部分,然后使用索引来找到它。

list_name[0].index("I want coffee")

1
但是你怎么知道你想要哪个“列表部分”呢?也就是说,在不提前了解列表结构的情况下,你从哪里获取0的值? - supervacuo

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