当前位置:  开发笔记 > 编程语言 > 正文

Python:根据条件拆分列表?

如何解决《Python:根据条件拆分列表?》经验,为你挑选了10个好方法。

从美学角度和绩效角度来看,根据条件将项目列表拆分为多个列表的最佳方法是什么?相当于:

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)

这样你就不会处理任何两次而且也不会重复代码.



1> John La Rooy..:
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)


`good.append(x)如果x在goodvals中,则bad.append(x)`更具可读性.
这段代码非常诙谐,不可读.
@dansalmo特别是因为你可以使用for-cycle制作一个单行,如果你想附加比'x`更复杂的东西,你只能把它变成一个'append`:`for m in mylist :(好的,如果好的(x)其他不好).append(x)`
这非常巧妙!我花了一段时间来了解发生了什么.我想知道其他人是否认为这可以被视为可读代码.
@dansalmo"更具可读性".是.但没有更多的乐趣.
一个略短的变化:`(好的,如果在其他的好的情况下是坏的).append(x)`
@MLister,在这种情况下,您应该包括属性lookup(bad.append,good.append)
这是一个很好的答案,我很高兴阅读,但也很高兴它不是公认的答案。

2> dbr..:
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


是不是有列表理解方式而不必循环遍历列表两次?
问题是这违反了DRY原则.如果有更好的方法可以做到这一点真好.
一旦对函数式编程(Haskell)或函数式(LINQ)的兴趣提高,我们开始闻到Python的年龄 - "[x for x in blah if ...]` - verbose,`lambda`是笨拙的有限......感觉就像从1995年开始驾驶最酷的汽车.当时不一样.
@TomaszGandor FTR,Haskell比Python更_older(实际上影响了它的设计).我认为列表理解和lambdas的语法是故意保持在冗长的一面,也许是为了阻止过度使用它们.这确实有点风险......就像我喜欢Haskell一样,我可以看到为什么很多人发现Python通常更具可读性.
简单的for循环是最好的方法...单个循环,非常清晰和可读
这个简单的for循环应该是一个内置函数,鼓励人们在一个显而易见的任务中运行**2**list-comprehension,除了python缺少一个函数,这是一个可怕的想法imho.
执行此操作的一种更优雅但不高效的方法是实际使用groupby,因为这正是您要尝试执行的操作。只有您不想保存组密钥。

3> Ants Aasma..:

这是懒惰的迭代器方法:

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)]`需要很长时间才能运行,你肯定不想做两次!
请注意,`tee`存储它返回的迭代器之间的所有值,因此如果循环遍历整个生成器而不是另一个生成器,它将不会真正节省内存.

4> 小智..:

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

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

这样你就不会处理任何两次而且也不会重复代码.


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

5> sastanin..:

我接受了.我提出了一个懒惰的单通partition函数,它保留了输出子序列中的相对顺序.

1.要求

我认为要求是:

维护元素的相对顺序(因此,没有集合和字典)

每个元素只评估一次条件(因此不使用(i)filtergroupby)

允许任意序列的延迟消耗(如果我们能够预先计算它们,那么天真的实现也可能是可接受的)

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)

魔术发生在它的.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> 小智..:

我基本上喜欢安德斯的方法,因为它很一般.这是一个将分类程序放在第一位(匹配过滤器语法)并使用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



7> RichieHindle..:

先去(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'])

这将提高性能.否则,你看到的对我来说很好.


这不会删除重复吗?
如果列表在拆分之前处于某种顺序并且您需要它们保持该顺序,则不是最佳解决方案.

8> Brian..:

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> BJ Homer..:

就个人而言,我喜欢你引用的版本,假设你已经有了一个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)]

总的来说,我发现列表理解的美学非常令人愉悦.当然,如果你实际上不需要保留排序而不需要重复,那么在集合上使用intersectiondifference方法也可以很好地工作.



10> michau..:

如果要以FP样式制作:

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

这不是最易读的解决方案,但是至少仅一次遍历mylist。

推荐阅读
linjiabin43
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有