Python列表推导式与if-else条件

3

我有一个名为detected_chems的化学品名称小列表(<100)。

还有一个更大的可迭代对象,一个包含化学品名称作为键化学品属性作为值的字典chem_db。就像这样:

{'chemicalx':{'property1':'smells','property2':'poisonous'},
 'chemicaly':{'property1':'stinks','property2':'toxic'}}

我正在尝试将检测到的所有化学物质与数据库中的物质进行匹配,并提取它们的属性。我已经研究了这些问题/答案,但似乎无法将其应用到我的情况中(抱歉)。
因此,我正在创建一个结果列表res,但是我没有使用嵌套的for循环和if x in条件,而是创建了这个。
res = [{chem:chem_db[chem]}
       for det_chem in detected_chems
       for chem in chem_db.keys()
       if det_chem in chem]

这在某种程度上是有效的!

我认为我在这里做的是创建一个字典列表,其中将具有化学名称(键)和关于化学物质的信息(作为值的字典本身)的键值对,如果检测到的化学物质在化学数据库(chem_db)中找到。

问题是,并非所有检测到的化学物质都能在数据库中找到。这 可能 是由于拼写错误或名称变化(例如,它们包含数字)或类似的原因。

因此,为了解决问题,我需要确定哪些被检测到的化学物质没有匹配成功。我认为这可能是一个解决方案:

not_matched=[]
res = [{chem:chem_db[chem]}
       for det_chem in detected_chems
       for chem in chem_db.keys()
       if det_chem in chem else not_matched.append(det_chem)]

我遇到了语法错误,是由于else not_matched.append(det_chem)这部分引起的。

我的两个问题:

1)为了避免语法错误,我应该把else条件放在哪里?

2)是否可以在列表推导式内部构建not_matched列表,以便我不必先创建一个空列表。

res = [{chem:chem_db[chem]}
       for det_chem in detected_chems
       for chem in chem_db.keys()
       if det_chem in chem else print(det_chem)]

我想要实现的是类似于以下内容:
in: len(detected_chems)
out: 20
in: len(res)
out: 18
in: len(not_matched)
out: 2

in: print(not_matched)
out: ['chemical_strange_character$$','chemical___WeirdSPELLING']

那会帮助我找到问题并解决匹配。


4
你能否按照“最小可重现示例”(Minimal, Reproducible Example)的要求,移除问题中不必要的信息?这样做可以让问题更加简明扼要。示例请参考链接:https://stackoverflow.com/help/minimal-reproducible-example。 - shaik moeed
谢谢 - 我将删除与我的问题无直接关系的不必要信息。 - Westworld
4个回答

4
你应该:
if det_chem in chem or not_matched.append(det_chem)

尽管如此,如果按照评论所述进行一些清理,我认为有一种更有效率的方法可以实现你想要的功能。上述代码的解释是append返回None,因此整个if条件将被评估为False(但该项目仍将添加到not_matched列表中)。

关于效率:

res = [{det_chem:chem_db[det_chem]}
       for det_chem in detected_chems
       if det_chem in chem_db or not_matched.append(det_chem)]

这应该会显著更快——字典键上的for循环是O(n)操作,而使用字典正是因为查找是O(1),所以我们使用基于哈希的det_chem in chem_db查找,而不是逐个检索键并进行比较。
奖励:字典推导式(解决问题2)
我不确定为什么需要构建一个一键字典列表,但可能需要的是一个字典推导式,如下所示:
chem_db = {1: 2, 4: 5}
detected_chems = [1, 3]
not_matched = []
res = {det_chem: chem_db[det_chem] for det_chem in detected_chems if
       det_chem in chem_db or not_matched.append(det_chem)}
# output
print(res) # {1: 2}
print(not_matched) # [3]

我想不到一种同时构建not_matched列表和使用单个列表/字典推导来构建res的方法。


谢谢。请澄清: 1)解决方案应该比什么快得多? 2)因为它是一个字典,所以应该是if det_chem in chem_db.keys()吗? - Westworld
@Jimmy9zz:1)比使用“for chem in chem_db.keys()”要快得多-2)不,检查某个东西是否在字典中完成为“x in my_dict”,这是O(1),检查键是O(len(keys)) - Mr_and_Mrs_D
我也打算写这个答案。有更简单的方法可以做到。 - Nijat Mursali
1
@Jimmy9zz:添加了有关字典推导的部分。 - Mr_and_Mrs_D

1

列表推导式正式包含三个部分。让我们通过一个例子来展示它们:

