การแปลงรายการเป็นชุดจะเปลี่ยนลำดับองค์ประกอบ


120

เมื่อเร็ว ๆ นี้ฉันสังเกตเห็นว่าเมื่อฉันแปลง a listไปsetเป็นลำดับขององค์ประกอบมีการเปลี่ยนแปลงและเรียงลำดับตามอักขระ

ลองพิจารณาตัวอย่างนี้:

x=[1,2,20,6,210]
print x 
# [1, 2, 20, 6, 210] # the order is same as initial order

set(x)
# set([1, 2, 20, 210, 6]) # in the set(x) output order is sorted

คำถามของฉันคือ -

  1. เหตุใดจึงเกิดขึ้น
  2. ฉันจะตั้งค่าการดำเนินการ (โดยเฉพาะ Set Difference) โดยไม่สูญเสียคำสั่งเริ่มต้นได้อย่างไร

8
ทำไมคุณไม่ต้องการสูญเสียคำสั่งเริ่มต้นโดยเฉพาะอย่างยิ่งหากคุณกำลังดำเนินการตั้งค่าอยู่ "order" เป็นแนวคิดที่ไม่มีความหมายสำหรับเซตไม่ใช่แค่ใน Python แต่ในคณิตศาสตร์
Karl Knechtel

131
@KarlKnechtel - ใช่ "คำสั่งเป็นแนวคิดที่ไม่มีความหมายสำหรับเซต ... ในคณิตศาสตร์" แต่ฉันมีปัญหาในโลกแห่งความจริง :)
d.putto

เมื่อวันที่ CPython unique = list(dict.fromkeys([1, 2, 1]).keys())3.6 ขึ้นไป สิ่งนี้ได้ผลเนื่องจากdictรักษาลำดับการแทรกในขณะนี้
บอริส

คำตอบ:


107
  1. A setเป็นโครงสร้างข้อมูลที่ไม่เรียงลำดับดังนั้นจึงไม่เก็บรักษาลำดับการแทรก

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

    >>> a = [1, 2, 20, 6, 210]
    >>> b = set([6, 20, 1])
    >>> [x for x in a if x not in b]
    [2, 210]

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

    >>> a = dict.fromkeys([1, 2, 20, 6, 210])
    >>> b = dict.fromkeys([6, 20, 1])
    >>> dict.fromkeys(x for x in a if x not in b)
    {2: None, 210: None}

    bไม่จำเป็นต้องสั่งซื้อที่นี่คุณสามารถใช้ a ได้setเช่นกัน โปรดทราบว่าa.keys() - b.keys()จะคืนค่าความแตกต่างที่ตั้งไว้เป็น a setดังนั้นจะไม่รักษาลำดับการแทรก

    ใน Python เวอร์ชันเก่าคุณสามารถใช้collections.OrderedDictแทน:

    >>> a = collections.OrderedDict.fromkeys([1, 2, 20, 6, 210])
    >>> b = collections.OrderedDict.fromkeys([6, 20, 1])
    >>> collections.OrderedDict.fromkeys(x for x in a if x not in b)
    OrderedDict([(2, None), (210, None)])

