Python: แยกรายการตามเงื่อนไขหรือไม่


272

อะไรคือวิธีที่ดีที่สุดทั้งในเชิงสุนทรียศาสตร์และจากมุมมองของประสิทธิภาพในการแบ่งรายการออกเป็นหลายรายการตามเงื่อนไข เทียบเท่ากับ:

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]

5
ลงจอดที่นี่เพื่อหาวิธีที่จะมีเงื่อนไขในคำสั่ง set builder คำถามของคุณตอบคำถามของฉัน :)
Anuvrat Parashar

5
splitเป็นคำอธิบายที่โชคร้ายของการดำเนินการนี้เนื่องจากมีความหมายเฉพาะเกี่ยวกับสตริง Python ฉันคิดว่าการแบ่งเป็นคำที่แม่นยำยิ่งขึ้น (หรืออย่างน้อยก็โอเวอร์โหลดในบริบทของ Python iterables) เพื่ออธิบายการดำเนินการนี้ ฉันลงจอดที่นี่เพื่อค้นหารายการที่เทียบเท่าstr.split()เพื่อแยกรายการออกเป็นชุดสะสมที่เรียงลำดับแล้วของรายการย่อยต่อเนื่อง เช่นsplit([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])ตรงข้ามกับการแบ่งองค์ประกอบของรายการตามหมวดหมู่
Stew

การอภิปรายในหัวข้อเดียวกันในรายการหลาม
Xiong Chiamiov

IMAGE_TYPES ควรจะตั้งค่าแทน tuple IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')ที่: n (1) แทนที่จะเป็น n (o / 2) โดยที่จริงแล้วไม่มีความแตกต่างในการอ่าน
ChaimG

คำตอบ:


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

อีกครั้งนี่เป็นเรื่องปกติ!

อาจมีการปรับปรุงประสิทธิภาพเล็กน้อยโดยใช้ชุด แต่มันเป็นความแตกต่างเล็กน้อยและฉันพบว่าการอ่านรายการเข้าใจง่ายยิ่งขึ้นและคุณไม่ต้องกังวลเกี่ยวกับลำดับที่ถูกทำให้ซ้ำซ้อนถูกลบออกไปเรื่อย ๆ

ในความเป็นจริงฉันอาจไปอีกขั้นหนึ่ง "ย้อนกลับ" และใช้การวนรอบแบบง่าย:

images, anims = [], []

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

list-comprehension หรือใช้set()ไม่เป็นไรจนกว่าคุณจะต้องเพิ่มเช็คอื่น ๆ หรือตรรกะอื่น ๆ - ว่าคุณต้องการลบ 0-byte jpeg ทั้งหมดของคุณเพียงแค่เพิ่ม ..

if f[1] == 0:
    continue

44
ไม่มีวิธีการเข้าใจรายการโดยไม่ต้องวนซ้ำรายการสองครั้งหรือไม่
balki

35
ปัญหาคือว่านี่เป็นการละเมิดหลักการแห้ง มันคงจะดีถ้ามีวิธีที่ดีกว่าในการทำเช่นนี้
พลวง

21
เมื่อความกระหายในการเขียนโปรแกรมฟังก์ชั่น (Haskell) หรือรูปแบบการทำงาน (LINQ) เพิ่มขึ้นเราเริ่มได้กลิ่น Python สำหรับอายุของมัน[x for x in blah if ...]- verbose lambdaเป็นเงอะงะและ จำกัด ... รู้สึกเหมือนขับรถที่ยอดเยี่ยมที่สุดตั้งแต่ปี 1995 ไม่เหมือนเดิมแล้ว
Tomasz Gandor

6
@TomaszGandor FTR, Haskell มีอายุมากกว่า Python (และได้รับอิทธิพลจากการออกแบบ) ฉันคิดว่าไวยากรณ์สำหรับรายการความเข้าใจและ lambdas ถูกเก็บไว้โดยเจตนาเล็กน้อยในด้าน verbose บางทีเพื่อกีดกันการใช้มากเกินไป ซึ่งแน่นอนว่ามีความเสี่ยง ... เท่าที่ฉันชอบ Haskell ฉันสามารถดูได้ว่าทำไมหลาย ๆ คนพบว่างูใหญ่อ่านง่ายกว่า
leftaroundabout

4
ง่ายสำหรับวนเป็นวิธีที่ดีที่สุดในการทำเช่นนี้ ... วงเดียวชัดเจนและอ่านง่าย
Anentropic

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