[2 * i          for i in range(10)         if i % 3 == 0]
  1. 第一部分是一个表达式,它可能包含三元操作符(x if y else z)。

  2. 第二部分是一个列表(或多个嵌套的for循环中的列表),用于从中选择变量的值。

  3. 第三部分(可选)是一个过滤器(用于在第2部分中进行选择),此处不允许使用else子句!

因此,如果您想使用else分支,必须将其放入第一部分中,例如:

[2 * i  if i < 5  else 3 * i           for i in range(10)          if i % 3 == 0]

1
你的语法错误源于推导式不接受 else 子句。
你可以使用三元运算符 ... if ... else ... 来确定推导式结果中要放置的值。类似下面这样:
not_matched=[]
res = [{chem:chem_db[chem]} if det_chem in chem else not_matched.append(det_chem)
       for det_chem in detected_chems
       for chem in chem_db.keys()]

但这是一个不好的主意,因为对于每个未匹配的情况,你的 res 中都会有 None。这是因为 ... if ... else ... 运算符总是返回一个值,在你的情况下,该值将是 list.append 方法的返回值(= None)。你可以过滤 res 列表以删除 None 值,但是... 呃...
一种更好的解决方案是仅保留您的第一个理解,并获取原始chem列表和res列表之间的差异。
not_matched = set(chems).difference(<the already matched chems>)

请注意,我使用了一个已匹配的chems占位符而不是一段真正的代码,因为您存储res的方式根本不实用。实际上,它是一个单键字典的列表,这是没有意义的。字典的作用是保存由键标识的多个值。
解决方案是将res变成一个字典,而不是一个列表,使用字典推导式:
res = {chem: chem_db[chem]
       for det_chem in detected_chems
       for chem in chem_db.keys()
       if det_chem in chem}

这样做,已匹配的化学品占位符可以被res.values()替换。
作为补充,尽管在许多情况下,推导式是一种非常酷的功能,但它们并不是应该在任何地方都使用的神奇功能。而且,嵌套推导式很难阅读,应该避免使用(至少在我看来)。

谢谢你的意见。就像我最初提问时所述(为了简洁而后来删除),我有意尝试在任何地方都使用列表推导式,只是为了学习!希望以后,当我学会它们/不适合它们的地方时,我将有能力根据需要选择它们或嵌套的for循环。 - Westworld

0
下面的示例代码将为您提供所需的输出。它使用了字典推导式而不是列表推导式来捕获匹配的字典项信息作为一个字典。这是因为您需要一个匹配的化学物品的字典,而不是一个列表。在匹配项目的字典中,稍后获取它们的属性会更容易。另外,您不需要使用chem_db.keys(),因为"in"运算符本身搜索整个序列(无论是列表还是字典)中的目标。如果seq是一个字典,则将目标与字典内的所有键匹配。
代码:
detected_chems=['chemical_strange_character$$','chemical___WeirdSPELLING','chem3','chem4','chem5','chem6','chem7','chem8','chem9','chem10','chem11','chem12','chem13','chem14','chem15','chem16','chem17','chem18','chem19','chem20']
chem_db = {'chem1':{'property1':'smells','property2':'poisonous'},'chem2':{'property1':'stinks','property2':'toxic'},'chem3':{'property1':'smells','property2':'poisonous'},'chem4':{'property1':'smells','property2':'poisonous'},'chem5':{'property1':'smells','property2':'poisonous'},'chem6':{'property1':'smells','property2':'poisonous'},'chem7':{'property1':'smells','property2':'poisonous'},'chem8':{'property1':'smells','property2':'poisonous'},'chem9':{'property1':'smells','property2':'poisonous'},'chem10':{'property1':'smells','property2':'poisonous'},'chem11':{'property1':'smells','property2':'poisonous'},'chem12':{'property1':'smells','property2':'poisonous'},'chem13':{'property1':'smells','property2':'poisonous'},'chem14':{'property1':'smells','property2':'poisonous'},'chem15':{'property1':'smells','property2':'poisonous'},'chem16':{'property1':'smells','property2':'poisonous'},'chem17':{'property1':'smells','property2':'poisonous'},'chem18':{'property1':'smells','property2':'poisonous'},'chem19':{'property1':'smells','property2':'poisonous'},'chem20':{'property1':'smells','property2':'poisonous'}}

not_matched = []

res = {det_chem:chem_db[det_chem]
       for det_chem in detected_chems
       if det_chem in chem_db or not_matched.append(det_chem)}

print(len(detected_chems))
print(len(res))
print(len(not_matched))

print(not_matched)

输出:

20
18
2
['chemical_strange_character$$', 'chemical___WeirdSPELLING']

如果需要更多信息,请查看:Python字典

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