我有一些代码,类似于:
good = [x for x in mylist if x in goodvals]
bad = [x for x in mylist if x not in goodvals]
目标是根据条件将mylist
的内容拆分成另外两个列表。
我该如何更加优雅地完成这个操作?能否避免对mylist
进行两次独立迭代?这样做可以提高性能吗?
手动迭代,使用条件选择一个列表,将每个元素附加到该列表:
good, bad = [], []
for x in mylist:
(bad, good)[x in goodvals].append(x)
good.append(x)
,否则执行bad.append(x)
。这样的代码更易读。 - dansalmox
更加复杂,你也可以将它变成一个 append
:for x in mylist: (好的 if isgood(x) else 不好的).append(x)
- yo'
good = [x for x in mylist if x in goodvals] bad = [x for x in mylist if x not in goodvals]
is there a more elegant way to do this?
那段代码非常易读,清晰明了!
# 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
[x for x in blah if ...]
写法啰嗦,lambda
笨拙且有限制……这就像今天开着1995年最酷的汽车,但感觉和当年不同了。 - Tomasz Gandor这里是惰性迭代器的方法:
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']
[ x for x in my_list if ExpensiveOperation(x) ]
运行时间很长,你肯定不想重复执行它! - dash-tom-bangtee
会存储其返回的迭代器之间的所有值,因此如果您循环遍历完一个生成器再遍历另一个生成器,它实际上并不会节省内存。 - John La Rooy所有提出的解决方案都存在一个问题,那就是它将扫描并两次应用过滤函数。我会编写一个简单的小函数,如下所示:
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
这样你就不会重复处理任何东西,也不会重复编写代码。
我的想法是提出一个懒惰、单遍、partition
函数,可以保留输出子序列的相对顺序。
我假设这些要求是:
i
)filter
或groupby
)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')]
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
函数。它接受一个条件函数和一个序列(与map
或filter
类似),并返回两个生成器。第一个生成器构建满足条件的元素的子序列,第二个生成器构建补集子序列。迭代器和生成器允许即使是长或无限序列也可以进行惰性划分。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()
我选择将测试函数作为第一个参数,以便将来可以进行部分应用(类似于map
和filter
将测试函数作为第一个参数的方式)。
split
包,但是Bitbucket存储库(也是PyPI上的项目主页链接)似乎已经消失了。 - Karl Knechtel我基本上喜欢Anders的方法,因为它非常通用。这里有一个版本,将分类器放在第一位(以匹配筛选语法),并使用了一个默认字典(假设已导入)。
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
受DanSalmo评论的启发,这里提供一种简洁、优雅且同时也是最快解决方案之一。
good_set = set(goodvals)
good, bad = [], []
for item in my_list:
good.append(item) if item in good_set else bad.append(item)
提示:将goodvals
转换为一个集合会使我们获得更快的速度提升。
为了获得最大的速度,我们采用最快的答案,并通过将good_list转换为一个集合来进行加速。这单一的操作就能够给我们带来40%以上的速度提升,而且即使它仍然易读,我们最终得到的解决方案也比最慢的解决方案快5.5倍以上。
good_list_set = set(good_list) # 40%+ 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) # 40%+ 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)
优美之美有些主观,但一些像鲁伯·戈尔德堡式的可爱和独具匠心的解决方案相当令人担忧,并且不应该在任何语言的生产代码中使用,更不用说 Python 了,因为 Python 本质上是优雅的。
基准测试结果:
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(" "))
首次尝试(在 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]
这对于可读性(我的看法)和性能都是有益的。
第二步(在原帖编辑后发布):
将你的好扩展列表创建为一个集合:
IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])
否则,这看起来对我来说很好,但这样做可以提高性能。
goodvals
中的每个项进行比较。 - ChaimGitertools.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')]
与其他解决方案类似,可以定义key func以将其分成任意数量的组。
good.append(x) if x in goodvals else bad.append(x)
这个优雅而简明的回答由@dansalmo在评论中被埋没了,所以我在这里重新发布作为一个回答,以便它能够获得应有的重视,特别是对于新读者。
完整的示例:
good, bad = [], []
for x in my_list:
good.append(x) if x in goodvals else bad.append(x)
(bad, good)[x in goodvals].append(x)
。 - malthe
str.split()
相当的列表等效方法,以将列表分割成一组连续的子列表。例如,split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])
,而不是按类别划分列表元素。 - StewIMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')
。在可读性上几乎没有区别,应该使用 n(1) 而不是 n(o/2)。 - ChaimG