我有这样的代码:

good = [x for x in mylist if x in goodvals]
bad = [x for x in mylist if x not in goodvals]

目标是根据mylist的内容是否满足条件,将它们拆分为另外两个列表。

我怎样才能做得更优雅呢?我能避免在mylist上做两个单独的迭代吗?我可以通过这样做来提高性能吗?


当前回答

如果你坚持聪明,你可以采用温登的解决方案,再加上一点虚假的聪明:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d

其他回答

手动迭代,使用条件选择每个元素将被追加到的列表:

good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)

我将采用2步方法,将谓词的求值与列表的过滤分离:

def partition(pred, iterable):
    xs = list(zip(map(pred, iterable), iterable))
    return [x[1] for x in xs if x[0]], [x[1] for x in xs if not x[0]]

就性能而言(除了在iterable的每个成员上只对pred求值一次之外),这样做的好处在于它将大量逻辑从解释器中移出,转移到高度优化的迭代和映射代码中。这可以加快长迭代对象的迭代速度,就像回答中描述的那样。

在表达性方面,它利用了像理解和映射这样的表达性习语。

所有提出的解决方案的问题是,它将扫描和应用过滤功能两次。我会做一个简单的小函数,像这样:

def split_into_two_lists(lst, f):
    a = []
    b = []
    for elem in lst:
        if f(elem):
            a.append(elem)
        else:
            b.append(elem)
    return a, b

这样你就不会重复处理任何东西,也不会重复代码。

为了提高性能,请尝试itertools。

itertools模块标准化了一组快速、内存高效的核心工具,这些工具单独使用或组合使用都很有用。它们一起构成了一个“迭代器代数”,使得用纯Python简洁有效地构造专门的工具成为可能。

出现看到itertools。过滤器或imap。

itertools。iterable ifilter(谓词) 创建一个迭代器,从iterable中过滤元素,只返回谓词为True的元素

def partition(pred, seq):
  return reduce( lambda (yes, no), x: (yes+[x], no) if pred(x) else (yes, no+[x]), seq, ([], []) )