Chuyển đổi danh sách thành một tập hợp sẽ thay đổi thứ tự phần tử


119

Gần đây tôi nhận thấy rằng khi tôi đang chuyển đổi một listđến setthứ tự của các yếu tố được thay đổi và được sắp xếp theo nhân vật.

Hãy xem xét ví dụ này:

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

Câu hỏi của tôi là -

  1. Tại sao chuyện này đang xảy ra?
  2. Làm thế nào tôi có thể thực hiện các hoạt động đặt (đặc biệt là Đặt Chênh lệch) mà không bị mất lệnh ban đầu?

8
Tại sao bạn không muốn mất lệnh ban đầu, đặc biệt là nếu bạn đang thực hiện các hoạt động thiết lập? "order" là một khái niệm vô nghĩa đối với các tập hợp, không chỉ trong Python mà trong toán học.
Karl Knechtel

131
@KarlKnechtel - Yes "để là một khái niệm vô nghĩa cho bộ ... trong toán học" nhưng tôi có vấn đề thế giới thực :)
d.putto

Trên CPython 3.6+ unique = list(dict.fromkeys([1, 2, 1]).keys()). Điều này hoạt động vì dictbây giờ bảo tồn thứ tự chèn.
Boris

Câu trả lời:


106
  1. A setlà một cấu trúc dữ liệu không có thứ tự, vì vậy nó không bảo toàn thứ tự chèn.

  2. Điều này phụ thuộc vào yêu cầu của bạn. Nếu bạn có một danh sách bình thường và muốn loại bỏ một số tập hợp phần tử trong khi vẫn giữ nguyên thứ tự của danh sách, bạn có thể thực hiện việc này bằng cách hiểu danh sách:

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

    Nếu bạn cần cấu trúc dữ liệu hỗ trợ cả kiểm tra tư cách thành viên nhanhduy trì thứ tự chèn , bạn có thể sử dụng các khóa của từ điển Python, bắt đầu từ Python 3.7 được đảm bảo duy trì thứ tự chèn:

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

    bkhông thực sự cần phải đặt hàng ở đây - bạn cũng có thể sử dụng set. Lưu ý rằng a.keys() - b.keys()trả về sự khác biệt đã đặt dưới dạng a set, vì vậy nó sẽ không bảo toàn thứ tự chèn.

    Trong các phiên bản cũ hơn của Python, bạn có thể sử dụng collections.OrderedDictthay thế:

    >>> 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
Không có đối tượng nào có giá 16 byte. Nếu chỉ có một OrderedSet mặc định (). :(
Sean

2
@Sean không, họ không. Nonelà một ngôn ngữ được bảo đảm singleton. Trong CPython, chi phí thực tế chỉ là con trỏ (mặc dù chi phí đó luôn ở đó, nhưng đối với một mệnh đề, bạn gần như có thể xem xét Nonevà các tệp đơn hoặc tài liệu tham khảo được chia sẻ khác "miễn phí"), do đó, một từ máy, có thể là 8 byte trên máy tính hiện đại . Nhưng vâng, nó không hiệu quả về không gian như một bộ có thể.
juanpa.arrivillaga

2
Trên CPython 3.6+, bạn chỉ có thể làm được dict.fromkeys([1, 2, 1]).keys()dictcũng có thứ tự duy trì thông thường .
Boris

@Boris Đây chỉ là một phần của đặc tả ngôn ngữ bắt đầu từ Python 3.7. Mặc dù việc triển khai CPython đã duy trì thứ tự chèn trong phiên bản 3.6, đây được coi là chi tiết triển khai có thể không được các triển khai Python khác tuân theo.
Sven Marnach

@Sven tôi đã nói CPython. Tôi đăng bài này ở khắp mọi nơi, tôi chỉ cảm thấy mệt mỏi khi viết "CPython 3.6 hoặc bất kỳ triển khai nào khác bắt đầu với Python 3.7". Nó thậm chí không quan trọng, tất cả mọi người đang sử dụng CPython
Boris

53

Trong Python 3.6, set()bây giờ nên giữ thứ tự, nhưng có một giải pháp khác cho Python 2 và 3:

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

8
Hai lưu ý liên quan đến việc bảo toàn thứ tự: chỉ đối với Python 3.6 và thậm chí ở đó, nó được coi là chi tiết triển khai, vì vậy đừng dựa vào nó. Ngoài ra, mã của bạn rất kém hiệu quả vì mỗi lần x.indexđược gọi, một tìm kiếm tuyến tính được thực hiện. Nếu bạn ổn với độ phức tạp bậc hai, không có lý do gì để sử dụng a setngay từ đầu.
Thijs van Dien

27
@ThijsvanDien này là sai, set()không ra lệnh bằng Python 3.6, thậm chí không như một chi tiết thực hiện, bạn đang nghĩ đến việc dicts
Chris_Rands

8
@ThijsvanDien Không, chúng không được sắp xếp, mặc dù đôi khi xuất hiện như vậy vì intchúng thường tự băm cho chính mình stackoverflow.com/questions/45581901/…
Chris_Rands,

3
Hãy thử x=[1,2,-1,20,6,210]và làm cho nó thành một bộ. Bạn sẽ thấy nó không có thứ tự nào cả, được thử nghiệm bằng Python 3.6.
GabrielChu

3
Tôi không thể hiểu tại sao câu trả lời này có quá nhiều lượt ủng hộ, nó không giữ thứ tự chèn, không trả về một tập hợp.
Igor Rodriguez

20

Trả lời câu hỏi đầu tiên của bạn, tập hợp là cấu trúc dữ liệu được tối ưu hóa cho các hoạt động tập hợp. Giống như một tập hợp toán học, nó không thực thi hoặc duy trì bất kỳ thứ tự cụ thể nào của các phần tử. Khái niệm trừu tượng của một tập hợp không thực thi trật tự, vì vậy việc triển khai không bắt buộc. Khi bạn tạo một tập hợp từ một danh sách, Python có quyền tự do thay đổi thứ tự của các phần tử theo nhu cầu của việc triển khai nội bộ mà nó sử dụng cho một tập hợp, điều này có thể thực hiện các hoạt động tập hợp một cách hiệu quả.


9

loại bỏ các bản sao và duy trì thứ tự bằng chức năng bên dưới

def unique(sequence):
    seen = set()
    return [x for x in sequence if not (x in seen or seen.add(x))]

kiểm tra liên kết này


Tuyệt vời, cách tốt hơn giải pháp của tôi :)
Tiger-222

