从美学和性能角度来看,基于条件将项目列表拆分为多个列表的最佳方法是什么?等价于:

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


是否有更优雅的方法来做到这一点?

更新:以下是实际用例,以更好地解释我要做什么:

# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f[2].lower() not in IMAGE_TYPES]


评论

登陆这里寻找在set builder语句中有条件的方法,您的问题回答了我的问题:)

split是对此操作的不幸描述,因为它已经对Python字符串具有特定含义。我认为除法是一个更精确的词(或者在Python可迭代的上下文中至少是较少的重载)来描述此操作。我在这里寻找等效于str.split()的列表,以将该列表拆分为有序的连续子列表集合。例如。 split([1,2,3,4,5,3,6],3)->([1,2,,4,5],[6]),而不是将列表的元素按类别划分。

关于python-list的同一主题的讨论。

IMAGE_TYPES应该是集合而不是元组:IMAGE_TYPES = set('。jpg','。jpeg','。gif','。bmp','。png')。 n(1)而不是n(o / 2),实际上在可读性上没有区别。

#1 楼


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


还有更优雅的方法吗?


该代码可读性非常好,而且非常清晰!

# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f[2].lower() not in IMAGE_TYPES]


再次,这很好!

使用集合可能会稍微改善性能,但这是微不足道的区别,我发现列表理解要容易得多阅读,您不必担心订单会被弄乱,重复副本会被删除等等。

实际上,我可能会向后走另一步,而对于循环:

images, anims = [], []

for f in files:
    if f.lower() in IMAGE_TYPES:
        images.append(f)
    else:
        anims.append(f)


列表理解或使用set()很好,直到您需要添加其他检查或其他逻辑-说要删除所有0字节jpeg,只需添加类似的内容即可。.

if f[1] == 0:
    continue


评论


是否有一种无需遍历列表两次即可理解列表的方法?

– Balki
2012年7月21日在15:42

问题在于这违反了DRY原理。如果有更好的方法可以做到这一点。

–锑
2013年5月9日18:03

一旦提高了对函数式编程(Haskell)或函数式样式(LINQ)的胃口,我们就开始闻到Python的时代了-[如果x代表x表示……]-冗长,lambda笨拙且受限制...感觉就像是驾驶1995年至今最酷的汽车。和那时不一样。

– Tomasz Gandor
15年5月24日在13:52

@TomaszGandor FTR,Haskell比Python古老(实际上影响了它的设计)。我认为列表理解和lambda的语法故意保留在冗长的一面,也许是为了防止过度使用它们。确实有一点风险……正如我喜欢Haskell一样,我可以理解为什么很多人认为Python通常更具可读性。

–leftaround关于
15年9月30日在20:04

简单的for循环是执行此操作的最佳方法...单个循环,非常清晰易读

–熵
16年4月21日在10:04

#2 楼

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


评论


那真是太巧妙了!我花了一段时间才了解正在发生的事情。我想知道其他人是否认为这可以视为可读代码。

– jgpaiva
13年4月11日在11:11

如果x处于良好状态,则good.append(x)否则bad.append(x)更具可读性。

– dansalmo
13年5月30日在23:49

@dansalmo特别是因为您可以使用for循环将其设置为单线,并且如果您想附加比x更复杂的内容,则可以仅将其附加到一个附加中:对于mylist中的x :(如果isgood(x )else bad).append(x)

–吗?
14年2月13日在13:37

@MLister,在这种情况下,您可能应该包括属性查找(bad.append,good.append)

– John La Rooy
15年7月12日在7:36

稍微短一点的变化:(如果x处于良好状态则很好,否则不好).append(x)

– Pi Delport
17-10-2在10:39

#3 楼

这是惰性迭代器方法:

from itertools import tee

def split_on_condition(seq, condition):
    l1, l2 = tee((condition(item), item) for item in seq)
    return (i for p, i in l1 if p), (i for p, i in l2 if not p)


它对每个项目评估一次条件,并返回两个生成器,第一个生成条件为true的序列的值,另一个生成条件

因为它很懒,所以可以在任何迭代器上使用它,甚至可以在无限迭代器上使用它:

from itertools import count, islice

