Python: chia một danh sách dựa trên một điều kiện?


272

Cách tốt nhất, cả về mặt thẩm mỹ và từ góc độ hiệu suất, để phân chia một danh sách các mục thành nhiều danh sách dựa trên một điều kiện? Tương đương với:

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

Có cách nào thanh lịch hơn để làm điều này?

Cập nhật: đây là trường hợp sử dụng thực tế, để giải thích rõ hơn những gì tôi đang cố gắng thực hiện:

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

5
hạ cánh ở đây để tìm cách có một điều kiện trong tuyên bố của người xây dựng, câu hỏi của bạn đã trả lời câu hỏi của tôi :)
Anuvrat Parashar

5
split là một mô tả đáng tiếc của hoạt động này, vì nó đã có một ý nghĩa cụ thể đối với các chuỗi Python. Tôi nghĩ rằng chia là một từ chính xác hơn (hoặc ít nhất là quá tải trong ngữ cảnh của Python iterables) để mô tả hoạt động này. Tôi đến đây để tìm một danh sách tương đương str.split(), để chia danh sách thành một bộ sưu tập các danh sách phụ liên tiếp. Ví dụ split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6]), trái ngược với việc phân chia các yếu tố của danh sách theo danh mục.
Hầm

Thảo luận về cùng một chủ đề trên danh sách python.
Xiong Chiamiov

IMAGE_TYPES phải là một bộ thay vì một tuple : IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (1) thay vì n (o / 2), thực tế không có sự khác biệt về khả năng đọc.
ChaimG

Câu trả lời:


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

Có cách nào thanh lịch hơn để làm điều này?

Mã đó là hoàn toàn dễ đọc, và cực kỳ rõ ràng!

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

Một lần nữa, điều này là tốt!

Có thể có những cải tiến hiệu suất nhỏ khi sử dụng các bộ, nhưng đó là một sự khác biệt nhỏ và tôi thấy việc hiểu danh sách dễ đọc hơn rất nhiều và bạn không phải lo lắng về việc trật tự bị rối, các bản sao bị xóa như vậy.

Trong thực tế, tôi có thể đi một bước nữa "lạc hậu" và chỉ sử dụng một vòng lặp đơn giản:

images, anims = [], []

for f in files:
    if f.lower() in IMAGE_TYPES:
        images.append(f)
    else:
        anims.append(f)

Việc hiểu hoặc sử dụng danh sách set()là tốt cho đến khi bạn cần thêm một số kiểm tra khác hoặc một chút logic khác - giả sử bạn muốn xóa tất cả jpeg 0 byte, bạn chỉ cần thêm một cái gì đó như ..

if f[1] == 0:
    continue

44
Không có cách hiểu danh sách mà không phải lặp qua danh sách hai lần sao?
balki

35
Vấn đề là điều này vi phạm nguyên tắc DRY. Sẽ tốt hơn nếu có một cách tốt hơn để làm điều này.
Antimon

21
Một khi sự khao khát lập trình chức năng (Haskell), hoặc phong cách chức năng (LINQ) được nâng lên, chúng ta bắt đầu ngửi thấy Python trong độ tuổi của nó - [x for x in blah if ...]- dài dòng, lambdavụng về và hạn chế ... Cảm giác như lái chiếc xe tuyệt vời nhất từ ​​năm 1995 ngày nay. Không giống như hồi đó.
Tomasz Gandor

6
@TomaszGandor FTR, Haskell hơn Python (và thực sự ảnh hưởng đến thiết kế của nó). Tôi nghĩ rằng cú pháp để hiểu danh sách và lambdas đã cố tình giữ một chút về phía dài dòng, có lẽ để ngăn cản việc sử dụng chúng quá mức. Đó thực sự là một chút rủi ro ... nhiều như tôi thích Haskell, tôi có thể thấy lý do tại sao nhiều người tìm thấy Python thường dễ đọc hơn.
rẽ trái

4
vòng lặp đơn giản là cách tốt nhất để làm điều này ... một vòng lặp duy nhất, rất rõ ràng và dễ đọc
Anentropic