14
นั่นเป็นความคิดสร้างสรรค์อย่างไม่น่าเชื่อ! ฉันใช้เวลาสักพักกว่าจะเข้าใจว่าเกิดอะไรขึ้น ฉันต้องการทราบว่าคนอื่นคิดว่านี่เป็นโค้ดที่อ่านได้หรือไม่
jgpaiva

171
good.append(x) if x in goodvals else bad.append(x)สามารถอ่านได้มากขึ้น
dansalmo

21
@dansalmo โดยเฉพาะอย่างยิ่งเนื่องจากคุณสามารถทำให้เป็นหนึ่งรอบกับรอบและถ้าคุณต้องการผนวกสิ่งที่ซับซ้อนกว่าxคุณสามารถทำให้เป็นหนึ่งappendเดียว:for x in mylist: (good if isgood(x) else bad).append(x)
yo '13

2
@MLister ในกรณีนี้คุณควรรวมการค้นหาแอตทริบิวต์ไว้ด้วย(bad.append, good.append)
John La Rooy

11
รูปแบบที่สั้นกว่าเล็กน้อย:(good if x in goodvals else bad).append(x)
Pi Delport

104

นี่คือวิธีตัววนซ้ำที่ขี้เกียจ:

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

คุณอาจพูดถูกว่านี่เป็นการละเมิดหลักการของ YAGNI มันขึ้นอยู่กับข้อสันนิษฐานว่าจำนวนรายการต่าง ๆ ที่สิ่งต่าง ๆ สามารถแบ่งพาร์ติชันเข้าไปจะเพิ่มขึ้นในอนาคต
มด Aasma

17
อาจเป็นรหัสจำนวนมาก แต่หาก[ x for x in my_list if ExpensiveOperation(x) ]ใช้เวลานานในการรันแน่นอนคุณไม่ต้องการทำสองครั้ง!
dash-tom-bang

1
+1 สำหรับการเสนอหลากหลายรูปแบบรวมถึงตัวทำซ้ำและโซลูชัน "in X" ที่เฉพาะเจาะจง OP "ในช่วงเวลาที่ดี" อาจมีขนาดเล็ก แต่การแทนที่ด้วยพจนานุกรมที่มีขนาดใหญ่มากหรือภาคแสดงราคาแพงอาจมีราคาแพง นอกจากนี้ยังช่วยลดความจำเป็นในการเขียนรายการความเข้าใจสองครั้งในทุกที่ที่ต้องการซึ่งจะช่วยลดโอกาสในการแนะนำข้อผิดพลาดที่พิมพ์ผิด / ผู้ใช้ ทางออกที่ดี ขอบคุณ!
cod3monk3y

3
โปรดทราบว่าจะteeเก็บค่าทั้งหมดระหว่างตัววนซ้ำที่ส่งคืนดังนั้นมันจะไม่บันทึกหน่วยความจำจริงๆถ้าคุณวนซ้ำตัวสร้างทั้งหมดหนึ่งตัวและตัวอื่น ๆ
John La Rooy

25

ปัญหาเกี่ยวกับแนวทางแก้ไขทั้งหมดที่เสนอคือมันจะสแกนและใช้งานฟังก์ชั่นการกรองสองครั้ง ฉันจะทำฟังก์ชั่นเล็ก ๆ ง่ายๆเช่นนี้

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

ด้วยวิธีนี้คุณจะไม่ประมวลผลใด ๆ สองครั้งและยังไม่ได้ทำซ้ำรหัส


ฉันเห็นด้วย. ฉันกำลังมองหาวิธี "สง่างาม" (เช่นที่นี่หมายถึงวิธีที่สั้นและในตัว / โดยปริยาย) ในการทำเช่นนี้โดยไม่ต้องสแกนรายการสองครั้ง แต่สิ่งนี้ดูเหมือนว่า (ไม่มีการทำโปรไฟล์) แน่นอนว่ามันจะมีความสำคัญสำหรับข้อมูลจำนวนมากเท่านั้น
Matthew Flaschen

IMHO ถ้าคุณรู้วิธีการทำโดยใช้ cpu น้อยลง (และใช้พลังงานน้อยลง) ไม่มีเหตุผลที่จะไม่ใช้มัน
Winden

2
@winden ... นำ Python ของฉันไปที่ C. ;)
Elliot Cameron

19

ฉันใช้มัน ฉันเสนอขี้เกียจเที่ยวเดียวpartitionฟังก์ชันซึ่งเก็บรักษาลำดับสัมพัทธ์ไว้ในเอาต์พุตอนุกรม

1. ข้อกำหนด