def is_prime(n):
    return n > 1 and all(n % i for i in xrange(2, n))

primes, not_primes = split_on_condition(count(), is_prime)
print("First 10 primes", list(islice(primes, 10)))
print("First 10 non-primes", list(islice(not_primes, 10)))


通常,尽管非惰性返回列表的方法更好:

def split_on_condition(seq, condition):
    a, b = [], []
    for item in seq:
        (a if condition(item) else b).append(item)
    return a, b


编辑:对于您更具体的用例,可以通过一些键将项目分为不同的列表,下面是一个通用函数:

DROP_VALUE = lambda _:_
def split_by_key(seq, resultmapping, keyfunc, default=DROP_VALUE):
    """Split a sequence into lists based on a key function.

        seq - input sequence
        resultmapping - a dictionary that maps from target lists to keys that go to that list
        keyfunc - function to calculate the key of an input value
        default - the target where items that don't have a corresponding key go, by default they are dropped
    """
    result_lists = dict((key, []) for key in resultmapping)
    appenders = dict((key, result_lists[target].append) for target, keys in resultmapping.items() for key in keys)

    if default is not DROP_VALUE:
        result_lists.setdefault(default, [])
        default_action = result_lists[default].append
    else:
        default_action = DROP_VALUE

    for item in seq:
        appenders.get(keyfunc(item), default_action)(item)

    return result_lists


用法:

def file_extension(f):
    return f[2].lower()

split_files = split_by_key(files, {'images': IMAGE_TYPES}, keyfunc=file_extension, default='anims')
print split_files['images']
print split_files['anims']


评论


您可能是对的,因为这违反了YAGNI原则。它基于这样的假设:将来可以划分事物的不同列表的数量会增加。

– Ants Aasma
2009年6月4日15:33

可能需要很多代码,但是如果[如果ExpensiveOperation(x)中my_list中的x用于x]需要很长时间才能运行,则您当然不想重复两次!

–dash-tom-bang
13年1月10日在22:29

+1提供多种版本,包括基于迭代器的版本和特定的“ in X”解决方案。 OP的“处于良好状态”可能很小,但是用非常大的词典或昂贵的谓词代替它可能会很昂贵。此外,它还减少了在任何需要的地方两次编写列表理解的需求,从而减少了引入错别字/用户错误的可能性。不错的解决方案。谢谢!

– cod3monk3y
13年11月16日在21:08

请注意,tee在返回的迭代器之间存储所有值,因此,如果您先循环一个生成器,然后循环另一个生成器,则它不会真正节省内存。

– John La Rooy
2014年8月8日下午4:51

#4 楼

所有提出的解决方案的问题在于它将扫描并应用两次过滤功能。我会做一个像这样的简单小函数:
 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
 

这样,您就不会重复处理两次,也不会重复代码。

评论


我同意。我一直在寻找一种“优雅”的方式(即此处的简短和内置/隐式方式)来完成此操作,而无需两次扫描列表,但这似乎是(不进行概要分析的)方式。当然,无论如何,只有大量数据才有意义。

–马修·弗拉申(Matthew Flaschen)
09年6月4日在8:32

恕我直言,如果您知道一种可以减少CPU使用量(从而减少功耗)的方法,则没有理由不使用它。

–缩小
09年6月4日在19:46

@winden ...将我所有的Python移植到C.

–艾略特·卡梅伦(Elliot Cameron)
16年4月27日在21:52

#5 楼

我接受。我提出了一个惰性的单遍partition函数,该函数在输出子序列中保留相对顺序。

1。要求

我假设这些要求是:


维护元素的相对顺序(因此,没有集合和字典)
求值对于每个元素仅条件一次(因此不使用
ifiltergroupby
允许任意使用任意序列的序列(如果我们能够负担
对其进行预计算,那么朴素的实现是可能也是
可以接受的)

2。 split

我的partition函数(在下面介绍)和其他类似函数
已将其放入一个小型库:


python-split

通常可以通过PyPI安装:

pip install --user split


要根据条件拆分列表,请使用partition函数:

>>> from split import partition
>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi') ]
>>> image_types = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> images, other = partition(lambda f: f[-1] in image_types, files)
>>> list(images)
[('file1.jpg', 33L, '.jpg')]
>>> list(other)
[('file2.avi', 999L, '.avi')]


3。 partition函数说明

内部,我们需要一次构建两个子序列,因此仅消耗一个输出序列将迫使另一个输出序列也被计算
。并且我们需要在用户请求之间保持状态(存储已处理
但尚未请求的元素)。为了保持状态,我使用了两个双端
队列(deques):

from collections import deque


SplitSeq类负责管理:

class SplitSeq:
    def __init__(self, condition, sequence):
        self.cond = condition
        self.goods = deque([])
        self.bads = deque([])
        self.seq = iter(sequence)


.getNext()方法神奇。几乎就像迭代器的.next()
,但是这次允许指定我们想要哪种元素
。在后台,它不会丢弃被拒绝的元素,而是将它们放入两个队列之一:

    def getNext(self, getGood=True):
        if getGood:
            these, those, cond = self.goods, self.bads, self.cond
        else:
            these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
        if these:
            return these.popleft()
        else:
            while 1: # exit on StopIteration
                n = self.seq.next()
                if cond(n):
                    return n
                else:
                    those.append(n)


最终用户应该使用partition函数。它带有一个
条件函数和一个序列(就像mapfilter一样),并且
返回两个生成器。第一个生成器构建
的子序列满足条件的元素,第二个元素构建互补子序列。迭代器和生成器允许懒散地拆分甚至很长或无限的序列。

def partition(condition, sequence):
    cond = condition if condition else bool  # evaluate as bool if condition == None
    ss = SplitSeq(cond, sequence)
    def goods():
        while 1:
            yield ss.getNext(getGood=True)
    def bads():
        while 1:
            yield ss.getNext(getGood=False)
    return goods(), bads()


我选择测试函数作为方便实现的第一个参数
将来的部分应用(类似于mapfilter
如何将测试功能作为第一个参数)。

#6 楼

我基本上喜欢Anders的方法,因为它非常笼统。这是一个将分类程序放在第一位(以匹配过滤器语法)并使用defaultdict(假定为已导入)的版本。

评论


我本打算尝试从适用于Python的Zen语句中选择适用于此的语句,但要评论太多了。 =)很棒的代码。

– jpmc26
2014-10-24 18:49

#7 楼

第一次使用(OP之前的编辑):使用集:

mylist = [1,2,3,4,5,6,7]
goodvals = [1,3,7,8,9]

myset = set(mylist)
goodset = set(goodvals)

print list(myset.intersection(goodset))  # [1, 3, 7]
print list(myset.difference(goodset))    # [2, 4, 5, 6]


这对可读性(IMHO)和性能都很好。

第二个go(操作后编辑):

创建一组好的扩展列表:

IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])


,这将提高性能。否则,你的东西对我来说很好。

评论


如果列表在拆分之前按某种顺序排列,而您需要它们保持该顺序,则不是最佳解决方案。

–丹尼雅
09年6月4日在7:48

那不会删除重复项吗?

– mavnn
09年6月4日在7:48

创建集合是O(n log n)。对列表进行两次迭代是O(n)。设置的解决方案可能更优雅(首先正确时),但是肯定会随着n的增加而变慢。

–dash-tom-bang
2013年1月10日22:32

@ dash-tom-bang迭代列表为O(n * n)。这是因为可能需要将列表中的每个项目与每个项目的正常状态进行比较。

– ChaimG
19 Mar 25 '19在15:37

@ChaimG的好处是,尽管我们还需要考虑相交和差值运算的成本(我暂时不知道这点,但是我很确定它们也是超线性的)。

–dash-tom-bang
19 Mar 30 '19在19:36

#8 楼

itertools.groupby几乎可以满足您的要求,只是它要求对项目进行排序以确保获得单个连续范围,因此您需要首先按关键字进行排序(否则每种类型将获得多个交错的组)。例如。

def is_good(f):
    return f[2].lower() in IMAGE_TYPES

files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file3.gif', 123L, '.gif')]

for key, group in itertools.groupby(sorted(files, key=is_good), key=is_good):
    print key, list(group)


给定:

