从美学角度和绩效角度来看,根据条件将项目列表拆分为多个列表的最佳方法是什么?相当于:
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]
John La Rooy.. 196
good, bad = [], [] for x in mylist: (bad, good)[x in goodvals].append(x)
`good.append(x)如果x在goodvals中,则bad.append(x)`更具可读性. (156认同)
这段代码非常诙谐,不可读. (28认同)
@dansalmo特别是因为你可以使用for-cycle制作一个单行,如果你想附加比'x`更复杂的东西,你只能把它变成一个'append`:`for m in mylist :(好的,如果好的(x)其他不好).append(x)` (19认同)
这非常巧妙!我花了一段时间来了解发生了什么.我想知道其他人是否认为这可以被视为可读代码. (12认同)
@dansalmo"更具可读性".是.但没有更多的乐趣. (8认同)
一个略短的变化:`(好的,如果在其他的好的情况下是坏的).append(x)` (8认同)
@MLister,在这种情况下,您应该包括属性lookup(bad.append,good.append) (2认同)
这是一个很好的答案,我很高兴阅读,但也很高兴它不是公认的答案。 (2认同)
dbr.. 103
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
是不是有列表理解方式而不必循环遍历列表两次? (36认同)
问题是这违反了DRY原则.如果有更好的方法可以做到这一点真好. (32认同)
一旦对函数式编程(Haskell)或函数式(LINQ)的兴趣提高,我们开始闻到Python的年龄 - "[x for x in blah if ...]` - verbose,`lambda`是笨拙的有限......感觉就像从1995年开始驾驶最酷的汽车.当时不一样. (20认同)
@TomaszGandor FTR,Haskell比Python更_older(实际上影响了它的设计).我认为列表理解和lambdas的语法是故意保持在冗长的一面,也许是为了阻止过度使用它们.这确实有点风险......就像我喜欢Haskell一样,我可以看到为什么很多人发现Python通常更具可读性. (6认同)
简单的for循环是最好的方法...单个循环,非常清晰和可读 (4认同)
这个简单的for循环应该是一个内置函数,鼓励人们在一个显而易见的任务中运行**2**list-comprehension,除了python缺少一个函数,这是一个可怕的想法imho. (3认同)
执行此操作的一种更优雅但不高效的方法是实际使用groupby,因为这正是您要尝试执行的操作。只有您不想保存组密钥。 (2认同)
Ants Aasma.. 100
这是懒惰的迭代器方法:
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 my_list中的x,如果ExpensiveOperation(x)]`需要很长时间才能运行,你肯定不想做两次! (16认同)
请注意,`tee`存储它返回的迭代器之间的所有值,因此如果循环遍历整个生成器而不是另一个生成器,它将不会真正节省内存. (3认同)
小智.. 24
所有提出的解决方案的问题在于它将扫描并应用过滤功能两次.我会做一个像这样的简单小函数:
def SplitIntoTwoLists(l, f): a = [] b = [] for i in l: if f(i): a.append(i) else: b.append(i) return (a,b)
这样你就不会处理任何两次而且也不会重复代码.
good, bad = [], [] for x in mylist: (bad, good)[x in goodvals].append(x)
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
这是懒惰的迭代器方法:
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
函数,它保留了输出子序列中的相对顺序.
我认为要求是:
维护元素的相对顺序(因此,没有集合和字典)
每个元素只评估一次条件(因此不使用(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
有测试功能作为第一个参数).
我基本上喜欢安德斯的方法,因为它很一般.这是一个将分类程序放在第一位(匹配过滤器语法)并使用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
先去(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)和性能都有好处.
第二次去(后OP编辑):
创建一个好的扩展列表作为一组:
IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])
这将提高性能.否则,你看到的对我来说很好.
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')]
与其他解决方案类似,可以将键功能定义为分成您想要的任意数量的组.
就个人而言,我喜欢你引用的版本,假设你已经有了一个goodvals
悬挂列表.如果没有,比如:
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)]
总的来说,我发现列表理解的美学非常令人愉悦.当然,如果你实际上不需要保留排序而不需要重复,那么在集合上使用intersection
和difference
方法也可以很好地工作.
如果要以FP样式制作:
good, bad = [ sum(x, []) for x in zip(*(([y], []) if y in goodvals else ([], [y]) for y in mylist)) ]
这不是最易读的解决方案,但是至少仅一次遍历mylist。