217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)

14
Đó là vô cùng khéo léo! Tôi phải mất một lúc để hiểu những gì đang xảy ra. Tôi muốn biết nếu người khác nghĩ rằng đây có thể được coi là mã có thể đọc được hay không.
jgpaiva

171
good.append(x) if x in goodvals else bad.append(x)dễ đọc hơn
dansalmo

21
@dansalmo Đặc biệt vì bạn có thể biến nó thành một lớp lót theo chu kỳ và nếu bạn muốn nối thêm một thứ gì đó phức tạp hơn x, bạn chỉ có thể biến nó thành một append:for x in mylist: (good if isgood(x) else bad).append(x)
yo '

2
@MLister, trong trường hợp đó có lẽ bạn nên bao gồm tra cứu thuộc tính(bad.append, good.append)
John La Rooy

11
Một biến thể ngắn hơn một chút:(good if x in goodvals else bad).append(x)
Pi Delport

104

Đây là cách tiếp cận lặp lặp lười biếng:

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)

Nó đánh giá điều kiện một lần cho mỗi mục và trả về hai trình tạo, đầu tiên mang lại các giá trị từ chuỗi trong đó điều kiện là đúng, điều còn lại là sai.

Bởi vì nó lười biếng, bạn có thể sử dụng nó trên bất kỳ trình vòng lặp nào, thậm chí là vô hạn:

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

Thông thường mặc dù cách tiếp cận danh sách không lười biếng là tốt hơn:

def split_on_condition(seq, condition):
    a, b = [], []
    for item in seq:
        (a if condition(item) else b).append(item)
    return a, b

Chỉnh sửa: Đối với một usecase cụ thể hơn của bạn về việc chia các mục thành các danh sách khác nhau bằng một số phím, đây là một chức năng chung thực hiện điều đó:

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

Sử dụng:

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

Bạn có thể đúng rằng điều này vi phạm nguyên tắc YAGNI. Nó dựa trên giả định rằng số lượng danh sách khác nhau mà mọi thứ có thể được phân chia thành sẽ phát triển trong tương lai.
Kiến Aasma

17
Nó có thể là rất nhiều mã nhưng nếu [ x for x in my_list if ExpensiveOperation(x) ]mất nhiều thời gian để chạy, bạn chắc chắn không muốn làm điều đó hai lần!
dash-tom-bang

1
+1 để cung cấp nhiều biến thể bao gồm dựa trên iterator và giải pháp "trong X" cụ thể. "Trong những điều tốt đẹp" của OP có thể nhỏ, nhưng thay thế điều này bằng một từ điển rất lớn hoặc vị ngữ đắt tiền có thể tốn kém. Ngoài ra, nó giảm nhu cầu viết mức độ hiểu danh sách hai lần ở mọi nơi cần thiết, do đó giảm khả năng đưa ra lỗi chính tả / lỗi người dùng. Giải pháp tốt đẹp. Cảm ơn!
cod3monk3y

3
Lưu ý rằng teelưu trữ tất cả các giá trị giữa các trình vòng lặp mà nó trả về, vì vậy nó sẽ không thực sự tiết kiệm bộ nhớ nếu bạn lặp qua toàn bộ một trình tạo và sau đó là các trình tạo khác.
John La Rooy

25

Vấn đề với tất cả các giải pháp được đề xuất là nó sẽ quét và áp dụng chức năng lọc hai lần. Tôi sẽ thực hiện một chức năng nhỏ đơn giản như thế này:

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

Bằng cách đó, bạn không xử lý bất cứ điều gì hai lần và cũng không lặp lại mã.


Tôi đồng ý. Tôi đang tìm kiếm một cách "thanh lịch" (nghĩa là ở đây có nghĩa là ngắn gọn và tích hợp / ẩn) để làm điều này mà không cần quét danh sách hai lần, nhưng dường như (không có hồ sơ) là cách để đi. Tất nhiên nó chỉ quan trọng đối với số lượng lớn dữ liệu.
Matthew Flaschen