False [('file2.avi', 999L, '.avi')]
True [('file1.jpg', 33L, '.jpg'), ('file3.gif', 123L, '.gif')]


与其他解决方案类似,可以定义键函数分为任意数量的组。

#9 楼

good.append(x) if x in goodvals else bad.append(x)


@dansalmo的这个简洁明了的答案掩盖在评论中,所以我只是在这里重新张贴它作为答案,这样它才能得到应有的重视,特别是对于新读者。 br />
完整示例:

good, bad = [], []
for x in my_list:
    good.append(x) if x in goodvals else bad.append(x)


#10 楼

如果要以FP样式制作它:

good, bad = [ sum(x, []) for x in zip(*(([y], []) if y in goodvals else ([], [y])
                                        for y in mylist)) ]


不是最易读的解决方案,但至少仅迭代一次mylist。

评论


尽管仅循环访问列表一次,但由于追加了列表,因此性能并不理想。追加到列表可能是昂贵的操作(例如,与deque.append相比)。实际上,与此处的其他解决方案相比,该解决方案非常慢(在100000个随机整数上测试21.4s并测试其值)。

–lat
15年7月22日在11:34

#11 楼

就个人而言,我喜欢您引用的版本,假设您已经有了一个列表。如果不是这样,则类似:

good = filter(lambda x: is_good(x), mylist)
bad = filter(lambda x: not is_good(x), mylist)


当然,这实际上与使用列表推导非常相似,就像您最初所做的那样,只是使用函数而不是查找:

good = [x for x in mylist if is_good(x)]
bad  = [x for x in mylist if not is_good(x)]


一般来说,我发现列表理解的美学非常令人愉悦。当然,如果您实际上不需要保留顺序并且不需要重复,那么在集合上使用goodvalsintersection方法也可以很好地工作。

评论


当然,可以将filter(lambda x:is_good(x),mylist)简化为filter(is_good,mylist)

– robru
2014年11月7日,下午1:26

与列表理解相比,添加额外的函数调用实际上使执行时间翻了一番(!),这比我在性能分析中所看到的要好。大多数情况下,很难击败列表理解者。

– Corley Brigman
15年1月13日在13:09

#12 楼

有时候,列表理解似乎不是最好的选择!

我根据人们对该主题给出的答案做了一些测试,并在随机生成的列表上进行了测试。这是列表的生成(可能有更好的方法,但这不是重点):

good_list = ('.jpg','.jpeg','.gif','.bmp','.png')

import random
import string
my_origin_list = []
for i in xrange(10000):
    fname = ''.join(random.choice(string.lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(good_list)
    else:
        fext = "." + ''.join(random.choice(string.lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))


然后我们开始

# Parand
def f1():
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def f2():
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def f3():
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# Ants Aasma
def f4():
    l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
    return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def f5():
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def f6():
    return filter(lambda e: e[2] in good_list, my_origin_list), filter(lambda e: not e[2] in good_list, my_origin_list)


使用cmpthese函数,最好的结果是dbr答案:

f1     204/s  --    -5%   -14%   -15%   -20%   -26%
f6     215/s     6%  --    -9%   -11%   -16%   -22%
f3     237/s    16%    10%  --    -2%    -7%   -14%
f4     240/s    18%    12%     2%  --    -6%   -13%
f5     255/s    25%    18%     8%     6%  --    -8%
f2     277/s    36%    29%    17%    15%     9%  --


评论


此处更新了基准,功能更加快捷。

– ChaimG
5月6日13:55

#13 楼

def partition(pred, iterable):
    'Use a predicate to partition entries into false entries and true entries'
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)


检查此

#14 楼

我认为基于N条件拆分一个可迭代对象的一般做法很方便

from collections import OrderedDict
def partition(iterable,*conditions):
    '''Returns a list with the elements that satisfy each of condition.
       Conditions are assumed to be exclusive'''
    d= OrderedDict((i,list())for i in range(len(conditions)))        
    for e in iterable:
        for i,condition in enumerate(conditions):
            if condition(e):
                d[i].append(e)
                break                    
    return d.values()


例如:

ints,floats,other = partition([2, 3.14, 1, 1.69, [], None],
                              lambda x: isinstance(x, int), 
                              lambda x: isinstance(x, float),
                              lambda x: True)

print " ints: {}\n floats:{}\n other:{}".format(ints,floats,other)

 ints: [2, 1]
 floats:[3.14, 1.69]
 other:[[], None]


如果元素可以满足多个条件,请删除中断。

#15 楼

bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]