8

Trong toán học, có tập hợptập hợp có thứ tự (osets).

  • set : một vùng chứa không có thứ tự của các phần tử duy nhất (Đã triển khai)
  • oset : một vùng chứa có thứ tự các phần tử duy nhất (NotImplemented)

Trong Python, chỉ các tập hợp được triển khai trực tiếp. Chúng ta có thể mô phỏng hệ điều hành bằng các phím dict thông thường ( 3.7+ ).

Được

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

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

Bản giới thiệu

Các bản sao bị xóa, thứ tự chèn được giữ nguyên.

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

Hoạt động giống như thiết lập trên các phím chính tả.

oset - b
# {1, 20, 210}

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

oset & b
# {2, 6}

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

Chi tiết

Lưu ý: một cấu trúc không có thứ tự không loại trừ các phần tử có thứ tự . Đúng hơn, trật tự duy trì không được đảm bảo. Thí dụ:

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

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

Một người có thể được hài lòng để khám phá ra rằng một danh sáchMultiSet (mset) là hai hấp dẫn hơn, cấu trúc dữ liệu toán học:

  • danh sách : một vùng chứa có thứ tự các phần tử cho phép sao chép (Đã triển khai)
  • mset : một vùng chứa các phần tử không có thứ tự cho phép sao chép (NotImplemented) *

Tóm lược

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

* Một tập hợp có thể được mô phỏng gián tiếp với collections.Counter(), một ánh xạ giống như mệnh đề của các phép nhân (số đếm).


4

Như được biểu thị trong các câu trả lời khác, tập hợp là cấu trúc dữ liệu (và các khái niệm toán học) không bảo toàn thứ tự phần tử -

Tuy nhiên, bằng cách sử dụng kết hợp các bộ và từ điển, bạn có thể đạt được wathever mà mình muốn - hãy thử sử dụng các đoạn mã sau:

# 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

Dựa trên câu trả lời của Sven, tôi đã sử dụng các bộ sưu tập .OrderedDict như vậy đã giúp tôi hoàn thành những gì bạn muốn và cho phép tôi thêm nhiều mục hơn vào 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)])

Nếu bạn muốn thêm các mục nhưng vẫn coi nó như một tập hợp, bạn có thể làm:

z['nextitem']=None

Và bạn có thể thực hiện một thao tác như z.keys () trên dict và lấy bộ:

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

bạn cần làm gì list(z.keys())để có được đầu ra danh sách.
jxn

trong Python 3, có. không phải trong Python 2, mặc dù tôi nên chỉ định.
jimh

0

Việc triển khai khái niệm điểm cao nhất ở trên đưa nó trở lại danh sách:

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

Đã thử nghiệm (ngắn gọn) trên Python 3.6 và Python 2.7.


0

Trong trường hợp bạn có một số lượng nhỏ các phần tử trong hai danh sách ban đầu mà bạn muốn thực hiện thao tác đặt chênh lệch, thay vì sử dụng thao tác collections.OrderedDictnày sẽ làm phức tạp việc triển khai và làm cho nó khó đọc hơn, bạn có thể sử dụng:

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

Độ phức tạp về thời gian của nó không tốt lắm nhưng nó gọn gàng và dễ đọc.


0

Thật thú vị khi mọi người luôn sử dụng 'vấn đề thế giới thực' để làm trò đùa về định nghĩa trong khoa học lý thuyết.

Nếu bộ có thứ tự, trước tiên bạn cần tìm ra các vấn đề sau. Nếu danh sách của bạn có các phần tử trùng lặp, thứ tự sẽ như thế nào khi bạn chuyển nó thành một tập hợp? Thứ tự là gì nếu chúng ta kết hợp hai bộ? Thứ tự là gì nếu chúng ta giao nhau hai tập hợp có thứ tự khác nhau trên cùng một phần tử?

Ngoài ra, thiết lập nhanh hơn nhiều trong việc tìm kiếm một khóa cụ thể, điều này rất tốt trong hoạt động tập hợp (và đó là lý do tại sao bạn cần một tập hợp, nhưng không cần danh sách).

Nếu bạn thực sự quan tâm đến chỉ mục, chỉ cần giữ nó như một danh sách. Nếu bạn vẫn muốn thực hiện thao tác thiết lập trên các phần tử trong nhiều danh sách, cách đơn giản nhất là tạo một từ điển cho từng danh sách với các khóa giống nhau trong tập hợp cùng với một giá trị của danh sách chứa tất cả chỉ mục của khóa trong danh sách ban đầu.

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}

-8

Đây là một cách dễ dàng để làm điều đó:

x=[1,2,20,6,210]
print sorted(set(x))

3
Điều này không nhất thiết phải bảo toàn thứ tự.
David Boshton
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.