IMHO, nếu bạn biết cách thực hiện với việc sử dụng cpu ít hơn (và do đó ít tiêu hao năng lượng hơn), không có lý do gì để không sử dụng nó.
gió

2
@winden ... Chuyển tất cả Python của tôi sang C .;)
Elliot Cameron

19

Tôi nhận nó. Tôi đề xuất một hàm lười biếng, một lượt, partitionduy trì thứ tự tương đối trong các chuỗi đầu ra.

1. Yêu cầu

Tôi giả định rằng các yêu cầu là:

  • duy trì trật tự tương đối của các yếu tố (do đó, không có bộ và từ điển)
  • chỉ đánh giá điều kiện một lần cho mọi phần tử (do đó không sử dụng ( i) filterhoặc groupby)
  • cho phép lười biếng tiêu thụ một trong hai chuỗi (nếu chúng tôi có thể đủ khả năng để tính toán trước chúng, thì việc thực hiện ngây thơ cũng có thể được chấp nhận)

2. splitthư viện

partitionHàm của tôi (được giới thiệu bên dưới) và các hàm tương tự khác đã biến nó thành một thư viện nhỏ:

Nó có thể cài đặt bình thường thông qua PyPI:

pip install --user split

Để phân chia một danh sách dựa trên điều kiện, sử dụng partitionchức năng:

>>> 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. partitionchức năng giải thích

Trong nội bộ, chúng ta cần xây dựng hai chuỗi con cùng một lúc, do đó, chỉ tiêu thụ một chuỗi đầu ra cũng sẽ buộc một chuỗi khác cũng được tính toán. Và chúng ta cần giữ trạng thái giữa các yêu cầu của người dùng (lưu trữ các phần tử được xử lý nhưng chưa được yêu cầu). Để giữ trạng thái, tôi sử dụng hai hàng đợi hai đầu ( deques):

from collections import deque

SplitSeq lớp học chăm sóc vệ sinh:

class SplitSeq:
    def __init__(self, condition, sequence):
        self.cond = condition
        self.goods = deque([])
        self.bads = deque([])
        self.seq = iter(sequence)

Phép thuật xảy ra trong .getNext()phương pháp của nó . Nó gần giống như .next() các trình vòng lặp, nhưng cho phép chỉ định loại phần tử nào chúng ta muốn lần này. Đằng sau bối cảnh, nó không loại bỏ các yếu tố bị từ chối, mà thay vào đó đặt chúng vào một trong hai hàng đợi:

    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)

Người dùng cuối được cho là sử dụng partitionchức năng. Nó nhận một hàm điều kiện và một chuỗi (giống như maphoặc filter) và trả về hai trình tạo. Trình tạo đầu tiên xây dựng một chuỗi các phần tử mà điều kiện giữ, phần thứ hai xây dựng phần tử bổ sung. Trình vòng lặp và trình tạo cho phép lười biếng phân chia các chuỗi thậm chí dài hoặc vô hạn.

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

Tôi đã chọn hàm kiểm tra làm đối số đầu tiên để tạo điều kiện cho ứng dụng một phần trong tương lai (tương tự như cách thức mapfilter có chức năng kiểm tra làm đối số đầu tiên).


15

Về cơ bản, tôi thích cách tiếp cận của Anders vì nó rất chung chung. Đây là phiên bản đặt trình phân loại lên đầu tiên (để khớp cú pháp bộ lọc) và sử dụng defaultdict (giả định được nhập).

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

Tôi sẽ cố gắng chọn ra các tuyên bố từ Zen của Python áp dụng ở đây, nhưng quá nhiều cho một nhận xét. =) Đoạn mã tuyệt vời.
jpmc26

13

Đầu tiên (chỉnh sửa trước OP): Sử dụng bộ:

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]

Điều đó tốt cho cả khả năng đọc (IMHO) và hiệu suất.

Lần thứ hai (chỉnh sửa sau OP):

Tạo danh sách các tiện ích mở rộng tốt của bạn dưới dạng một bộ:

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

và điều đó sẽ tăng hiệu suất. Nếu không, những gì bạn có vẻ tốt với tôi.