append返回None,因此可以正常工作。

#16 楼

这个问题的另一种解决方案。我需要一个尽可能快的解决方案。这意味着仅对列表进行一次迭代,最好是O(1)以便将数据添加到结果列表之一。这与sastanin提供的解决方案非常相似,但更短:

from collections import deque

def split(iterable, function):
    dq_true = deque()
    dq_false = deque()

    # deque - the fastest way to consume an iterator and append items
    deque((
      (dq_true if function(item) else dq_false).append(item) for item in iterable
    ), maxlen=0)

    return dq_true, dq_false


然后,您可以通过以下方式使用该函数:

lower, higher = split([0,1,2,3,4,5,6,7,8,9], lambda x: x < 5)

selected, other = split([0,1,2,3,4,5,6,7,8,9], lambda x: x in {0,4,9})


如果您对生成的deque对象不满意,则可以轻松将其转换为listset任意形式(例如list(lower))。转换要快得多,直接建立列表即可。

此方法保持项目的顺序以及所有重复项。

#17 楼

例如,按偶数和奇数拆分列表

arr = range(20)
even, odd = reduce(lambda res, next: res[next % 2].append(next) or res, arr, ([], []))


或一般来说:

def split(predicate, iterable):
    return reduce(lambda res, e: res[predicate(e)].append(e) or res, iterable, ([], []))



最短的可行方法
缺点


需要功能知识编程范例


#18 楼

受@gnibbler出色的答案(但简洁!)的启发,我们可以应用该方法映射到多个分区:

from collections import defaultdict

def splitter(l, mapper):
    """Split an iterable into multiple partitions generated by a callable mapper."""

    results = defaultdict(list)

    for x in l:
        results[mapper(x)] += [x]

    return results


然后可以按如下方式使用splitter

>>> l = [1, 2, 3, 4, 2, 3, 4, 5, 6, 4, 3, 2, 3]
>>> split = splitter(l, lambda x: x % 2 == 0)  # partition l into odds and evens
>>> split.items()
>>> [(False, [1, 3, 3, 5, 3, 3]), (True, [2, 4, 2, 4, 6, 4, 2])]


这适用于两个以上具有更复杂映射的分区(也适用于迭代器):

>>> import math
>>> l = xrange(1, 23)
>>> split = splitter(l, lambda x: int(math.log10(x) * 5))
>>> split.items()
[(0, [1]),
 (1, [2]),
 (2, [3]),
 (3, [4, 5, 6]),
 (4, [7, 8, 9]),
 (5, [10, 11, 12, 13, 14, 15]),
 (6, [16, 17, 18, 19, 20, 21, 22])]


或使用字典进行映射:

>>> map = {'A': 1, 'X': 2, 'B': 3, 'Y': 1, 'C': 2, 'Z': 3}
>>> l = ['A', 'B', 'C', 'C', 'X', 'Y', 'Z', 'A', 'Z']
>>> split = splitter(l, map.get)
>>> split.items()
(1, ['A', 'Y', 'A']), (2, ['C', 'C', 'X']), (3, ['B', 'Z', 'Z'])]


评论


...只是注意到,这与@ alan-isaac已经回答的基本相同。

–乔什·鲍德(Josh Bode)
13年3月14日在11:20



#19 楼

这是最快的方法。

它使用if else(与dbr的答案类似),但首先创建一个集合。一组将操作次数从O(m * n)减少到O(log m)+ O(n),从而使速度提高了45%以上。

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    if item in good_list_set:
        good.append(item)
    else:
        bad.append(item)


简短一点:

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    out = good if item in good_list_set else bad
    out.append(item)


基准测试结果:

filter_BJHomer                  80/s       --   -3265%   -5312%   -5900%   -6262%   -7273%   -7363%   -8051%   -8162%   -8244%
zip_Funky                       118/s    4848%       --   -3040%   -3913%   -4450%   -5951%   -6085%   -7106%   -7271%   -7393%
two_lst_tuple_JohnLaRoy         170/s   11332%    4367%       --   -1254%   -2026%   -4182%   -4375%   -5842%   -6079%   -6254%
if_else_DBR                     195/s   14392%    6428%    1434%       --    -882%   -3348%   -3568%   -5246%   -5516%   -5717%
two_lst_compr_Parand            213/s   16750%    8016%    2540%     967%       --   -2705%   -2946%   -4786%   -5083%   -5303%
if_else_1_line_DanSalmo         292/s   26668%   14696%    7189%    5033%    3707%       --    -331%   -2853%   -3260%   -3562%
tuple_if_else                   302/s   27923%   15542%    7778%    5548%    4177%     343%       --   -2609%   -3029%   -3341%
set_1_line                      409/s   41308%   24556%   14053%   11035%    9181%    3993%    3529%       --    -569%    -991%
set_shorter                     434/s   44401%   26640%   15503%   12303%   10337%    4836%    4345%     603%       --    -448%
set_if_else                     454/s   46952%   28358%   16699%   13349%   11290%    5532%    5018%    1100%     469%       --


Python的完整基准代码3.7(由FunkySayu修改):

good_list = ['.jpg','.jpeg','.gif','.bmp','.png']

