我该如何基于条件来分割(拆分、划分)一个列表?

383

我有一些代码,类似于:

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

目标是根据条件将mylist的内容拆分成另外两个列表。

我该如何更加优雅地完成这个操作?能否避免对mylist进行两次独立迭代?这样做可以提高性能吗?


9
来到这里是为了寻找在集合构建器语句中使用条件的方法,你的问题回答了我的问题 :) - Anuvrat Parashar
7
“split”在这个操作中的描述是不幸的,因为它在Python字符串方面已经有了特定的含义。我认为“divide”是一个更精确(或者至少在Python可迭代对象的上下文中不太容易混淆)的词来描述这个操作。我在这里查找与str.split()相当的列表等效方法,以将列表分割成一组连续的子列表。例如,split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6]),而不是按类别划分列表元素。 - Stew
在python-list上讨论了同一主题的内容。讨论链接 - Xiong Chiamiov
IMAGE_TYPES 应该是一个集合而不是元组:IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')。在可读性上几乎没有区别,应该使用 n(1) 而不是 n(o/2)。 - ChaimG
40个回答

1
解决方案
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])

1
如果列表由组和间歇分隔符组成,您可以使用:
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]]

1
使用布尔逻辑将数据分配到两个数组中。
>>> images, anims = [[i for i in files if t ^ (i[2].lower() in IMAGE_TYPES) ] for t in (0, 1)]
>>> images
[('file1.jpg', 33, '.jpg')]
>>> anims
[('file2.avi', 999, '.avi')]


0

不确定这是否是一个好的方法,但也可以用这种方式来完成

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, ([], []))

0

简易生成器版本,尽可能少地在内存中保存值并仅调用pred一次:

from collections import deque
from typing import Callable, TypeVar, Iterable
_T = TypeVar('_T')

def iter_split(pred: Callable[[_T], bool],
               iterable: Iterable[_T]) -> tuple[Iterable[_T], Iterable[_T]]:
    """Split an iterable into two iterables based on a predicate.
    
    The predicate will only be called once per element.
    
    Returns:
        A tuple of two iterables, the first containing all elements for which
        the predicate returned True, the second containing all elements for
        which the predicate returned False.
    """
    iterator = iter(iterable)
    true_values: deque[_T] = deque()
    false_values: deque[_T] = deque()
    
    def true_generator():
        while True:
            while true_values:
                yield true_values.popleft()
            
            for item in iterator:
                if pred(item):
                    yield item
                    break
                false_values.append(item)
            else:
                break
            
    def false_generator():
        while True:
            while false_values:
                yield false_values.popleft()
            
            for item in iterator:
                if not pred(item):
                    yield item
                    break
                true_values.append(item)
            else:
                break

    return true_generator(), false_generator()

0

我使用numpy来解决这个问题,将其限制在几行内并转化为简单的函数。

我能够通过使用np.where将列表分成两部分来满足条件。这适用于数字,但我认为可以扩展到字符串和列表。

这就是代码...

from numpy import where as wh, array as arr

midz = lambda a, mid: (a[wh(a > mid)], a[wh((a =< mid))])
p_ = arr([i for i in [75, 50, 403, 453, 0, 25, 428] if i])
high,low = midz(p_, p_.mean())

0
有时候你不需要列表的另一半。 例如:
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.

0

如果你坚持要聪明一点,你可以采用Winden的解决方案,只需要稍微有些狡猾就行了:

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

3
"d或{}"有一些危险性。如果传入一个空字典,它将不会被地方修改。 - Brian
真的,但它已经被返回了,所以... 实际上,这是为什么你不想在你的代码中添加更多聪明的例子的完美例子。:-P - Anders Eurenius

0
我会采取两步方法,将谓词的评估与列表的过滤分开处理:
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),它将大量逻辑移出解释器并移到高度优化的迭代和映射代码中。如此答案所述,这可以加速对长迭代对象的迭代。

从表达能力上来说,它利用了像理解和映射这样的表达能力丰富的习语。


0

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接