ฉันคิดว่าความต้องการคือ:

  • รักษาลำดับความสัมพันธ์ขององค์ประกอบ (ด้วยเหตุนี้ไม่มีชุดและพจนานุกรม)
  • ประเมินเงื่อนไขเพียงครั้งเดียวสำหรับทุกองค์ประกอบ (จึงไม่ได้ใช้ ( i) filterหรือgroupby)
  • อนุญาตสำหรับการบริโภคที่ขี้เกียจของทั้งสองลำดับ (ถ้าเราสามารถที่จะ precompute พวกเขาแล้วการใช้งานไร้เดียงสาก็น่าจะเป็นที่ยอมรับเช่นกัน)

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ฟังก์ชั่น มันใช้ฟังก์ชั่นเงื่อนไขและลำดับ (เหมือนmapหรือfilter) และส่งคืนกำเนิดสองตัว เครื่องกำเนิดไฟฟ้าแรกสร้างองค์ประกอบขององค์ประกอบที่เงื่อนไขถือเป็นเครื่องกำเนิดไฟฟ้าที่สองสร้างองค์ประกอบประกอบที่สมบูรณ์ Iterators และเครื่องกำเนิดไฟฟ้าช่วยให้สามารถแยกลำดับของลำดับที่ยาวหรือไม่สิ้นสุดได้อย่างขี้เกียจ

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 มีฟังก์ชั่นทดสอบเป็นอาร์กิวเมนต์แรก)


15

โดยทั่วไปฉันชอบวิธีของ Anders เพราะเป็นเรื่องทั่วไปมาก ต่อไปนี้เป็นรุ่นที่ทำให้ตัวแบ่งหมวดหมู่เป็นอันดับแรก (เพื่อจับคู่ไวยากรณ์ตัวกรอง) และใช้ 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

ฉันจะลองเลือกข้อความจากZen of Pythonที่ใช้ที่นี่ แต่มันมีความคิดเห็นมากเกินไป =) โค้ดที่ยอดเยี่ยม
jpmc26

13