import random
import string
my_origin_list = []
for i in range(10000):
    fname = ''.join(random.choice(string.ascii_lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(list(good_list))
    else:
        fext = "." + ''.join(random.choice(string.ascii_lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

# Parand
def two_lst_compr_Parand(*_):
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def if_else_DBR(*_):
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def two_lst_tuple_JohnLaRoy(*_):
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# # Ants Aasma
# def f4():
#     l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
#     return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def zip_Funky(*_):
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def filter_BJHomer(*_):
    return list(filter(lambda e: e[2] in good_list, my_origin_list)), list(filter(lambda e: not e[2] in good_list,                                                                             my_origin_list))

# ChaimG's answer; as a list.
def if_else_1_line_DanSalmo(*_):
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list else bad.append(e)
    return good, bad

# ChaimG's answer; as a set.
def set_1_line(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list_set else bad.append(e)
    return good, bad

# ChaimG set and if else list.
def set_shorter(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        out = good if e[2] in good_list_set else bad
        out.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def set_if_else(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_set:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def tuple_if_else(*_):
    good_list_tuple = tuple(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_tuple:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

def cmpthese(n=0, functions=None):
    results = {}
    for func_name in functions:
        args = ['%s(range(256))' % func_name, 'from __main__ import %s' % func_name]
        t = Timer(*args)
        results[func_name] = 1 / (t.timeit(number=n) / n) # passes/sec

    functions_sorted = sorted(functions, key=results.__getitem__)
    for f in functions_sorted:
        diff = []
        for func in functions_sorted:
            if func == f:
                diff.append("--")
            else:
                diff.append(f"{results[f]/results[func]*100 - 100:5.0%}")
        diffs = " ".join(f'{x:>8s}' for x in diff)

        print(f"{f:27s} \t{results[f]:,.0f}/s {diffs}")


if __name__=='__main__':
    from timeit import Timer
cmpthese(1000, 'two_lst_compr_Parand if_else_DBR two_lst_tuple_JohnLaRoy zip_Funky filter_BJHomer if_else_1_line_DanSalmo set_1_line set_if_else tuple_if_else set_shorter'.split(" "))


#20 楼

解决方案

from itertools import tee

def unpack_args(fn):
    return lambda t: fn(*t)

def separate(fn, lx):
    return map(
        unpack_args(
            lambda i, ly: filter(
                lambda el: bool(i) == fn(el),
                ly)),
        enumerate(tee(lx, 2)))


测试

[even, odd] = separate(
    lambda x: bool(x % 2),
    [1, 2, 3, 4, 5])
print(list(even) == [2, 4])
print(list(odd) == [1, 3, 5])


#21 楼

如果您不介意使用外部库,那么我知道两个可以自然地实现此操作:

>>> files = [ ('file1.jpg', 33, '.jpg'), ('file2.avi', 999, '.avi')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')




iteration_utilities.partition

>>> from iteration_utilities import partition
>>> notimages, images = partition(files, lambda x: x[2].lower() in IMAGE_TYPES)
>>> notimages
[('file2.avi', 999, '.avi')]
>>> images
[('file1.jpg', 33, '.jpg')]



more_itertools.partition

>>> from more_itertools import partition
>>> notimages, images = partition(lambda x: x[2].lower() in IMAGE_TYPES, files)
>>> list(notimages)  # returns a generator so you need to explicitly convert to list.
[('file2.avi', 999, '.avi')]
>>> list(images)
[('file1.jpg', 33, '.jpg')]




#22 楼

要获得性能,请尝试itertools

itertools模块标准化了一组快速的,内存有效的工具的核心集,这些工具本身或结合使用很有用。它们一起形成了一个“迭代器代数”,从而可以在纯Python中简洁高效地构造专用工具。

请参阅itertools.ifilter或imap。

itertools.ifilter(谓词,可迭代)
进行迭代,从可迭代的元素中过滤出元素,仅返回谓词为True的元素


评论


ifilter / imap(和一般的生成器)非常慢...总的来说,在我的分析中,如果您对100000个整数的简单数组进行列表理解,例如[如果x> 50000中x为x,则为x .shuffle),filter(lambda x:x> 50000,a)将花费2倍的时间,ifilter(lambda x:x> 50000,a); list(result)大约需要2.3倍的时间。奇怪但真实。

– Corley Brigman
15年1月13日在13:12

#23 楼

如果您坚持聪明,则可以采用Winden的解决方案,但又需要一点点虚假的聪明:

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


评论


“ d或{}”有点危险。如果传入了一个空的dict,它将不会在原处被突变。

–布赖恩
09年6月4日在13:20

是的,但是它得到了返回,所以...实际上,这是为什么您不想在代码中添加更多智能的完美示例。 :-P

–安德斯·欧仁纽斯(Anders Eurenius)
09年5月5日在7:19

#24 楼

有时您不需要列表的另一半。
例如:

import sys
from itertools import ifilter

trustedPeople = sys.argv[1].split(',')
newName = sys.argv[2]

myFriends = ifilter(lambda x: x.startswith('Shi'), trustedPeople)

print '%s is %smy friend.' % (newName, newName not in myFriends 'not ' or '')


评论


这不能回答原始问题。

–亚历山大·五世(Alexandre V.)
10月24日9:38

#25 楼

这里已经有很多解决方案,但另一种解决方法是-

anims = []
images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]


仅对列表进行一次迭代,并且看起来更具Python风格,因此可读性强对我来说。

>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file1.bmp', 33L, '.bmp')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> anims = []
>>> images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]
>>> print '\n'.join([str(anims), str(images)])
[('file2.avi', 999L, '.avi')]
[('file1.jpg', 33L, '.jpg'), ('file1.bmp', 33L, '.bmp')]
>>>


#26 楼

我将采用一种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]]


在性能方面,这有什么好处(除了评估prediterable的每个成员上只有一次,这是因为它将大量逻辑移出了解释器,并转移到了高度优化的迭代和映射代码中。如答案中所述,这可以加快长迭代次数的迭代速度。

在表达方式上,它利用了诸如理解和映射之类的表达习语。

#27 楼

不确定这是否是一种好方法,但是也可以通过这种方式完成

IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi')]
images, anims = reduce(lambda (i, a), f: (i + [f], a) if f[2] in IMAGE_TYPES else (i, a + [f]), files, ([], []))


#28 楼

如果列表由组和间歇性分隔符组成,则可以使用:

 def split(items, p):
    groups = [[]]
    for i in items:
        if p(i):
            groups.append([])
        groups[-1].append(i)
    return groups
 


用法:

 split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
 


#29 楼

还有一个答案,简短但“邪恶”(用于列表理解副作用)。

digits = list(range(10))
odd = [x.pop(i) for i, x in enumerate(digits) if x % 2]

>>> odd
[1, 3, 5, 7, 9]

>>> digits
[0, 2, 4, 6, 8]


#30 楼

images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f not in images]

条件较长的情况下很好,例如您的示例。读者不必弄清楚负面条件以及它是否能捕获所有其他情况。