3

我面临一个特殊的问题,最近我觉得需要产生某种类型的列表理解。

例如:

[ re.search('xyz', l).group(0) for l in my_list if re.search('xyz', l) ]

现在,敏锐的读者可能已经观察到这个列表理解确实进行了两次正则表达式匹配。

我想消除这种“额外”的开销,以某种方式失去了列表推导的优雅。以前有人遇到过这样的问题吗?如果是这样,他们是如何解决的?

一般来说f(),我有一个函数应用于列表中的值/对象x,现在我希望f(x).b在我的列表中包含当且仅当f(x).a满足某些条件时。

我知道

empty_list = []
for l in my_list:
    match = re.search('xyz', l)
    if match:
        empty_list.append(match.group(0))

或更一般地说:

empty_list = []
for x in my_list:
    val = f(x)
    if val.a == condition:
        empty_list.append(val.b)

是一个可能的解决方案,但这似乎太冗长了,我敢肯定有一种更“pythonic”的方式来做到这一点。

4

4 回答 4

4

使用生成器推导:

# Your original list of data.
my_list = []

# This 'knows' how to produce a new list of elements where each 
# element satisfies some test, here represented as `predicate`.
# Looks like a list comprehension, but note the surrounding parens.
passing = (x for x in my_list if predicate(x))

# This knows how to apply some function `f` to each element in the list.
mapped = (f(x) for x in passing)

# Now use the know-how above to actually create your list.
# Note that because you're using chained generators, you are 
# only iterating over the original list once!
results = list(mapped)

# Or if you don't need all the intermediate values at once...
for el in mapped:
  do_something_with(el)
于 2013-10-29T14:05:22.337 回答
3

正如@tobias_k 在评论中所说,您可以使用嵌套理解(这里是生成器):

>>> [m.group(0) 
     for m in (re.search('xyz', item) for item in ['abc', 'aaxyz', 'xyz'])
     if m is not None]
['xyz', 'xyz']

为了测试是否有匹配,我总是使用if m is not None而不是if m. 失败的匹配总是None(并且身份测试非常快),而测试真实匹配对象的真实性会调用匹配对象上的方法,这对于足够数量的匹配对象可能会对运行时产生重大影响。如果您不在 100 MB 到 GB 的输入文本比例问题范围内,这无关紧要,但对于较小比例的问题并没有什么坏处。

于 2013-10-29T14:20:58.263 回答
1

就个人而言,在这种情况下,我会这样做:

matches = [i.group(0) for i in filter(None, (re.search('xyz', i) for i in my_list))]

(没有listif 你只需要遍历它)。

产生生成器表达式的filter所有具有布尔值的元素True(在这种情况下,那些不是None)。

但是,如果您想使用这种方法明确检查None,您需要这样的事情:

matches = [i.group(0) for i in filter(lambda x: x is not None, (re.search('xyz', i) for i in my_list)))]
于 2013-10-29T14:41:51.413 回答
1

如果您想避免计算两次,您可以将正则表达式分解为中间生成器理解。

让我们从您的 for 循环解决方案开始

for l in my_list:
  match = re.search('xyz', l)
  if match:
    empty_list.append(match.group(0))

我们可以通过人为地将匹配项放入一个元素列表中来将其变成一个双循环:

for l in my_list:
  for match in [re.search('xyz', l)]:
    if match:
      empty_list.append(match.group(0))

由于现在这只是一堆 for 循环和 if 语句,因此将其转换为嵌套列表推导很简单:

[ match.group(0) for match in [re.search('xyz', l) for l in my_list] if match ]

事实上,如果你想避免生成不必要的列表,你可以将内部列表变成生成器理解

[ match.group(0) for match in (re.search('xyz', l) for l in my_list) if match ]

最后一部分与您的问题无关,但我想指出,某些语言允许您在列表理解中绑定名称,这将允许您将整个内容编写为非嵌套列表理解。以下不是有效的 Python 代码,但可能

[ match.group(0) for l in my list, let match = re.search('xyz', l), if match ]

例如,在 Haskell 中,您可以编写

[ group 0 match | elem <- myList, let match = reSearch "xyz" elem, match ]

这是有效的代码(以我组成所有函数名的事实为模)。

于 2013-10-29T14:21:40.103 回答