Python:根据条件拆分一个列表?

从美观和性能的角度来看,最好的方法是将条目列表分成多个列表,基于条件? 相当于:

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] 
 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] 

再次,这是好的!

使用集合可能会有轻微的性能改进,但这是一个微不足道的差异,我发现列表理解更容易阅读,而且您不必担心订单被搞乱,重复删除等等。

事实上,我可能会“倒退”一步,只是使用一个简单的for循环:

 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 
 good, bad = [], [] for x in mylist: (bad, good)[x in goodvals].append(x) 

这是懒惰的迭代器方法:

 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) 

它对每个项目的条件进行一次评估,并返回两个生成器,首先从条件为真的序列中产生值,而另一个则是假的。

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

 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'] 

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

 def SplitIntoTwoLists(l, f): a = [] b = [] for i in l: if f(i): a.append(i) else: b.append(i) return (a,b) 

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

我承担这一点。 我提出了一个懒惰的单通partition函数,它在输出子序列中保留相对的顺序。

1.要求

我假设的要求是:

  • 保持元素的相对顺序(因此,没有集合和字典)
  • 对每个元素只评估一次(因此不使用( ifiltergroupby
  • 允许任一序列的惰性消耗(如果我们能够预先计算它们,那么简单的实现可能也是可以接受的)

2. split

我的partition功能(下面介绍)和其他类似的功能已经把它变成一个小型的库:

  • 蟒蛇分裂

它通常通过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) 

Magic发生在它的.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将测试函数作为第一个参数)。

首先去 (预操作编辑):使用集合:

 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)和性能都很好。

第二次去 (OP-edit后):

创建好的扩展名列表作为一个集合:

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

这会提高性能。 否则,你对我看起来很好。

我基本上喜欢安德斯的方法,因为它是非常一般的。 这是一个版本,首先把分类器(匹配过滤器语法),并使用defaultdict(假定导入)。

 def categorize(func, seq): """Return mapping from categories to lists of categorized items. """ d = defaultdict(list) for item in seq: d[func(item)].append(item) return d 

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')] 

类似于其他的解决方案,关键的func可以被定义为分成任意数量的组。

就我个人而言,我喜欢你引用的版本,假设你已经有一个悬挂的名单。 如果不是这样的话:

 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)] 

总的来说,我觉得列表理解的美学是非常令人愉快的。 当然,如果你实际上并不需要保持排序,也不需要重复,那么在集合上使用intersectiondifference方法也可以。

如果你想使它在FP风格:

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

不是最可读的解决方案,但至少在mylist中迭代一次。

 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) 

检查这个

我认为基于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] 

如果元素可能满足多个条件,则删除该中断。

有时,看起来列表理解不是最好的使用!

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

 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% -- 

这个问题的另一个解决方案。 我需要一个尽可能快的解决方案。 这意味着只有一个迭代在列表中,最好是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对象deque ,你可以很容易地将它转换为listset ,任何你喜欢的(例如list(lower) )。 转换速度要快得多,直接构建列表。

这种方法保持项目的顺序,以及任何重复。

为了性能,请尝试itertools

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

请参阅itertools.ifilter或imap。

itertools.ifilter(谓词,可迭代)

创建一个迭代器,用于过滤来自iterable的元素,仅返回谓词为True的元素

有时候你不需要另一半的名单。 例如:

 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 '') 

我最喜欢的食谱是:

 goodvals = set(goodvals) good, bad = [], [] _ = [good.append(x) if x in goodvals else bad.append(x) for x in mylist] 

简单,快速,可读性强 Python本来就是这样的。

  • 通过把一个set (使用一个哈希表)而不是一个tuple ,我们得到超快速的查找。
  • mylist每个项目只被检查一次。 这有助于使其更快。
  • _ =是一种Pythonic方式来声明我们正在故意排除列表理解的结果。 这不是一个错误。

(根据丹萨尔莫对这个答案的评论,因为它似乎应该是自己的答案。)

如果你坚持聪明,你可以采取温登的解决方案,只是一点虚假的聪明:

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

如果你的担心是不使用两行代码的语义只需要一次你只是在上面的一些方法(甚至你自己的)在一个单一的功能:

 def part_with_predicate(l, pred): return [i for i in l if pred(i)], [i for i in l if not pred(i)] 

这不是一个懒惰的方法,它遍历整个列表两次,但它允许你在一行代码中分割列表。

受@ 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)].append(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'])] 

这里已经有很多解决方案了,但是另外一个方法就是 –

 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)] 

迭代列表只有一次,看起来更Pythonic,因此对我来说是可读的。

 >>> 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')] >>> 

我会采取一个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一次)之外,这样做的iterable在于,它将大量逻辑从解释器中移出,并转化为高度优化的迭代和映射代码。 这可以加快对迭代过长的迭代,如本答案中所述 。

表达方式,它利用了表达的理解和映射成语。

 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]) 

如果你不介意使用一个外部库有两个我知道,nativly实施这个操作:

 >>> 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')] 

不知道这是否是一个好方法,但也可以这样做

 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, ([], [])) 
 def partition(pred, seq): return reduce( lambda (yes, no), x: (yes+[x], no) if pred(x) else (yes, no+[x]), seq, ([], []) )