ครั้งแรก (ก่อนแก้ไข 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'])

และนั่นจะเพิ่มประสิทธิภาพ มิฉะนั้นสิ่งที่คุณดูดีสำหรับฉัน


4
ไม่ใช่วิธีแก้ปัญหาที่ดีที่สุดหากมีรายการอยู่ในลำดับก่อนที่จะแยกและคุณต้องการให้อยู่ในลำดับนั้น
Daniyar

8
จะไม่ลบรายการที่ซ้ำกันหรือไม่
mavnn

การสร้างชุดคือ O (n log n) การวนซ้ำรายการสองครั้งคือ O (n) โซลูชันที่ตั้งค่าอาจดูสง่างามกว่า (เมื่อถูกต้องตั้งแต่แรก) แต่จะช้ากว่าเดิมอย่างแน่นอนเมื่อ n เพิ่มขึ้น
dash-tom-bang

1
@ dash-tom-bang การย้ำรายการคือ O (n * n) goodvalsนั่นเป็นเพราะรายการในรายการแต่ละคนอาจจะต้องมีการเปรียบเทียบกับแต่ละรายการใน
ChaimG

@ จุด ChaimG ที่ดีแม้ว่าเรายังต้องพิจารณาค่าใช้จ่ายของการแยกและการดำเนินงานที่แตกต่างกัน (ซึ่งฉันไม่ทราบว่ามือ แต่ฉันค่อนข้างแน่ใจว่าพวกเขายอดเยี่ยมเช่นกัน)
dash-tom-bang

10

itertools.group โดยเกือบจะทำในสิ่งที่คุณต้องการยกเว้นมันจะต้องเรียงลำดับรายการเพื่อให้แน่ใจว่าคุณได้รับช่วงที่ต่อเนื่องกันเพียงครั้งเดียวดังนั้นคุณต้องเรียงลำดับตามคีย์ของคุณก่อน (ไม่เช่นนั้นคุณจะได้รับกลุ่มแทรกหลายกลุ่ม เช่น.

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

เช่นเดียวกับโซลูชันอื่น ๆ สามารถกำหนดคีย์ func เพื่อแบ่งออกเป็นกลุ่มจำนวนเท่าใดก็ได้ที่คุณต้องการ


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

5

หากคุณต้องการให้เป็นสไตล์ FP:

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

ไม่ใช่โซลูชันที่อ่านง่ายที่สุด แต่อย่างน้อย iterates ผ่านรายการของฉันเพียงครั้งเดียว


1
แม้ว่าจะวนซ้ำผ่านรายการเพียงครั้งเดียว แต่ประสิทธิภาพไม่ดีเท่าที่ควรเนื่องจากรายการต่อท้าย การต่อท้ายรายการอาจเป็นการดำเนินการที่มีราคาแพง (เมื่อเปรียบเทียบกับ deque.append เป็นต้น) ที่จริงแล้วโซลูชันนี้ช้ามากเมื่อเทียบกับโซลูชันอื่น ๆ ที่นี่ (21.4s กับจำนวนเต็ม 100000 สุ่มและทดสอบค่าของพวกเขา)
rlat

5

โดยส่วนตัวแล้วฉันชอบเวอร์ชั่นที่คุณอ้างถึงโดยสมมติว่าคุณมีรายการที่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วิธีการในชุดก็ใช้ได้ดีเช่นกัน


แน่นอนfilter(lambda x: is_good(x), mylist)สามารถลดได้ถึงfilter(is_good, mylist)
robru

การเพิ่มการเรียกฟังก์ชั่นพิเศษจริง ๆ เพิ่มเป็นสองเท่า (!) เวลาดำเนินการเมื่อเทียบกับความเข้าใจในรายการจากสิ่งที่ฉันได้เห็นในโปรไฟล์ เป็นการยากที่จะเอาชนะความเข้าใจในรายการส่วนใหญ่
Corley Brigman

4

ฉันคิดว่าลักษณะทั่วไปของการแยก iterable ตามเงื่อนไข N เป็นประโยชน์

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

ตัวอย่างเช่น

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]

หากองค์ประกอบอาจตอบสนองหลายเงื่อนไขให้ลบตัวแบ่ง



3

บางครั้งดูเหมือนว่ารายการความเข้าใจไม่ใช่สิ่งที่ดีที่สุดที่จะใช้!

ฉันทำแบบทดสอบเล็กน้อยตามคำตอบที่คนให้กับหัวข้อนี้ทดสอบในรายการที่สร้างแบบสุ่ม นี่คือการสร้างรายการ (อาจเป็นวิธีที่ดีกว่าที่จะทำ แต่ไม่ใช่ประเด็น):

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

และที่นี่เราไป

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

การใช้ฟังก์ชั่นcmptheseผลลัพธ์ที่ดีที่สุดคือคำตอบ 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%  --

ฟังก์ชั่นได้เร็วขึ้นด้วยมาตรฐานการปรับปรุงที่นี่
ChaimG

2

อีกวิธีหนึ่งในการแก้ไขปัญหานี้ ฉันต้องการวิธีแก้ปัญหาที่เร็วที่สุด นั่นหมายถึงการวนซ้ำเพียงครั้งเดียวในรายการและโดยเฉพาะอย่างยิ่ง O (1) สำหรับการเพิ่มข้อมูลลงในรายการผลลัพธ์อย่างใดอย่างหนึ่ง สิ่งนี้คล้ายกับโซลูชันของsastaninยกเว้นสั้นกว่ามาก:

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

จากนั้นคุณสามารถใช้ฟังก์ชันในวิธีต่อไปนี้:

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

หากคุณไม่ได้ดีกับผลdequeวัตถุคุณสามารถแปลงเป็นlist, setสิ่งที่คุณชอบ (ตัวอย่างlist(lower)) การแปลงเร็วกว่ามากการสร้างรายการโดยตรง

วิธีการนี้จะเก็บลำดับของรายการรวมถึงรายการที่ซ้ำกัน


2

ตัวอย่างเช่นแยกรายการด้วยเลขคู่และคู่

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

หรือโดยทั่วไป:

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

ข้อดี:

  • วิธีที่เป็นไปได้สั้นที่สุด
  • เพรดิเคตใช้เพียงครั้งเดียวสำหรับแต่ละองค์ประกอบ

ข้อเสีย

  • ต้องมีความรู้เกี่ยวกับกระบวนทัศน์การเขียนโปรแกรมการทำงาน

2

แรงบันดาลใจจากคำตอบที่ยอดเยี่ยม (แต่สั้น!)ของ @ gnibbler เราสามารถนำวิธีการนั้นไปใช้กับแผนที่ในหลาย ๆ พาร์ติชัน:

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

จากนั้นsplitterสามารถใช้ดังนี้:

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

สิ่งนี้ใช้ได้กับพาร์ติชันมากกว่าสองพาร์ติชันที่มีการแมปที่ซับซ้อนมากขึ้น (และตัววนซ้ำด้วย):

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

หรือใช้พจนานุกรมเพื่อทำแผนที่:

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

... เพิ่งสังเกตเห็นว่าสิ่งนี้เป็นแบบเดียวกับ @ alan-isaac ที่ได้ตอบไปแล้ว
Josh Bode

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

ผนวกส่งคืนไม่มีดังนั้นจึงใช้งานได้


1

สำหรับ perfomance itertoolsลอง

itertoolsโมดูลมาตรฐานแกนชุดได้อย่างรวดเร็วและมีประสิทธิภาพหน่วยความจำเครื่องมือที่มีประโยชน์ด้วยตัวเองหรือในการรวมกัน พวกเขารวมกันเป็น“ พีชคณิตตัววนซ้ำ” ทำให้สามารถสร้างเครื่องมือพิเศษได้อย่างกระชับและมีประสิทธิภาพใน Python บริสุทธิ์

ดูitertools.ifilterหรือ imap

itertools.ifilter (เพรดิเคต iterable)

สร้างตัววนซ้ำที่กรององค์ประกอบจากการวนซ้ำที่คืนค่าเฉพาะส่วนที่เป็นจริงเท่านั้น


IFilter / IMAP (และเครื่องกำเนิดไฟฟ้าทั่วไป) มีความสวยช้า ... โดยทั่วไปในโปรไฟล์ของฉันถ้าคุณใช้ความเข้าใจรายการเช่น[x for x in a if x > 50000]บนอาร์เรย์ที่เรียบง่ายของจำนวนเต็ม 100000 (ผ่าน random.shuffle) filter(lambda x: x> 50000, a)จะใช้เวลา 2 เท่าเป็นเวลานาน, ifilter(lambda x: x> 50000, a); list(result)ใช้เวลา ประมาณ 2.3 เท่า แปลก แต่จริง
Corley Brigman

1

บางครั้งคุณอาจไม่ต้องการอีกครึ่งหนึ่งของรายการ ตัวอย่างเช่น:

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

นี่เป็นวิธีที่เร็วที่สุด

มันใช้if else(เช่นคำตอบของ dbr) แต่สร้างชุดแรก ชุดลดจำนวนการทำงานจาก O (m * n) เป็น O (log m) + O (n) ส่งผลให้ความเร็วเพิ่มขึ้น 45% +

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)