3
ไม่มีวัตถุใดที่มีราคา 16 ไบต์ หากมีเพียงชุดคำสั่งเริ่มต้น () :(
Sean

2
@ ฌอนไม่พวกเขาทำไม่ได้ Noneเป็นภาษาที่รับประกันซิงเกิลตัน ใน CPython ต้นทุนจริงเป็นเพียงตัวชี้ (แม้ว่าจะมีค่าใช้จ่ายนั้นอยู่เสมอ แต่สำหรับคำสั่งคุณสามารถพิจารณาNoneและ singletons อื่น ๆ หรือการอ้างอิงที่ใช้ร่วมกัน "ฟรี" ได้) ดังนั้นคำเครื่องอาจมีขนาด 8 ไบต์ในคอมพิวเตอร์สมัยใหม่ . แต่ใช่มันไม่ได้มีประสิทธิภาพในการใช้พื้นที่อย่างที่ตั้งไว้
juanpa.arrivillaga

2
ใน CPython 3.6+ คุณสามารถทำได้dict.fromkeys([1, 2, 1]).keys()เพราะปกติdicts รักษาคำสั่งเช่นกัน
บอริส

@Boris นี่เป็นเพียงส่วนหนึ่งของข้อกำหนดภาษาที่เริ่มจาก Python 3.7 แม้ว่าการใช้งาน CPython จะเก็บรักษาลำดับการแทรกในเวอร์ชัน 3.6 ไว้แล้ว แต่ถือว่าเป็นรายละเอียดการใช้งานซึ่งอาจไม่มีการใช้งาน Python อื่น ๆ
Sven Marnach

@Sven ฉันพูด CPython ฉันโพสต์สิ่งนี้ทุกที่ฉันเบื่อที่จะเขียน "CPython 3.6 หรือการใช้งานอื่น ๆ ที่เริ่มต้นด้วย Python 3.7" ไม่สำคัญหรอกทุกคนกำลังใช้ CPython
Boris

53

ใน Python 3.6 set()ตอนนี้ควรรักษาคำสั่งไว้ แต่มีวิธีอื่นสำหรับ Python 2 และ 3:

>>> x = [1, 2, 20, 6, 210]
>>> sorted(set(x), key=x.index)
[1, 2, 20, 6, 210]

8
หมายเหตุสองประการเกี่ยวกับการเก็บรักษาคำสั่งซื้อ: เฉพาะใน Python 3.6 เท่านั้นและถึงอย่างนั้นก็ถือว่าเป็นรายละเอียดการนำไปใช้งานดังนั้นอย่าพึ่งพามัน นอกเหนือจากนั้นโค้ดของคุณยังไม่มีประสิทธิภาพมากเนื่องจากทุกครั้งที่x.indexมีการเรียกจะทำการค้นหาเชิงเส้น หากคุณสบายดีกับความซับซ้อนกำลังสองก็ไม่มีเหตุผลที่จะใช้ a setตั้งแต่แรก
Thijs van Dien

27
@ThijsvanDien นี่ผิดset()ไม่ได้เรียงลำดับใน Python 3.6 ไม่ใช่แม้แต่รายละเอียดการใช้งานคุณกำลังคิดถึงdicts
Chris_Rands

8
@ThijsvanDien ไม่เรียงลำดับแม้ว่าบางครั้งจะปรากฏเช่นนั้นเพราะintมักจะแฮชกับตัวเองstackoverflow.com/questions/45581901/…
Chris_Rands

3
ลองx=[1,2,-1,20,6,210]ทำให้เป็นชุด คุณจะเห็นว่ามันไม่ได้เรียงลำดับเลยทดสอบใน Python 3.6
กาเบรียลชู

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

20

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



9

ในวิชาคณิตศาสตร์มีเซตและเซตตามลำดับ (osets)

  • ชุด : คอนเทนเนอร์ที่ไม่ได้เรียงลำดับขององค์ประกอบที่ไม่ซ้ำกัน (ใช้งาน)
  • oset : คอนเทนเนอร์ที่สั่งซื้อขององค์ประกอบเฉพาะ (NotImplemented)

ใน Python จะใช้เฉพาะชุดเท่านั้น เราสามารถจำลองระบบปฏิบัติการด้วยปุ่ม dict ปกติ ( 3.7+ )

ป.ร. ให้ไว้

a = [1, 2, 20, 6, 210, 2, 1]
b = {2, 6}

รหัส

oset = dict.fromkeys(a).keys()
# dict_keys([1, 2, 20, 6, 210])

การสาธิต

การจำลองจะถูกลบออกลำดับการแทรกจะถูกเก็บรักษาไว้

list(oset)
# [1, 2, 20, 6, 210]

ตั้งค่าการดำเนินการที่เหมือนกันบนปุ่ม dict

oset - b
# {1, 20, 210}

oset | b
# {1, 2, 5, 6, 20, 210}

oset & b
# {2, 6}

oset ^ b
# {1, 5, 20, 210}

รายละเอียด

หมายเหตุ: โครงสร้างที่ไม่เรียงลำดับไม่ได้กีดกันองค์ประกอบที่เรียงลำดับ แต่ไม่รับประกันความเป็นระเบียบเรียบร้อย ตัวอย่าง:

assert {1, 2, 3} == {2, 3, 1}                    # sets (order is ignored)

assert [1, 2, 3] != [2, 3, 1]                    # lists (order is guaranteed)

อาจมีความยินดีที่พบว่าlistและmultiset (mset) เป็นโครงสร้างข้อมูลทางคณิตศาสตร์ที่น่าสนใจอีกสองแบบ:

  • รายการ : คอนเทนเนอร์ขององค์ประกอบที่สั่งซื้อซึ่งอนุญาตให้จำลองแบบ (Implemented)
  • mset : คอนเทนเนอร์ที่ไม่เรียงลำดับขององค์ประกอบที่อนุญาตให้จำลองแบบ (NotImplemented) *

สรุป

Container | Ordered | Unique | Implemented
----------|---------|--------|------------
set       |    n    |    y   |     y
oset      |    y    |    y   |     n
list      |    y    |    n   |     y
mset      |    n    |    n   |     n*  

* ชุดข้อมูลหลายชุดสามารถจำลองทางอ้อมด้วยcollections.Counter()การทำแผนที่แบบตามคำสั่งของการคูณ (จำนวนนับ)


4

ดังที่แสดงในคำตอบอื่น ๆ ชุดคือโครงสร้างข้อมูล (และแนวคิดทางคณิตศาสตร์) ที่ไม่รักษาลำดับองค์ประกอบ -

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

# save the element order in a dict:
x_dict = dict(x,y for y, x in enumerate(my_list) )
x_set = set(my_list)
#perform desired set operations
...
#retrieve ordered list from the set:
new_list = [None] * len(new_set)
for element in new_set:
   new_list[x_dict[element]] = element

1

จากคำตอบของ Sven ฉันพบว่าการใช้คอลเลกชัน OrderDict เช่นนั้นช่วยให้ฉันทำสิ่งที่คุณต้องการให้สำเร็จและอนุญาตให้ฉันเพิ่มรายการเพิ่มเติมใน dict:

import collections

x=[1,2,20,6,210]
z=collections.OrderedDict.fromkeys(x)
z
OrderedDict([(1, None), (2, None), (20, None), (6, None), (210, None)])

หากคุณต้องการเพิ่มรายการ แต่ยังคงปฏิบัติเหมือนเป็นชุดคุณสามารถทำได้:

z['nextitem']=None

และคุณสามารถดำเนินการเช่น z.keys () บน dict และรับชุด:

z.keys()
[1, 2, 20, 6, 210]

คุณต้องทำlist(z.keys())เพื่อให้ได้ผลลัพธ์ของรายการ
jxn

ใน Python 3 ใช่ ไม่ได้อยู่ใน Python 2 แม้ว่าฉันควรระบุ
jimh

0

การใช้แนวคิดคะแนนสูงสุดข้างต้นที่นำกลับมาสู่รายการ:

def SetOfListInOrder(incominglist):
    from collections import OrderedDict
    outtemp = OrderedDict()
    for item in incominglist:
        outtemp[item] = None
    return(list(outtemp))

ทดสอบ (สั้น ๆ ) บน Python 3.6 และ Python 2.7


0

ในกรณีที่คุณมีองค์ประกอบจำนวนเล็กน้อยในรายการเริ่มต้นสองรายการที่คุณต้องการตั้งค่าการดำเนินการที่แตกต่างแทนที่จะใช้collections.OrderedDictสิ่งที่ทำให้การนำไปใช้งานซับซ้อนและทำให้อ่านได้น้อยลงคุณสามารถใช้:

# initial lists on which you want to do set difference
>>> nums = [1,2,2,3,3,4,4,5]
>>> evens = [2,4,4,6]
>>> evens_set = set(evens)
>>> result = []
>>> for n in nums:
...   if not n in evens_set and not n in result:
...     result.append(n)
... 
>>> result
[1, 3, 5]

ความซับซ้อนของเวลาไม่ดีเท่าไหร่ แต่ก็เรียบร้อยและอ่านง่าย


0

เป็นเรื่องน่าสนใจที่ผู้คนมักจะใช้ 'ปัญหาในโลกแห่งความจริง' เพื่อสร้างความตลกขบขันกับคำจำกัดความในวิทยาศาสตร์เชิงทฤษฎี

หากชุดมีคำสั่งก่อนอื่นคุณต้องหาปัญหาต่อไปนี้ หากรายการของคุณมีองค์ประกอบที่ซ้ำกันลำดับควรเป็นอย่างไรเมื่อคุณเปลี่ยนเป็นชุด อะไรคือคำสั่งถ้าเรารวมกันสองชุด? ลำดับคืออะไรถ้าเรานำสองชุดที่มีลำดับต่างกันในองค์ประกอบเดียวกัน

นอกจากนี้การตั้งค่ายังเร็วกว่ามากในการค้นหาคีย์เฉพาะซึ่งดีมากในการตั้งค่า (และนั่นคือเหตุผลที่คุณต้องใช้ชุด แต่ไม่ใช่รายการ)

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

def indx_dic(l):
    dic = {}
    for i in range(len(l)):
        if l[i] in dic:
            dic.get(l[i]).append(i)
        else:
            dic[l[i]] = [i]
    return(dic)

a = [1,2,3,4,5,1,3,2]
set_a  = set(a)
dic_a = indx_dic(a)

print(dic_a)
# {1: [0, 5], 2: [1, 7], 3: [2, 6], 4: [3], 5: [4]}
print(set_a)
# {1, 2, 3, 4, 5}

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