4
không phải là giải pháp tốt nhất nếu các danh sách theo thứ tự trước khi chia tách và bạn cần chúng ở lại theo thứ tự đó.
Daniyar

8
Điều đó sẽ không loại bỏ trùng lặp?
mavnn

Tạo một tập hợp là O (n log n). Lặp lại danh sách hai lần là O (n). Giải pháp thiết lập có thể thanh lịch hơn (khi nó đúng ở vị trí đầu tiên) nhưng chắc chắn là chậm hơn khi n tăng.
dash-tom-bang

1
@ dash-tom-bang Lặp lại danh sách là O (n * n). Đó là bởi vì mỗi mục trong danh sách có thể cần phải được so sánh với từng mục trong goodvals.
ChaimG

@ChaimG điểm tốt, mặc dù chúng tôi cũng cần xem xét chi phí của các hoạt động giao nhau và khác biệt (mà tôi không biết rõ nhưng tôi khá chắc chắn rằng chúng cũng là siêu tuyến).
dash-tom-bang

10

itertools.groupby hầu như làm những gì bạn muốn, ngoại trừ nó yêu cầu các mục được sắp xếp để đảm bảo rằng bạn có một phạm vi tiếp giáp duy nhất, vì vậy bạn cần sắp xếp theo khóa của mình trước (nếu không bạn sẽ nhận được nhiều nhóm xen kẽ cho mỗi loại). ví dụ.

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)

cho:

False [('file2.avi', 999L, '.avi')]
True [('file1.jpg', 33L, '.jpg'), ('file3.gif', 123L, '.gif')]

Tương tự như các giải pháp khác, func khóa có thể được xác định để chia thành bất kỳ số lượng nhóm nào bạn muốn.


6
good.append(x) if x in goodvals else bad.append(x)

Câu trả lời ngắn gọn và súc tích này của @dansalmo đã bị chôn vùi trong các bình luận, vì vậy tôi chỉ đăng lại nó ở đây như một câu trả lời để nó có thể nhận được sự nổi bật mà nó xứng đáng, đặc biệt là cho những độc giả mới.

Ví dụ hoàn chỉnh:

good, bad = [], []
for x in my_list:
    good.append(x) if x in goodvals else bad.append(x)

5

Nếu bạn muốn làm theo kiểu FP:

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

Không phải là giải pháp dễ đọc nhất, nhưng ít nhất lặp lại qua danh sách của tôi chỉ một lần.


1
Mặc dù nó chỉ lặp qua danh sách một lần, nhưng hiệu suất không tốt vì danh sách sẽ xuất hiện. Việc thêm vào một danh sách là hoạt động có khả năng tốn kém (ví dụ khi so sánh với deque.append). Trên thực tế, giải pháp này cực kỳ chậm khi so sánh với các giải pháp khác ở đây (21,4 giây trên 100000 số nguyên ngẫu nhiên và kiểm tra giá trị của chúng).
rlat

5

Cá nhân, tôi thích phiên bản bạn đã trích dẫn, giả sử bạn đã có một danh sách goodvalstreo xung quanh. Nếu không, một cái gì đó như:

good = filter(lambda x: is_good(x), mylist)
bad = filter(lambda x: not is_good(x), mylist)

Tất nhiên, điều đó thực sự rất giống với việc sử dụng một cách hiểu danh sách như ban đầu bạn đã làm, nhưng với một chức năng thay vì tra cứu:

good = [x for x in mylist if is_good(x)]
bad  = [x for x in mylist if not is_good(x)]

Nói chung, tôi thấy tính thẩm mỹ của việc hiểu danh sách là rất dễ chịu. Tất nhiên, nếu bạn không thực sự cần phải giữ trật tự và không cần trùng lặp, sử dụng intersectiondifferencecác phương thức trên bộ cũng sẽ hoạt động tốt.


Tất nhiên, filter(lambda x: is_good(x), mylist)có thể rút gọn thànhfilter(is_good, mylist)
robru 7/11/14