สั้นกว่าเล็กน้อย:

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)

ผลการทดสอบเกณฑ์มาตรฐาน:

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

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 or {}" ค่อนข้างอันตราย หาก dict ว่างเปล่าได้รับการส่งผ่านมันจะไม่ถูกเปลี่ยนแปลง
Brian

จริง แต่มันกลับมาดังนั้น ... จริง ๆ แล้วนี่เป็นตัวอย่างที่สมบูรณ์แบบของสาเหตุที่คุณไม่ต้องการเพิ่มรหัสของคุณให้ฉลาดขึ้น :-P
Anders Eurenius

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

วนซ้ำในรายการเพียงครั้งเดียวและดูไพทอนมากขึ้นและสามารถอ่านได้สำหรับฉัน

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

ฉันจะใช้วิธี 2-pass แยกการประเมินของภาคแสดงจากการกรองรายการ:

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

สิ่งที่ดีเกี่ยวกับเรื่องนี้ประสิทธิภาพที่ชาญฉลาด (นอกเหนือจากการประเมินpredเพียงครั้งเดียวในสมาชิกแต่ละคนiterable) คือมันย้ายตรรกะจำนวนมากออกจากล่าม วิธีนี้สามารถเพิ่มความเร็วในการวนซ้ำได้นานกว่า iterables ดังที่อธิบายไว้ในคำตอบนี้

ฉลาดหลักแหลมมันใช้ประโยชน์จากสำนวนที่แสดงออกเช่นความเข้าใจและการทำแผนที่


0

สารละลาย

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

0

ถ้าคุณไม่รังเกียจที่จะใช้ไลบรารี่ภายนอกฉันรู้ว่าสองอย่างนี้ใช้การดำเนินการนี้:

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

ไม่แน่ใจว่านี่เป็นวิธีการที่ดี แต่ก็สามารถทำได้ด้วยวิธีนี้เช่นกัน

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

หากรายการนี้ทำจากกลุ่มและตัวคั่นเป็นระยะ ๆ คุณสามารถใช้:

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

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]

ดีเมื่อเงื่อนไขยาวขึ้นเช่นในตัวอย่างของคุณ ผู้อ่านไม่จำเป็นต้องเข้าใจถึงเงื่อนไขเชิงลบและไม่ว่าจะเป็นกรณีอื่น ๆ ทั้งหมดหรือไม่


0

อีกคำตอบสั้น ๆ แต่ "ชั่วร้าย" (สำหรับผลข้างเคียงของรายการความเข้าใจ)

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]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.