việc thêm hàm gọi thêm thực sự nhân đôi (!) thời gian thực hiện, so với mức hiểu danh sách, từ những gì tôi đã thấy trong hồ sơ. hầu như không thể đánh bại một sự hiểu biết danh sách, hầu hết thời gian.
Corley Brigman

4

Tôi nghĩ rằng việc khái quát hóa việc chia một vòng lặp dựa trên các điều kiện N là tiện dụng

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

Ví dụ:

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]

Nếu phần tử có thể đáp ứng nhiều điều kiện, loại bỏ ngắt.


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

Kiểm tra cái này


3

Đôi khi, có vẻ như việc hiểu danh sách không phải là điều tốt nhất để sử dụng!

Tôi đã thực hiện một bài kiểm tra nhỏ dựa trên câu trả lời mà mọi người đưa ra cho chủ đề này, được thử nghiệm trên một danh sách được tạo ngẫu nhiên. Đây là thế hệ của danh sách (có lẽ có một cách tốt hơn để làm, nhưng đó không phải là vấn đề):

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

Và chúng ta đi đây

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

Sử dụng hàm cmpthese , kết quả tốt nhất là câu trả lời 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%  --

Chức năng nhanh hơn với điểm chuẩn cập nhật ở đây .
ChaimG

2

Một giải pháp khác cho vấn đề này. Tôi cần một giải pháp nhanh nhất có thể. Điều đó có nghĩa là chỉ một lần lặp trong danh sách và tốt nhất là O (1) để thêm dữ liệu vào một trong các danh sách kết quả. Điều này rất giống với giải pháp được cung cấp bởi sastanin , ngoại trừ ngắn hơn nhiều:

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

Sau đó, bạn có thể sử dụng hàm theo cách sau:

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

Nếu bạn không bằng lòng với kết quả dequeđối tượng, bạn có thể dễ dàng chuyển nó sang list, set, bất cứ điều gì bạn thích (ví dụ list(lower)). Việc chuyển đổi nhanh hơn nhiều, đó là việc xây dựng danh sách trực tiếp.

Phương pháp này giữ trật tự của các mục, cũng như bất kỳ bản sao.


2

Ví dụ: chia danh sách theo số chẵn và lẻ

arr = range(20)
even, odd = reduce(lambda res, next: res[next % 2].append(next) or res, arr, ([], []))

Hoặc nói chung:

def split(predicate, iterable):
    return reduce(lambda res, e: res[predicate(e)].append(e) or res, iterable, ([], []))

Ưu điểm:

  • Cách rõ ràng ngắn nhất
  • Vị ngữ chỉ áp dụng một lần cho mỗi yếu tố

Nhược điểm

  • Yêu cầu kiến ​​thức về mô hình lập trình chức năng

2

Lấy cảm hứng từ câu trả lời tuyệt vời (nhưng ngắn gọn!) Của @ gnibbler's , chúng ta có thể áp dụng phương pháp đó để ánh xạ tới nhiều phân vùng:

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)] += [x]

    return results

Sau đó splittercó thể được sử dụng như sau:

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

Điều này hoạt động cho hơn hai phân vùng với ánh xạ phức tạp hơn (và trên cả các trình vòng lặp nữa):

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

Hoặc sử dụng từ điển để lập bản đồ:

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

... chỉ cần lưu ý rằng điều này về cơ bản giống như @ alan-isaac đã trả lời.
Josh Bode

2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

nối lại trả về Không, vì vậy nó hoạt động.


1

Đối với nước hoa, hãy thử itertools.

Các itertools mô-đun chuẩn hóa một bộ cốt lõi của nhanh, bộ nhớ hiệu quả các công cụ hữu ích của bản thân hoặc kết hợp. Cùng nhau, chúng tạo thành một đại số lặp đi lặp lại, cho phép xây dựng các công cụ chuyên dụng ngắn gọn và hiệu quả trong Python thuần túy.

Xem itertools.ifilter hoặc imap.

itertools.ifilter (vị ngữ, lặp lại)

Tạo một trình vòng lặp lọc các phần tử từ iterable chỉ trả về những phần tử có vị ngữ là True


IFilter / imap (và máy phát điện nói chung) là khá chậm ... nói chung, trong hồ sơ của tôi, nếu bạn có một danh sách hiểu biết như [x for x in a if x > 50000]trên một mảng đơn giản của 100000 số nguyên (thông qua random.shuffle), filter(lambda x: x> 50000, a)sẽ mất 2x càng lâu, ifilter(lambda x: x> 50000, a); list(result)mất dài khoảng 2,3 lần. Lạ nhưng có thật.
Corley Brigman

1

Đôi khi bạn sẽ không cần nửa kia của danh sách. Ví dụ:

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

1

Đây là cách nhanh nhất.

Nó sử dụng if else, (như câu trả lời của dbr) nhưng tạo ra một tập hợp đầu tiên. Một tập hợp làm giảm số lượng các hoạt động từ O (m * n) xuống O (log m) + O (n), dẫn đến tăng 45% + tốc độ.

good_list_set = set(good_list)  # 45% 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)

Ngắn hơn một chút:

good_list_set = set(good_list)  # 45% 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)

Kết quả điểm chuẩn:

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

Mã điểm chuẩn đầy đủ cho Python 3.7 (được sửa đổi từ 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(" "))

0

Nếu bạn khăng khăng thông minh, bạn có thể sử dụng giải pháp của Winden và chỉ cần một chút thông minh giả mạo:

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

3
"D hoặc {}" là một chút nguy hiểm. Nếu một lệnh trống được truyền vào, nó sẽ không bị thay đổi tại chỗ.
Brian

Đúng, nhưng nó được trả về, vì vậy ... Trên thực tế, đây là ví dụ hoàn hảo về lý do tại sao bạn không muốn thêm thông minh hơn vào mã của mình. :-P
Anders Eurenius

0

Đã có một vài giải pháp ở đây, nhưng một cách khác để làm điều đó sẽ là -

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

Lặp lại danh sách chỉ một lần, và trông có vẻ nhiều pythonic hơn và do đó tôi có thể đọc được.

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

0

Tôi sẽ thực hiện một cách tiếp cận 2 lượt, tách biệt đánh giá vị ngữ khỏi việc lọc danh sách:

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

Điều tuyệt vời về điều này, thông minh về hiệu suất (ngoài việc predchỉ đánh giá một lần cho mỗi thành viên iterable), là nó di chuyển rất nhiều logic ra khỏi trình thông dịch và thành mã lặp và ánh xạ được tối ưu hóa cao. Điều này có thể tăng tốc độ lặp qua các lần lặp dài, như được mô tả trong câu trả lời này .

Biểu cảm-khôn ngoan, nó tận dụng các thành ngữ biểu cảm như hiểu và lập bản đồ.


0

giải pháp

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

kiểm tra

[even, odd] = separate(
    lambda x: bool(x % 2),
    [1, 2, 3, 4, 5])
print(list(even) == [2, 4])
print(list(odd) == [1, 3, 5])

0

Nếu bạn không phiền khi sử dụng một thư viện bên ngoài thì có hai tôi biết rằng thực hiện thao tác này một cách tự nhiên:

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

0

Không chắc đây có phải là một cách tiếp cận tốt hay không nhưng nó cũng có thể được thực hiện theo cách này

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

Nếu danh sách được tạo thành từ các nhóm và dấu phân cách không liên tục, bạn có thể sử dụng:

def split(items, p):
    groups = [[]]
    for i in items:
        if p(i):
            groups.append([])
        groups[-1].append(i)
    return groups

Sử dụng:

split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

0
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f not in images]

Đẹp khi điều kiện dài hơn, chẳng hạn như trong ví dụ của bạn. Người đọc không phải tìm ra điều kiện tiêu cực và liệu nó có nắm bắt được tất cả các trường hợp khác hay không.


0

Còn một câu trả lời khác, ngắn nhưng "xấu xa" (đối với tác dụng phụ của danh sách).

digits = list(range(10))
odd = [x.pop(i) for i, x in enumerate(digits) if x % 2]

>>> odd
[1, 3, 5, 7, 9]

>>> digits
[0, 2, 4, 6, 8]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.