Python có một bộ được đặt hàng không?


477

Python có một từ điển được đặt hàng . Một bộ được đặt hàng thì sao?


18
Thế còn converse, một túi đồ? (không có thứ tự và không độc đáo)
wim

19
@wim collections.Counterlà túi của Python.
flornquake

1
Điều gì nếu một cái gì đó được thêm hai lần? Vị trí nên là gì?
McKay

2
@McKay - nếu nó đã đi theo hành vi của collections.OrderDict nó vẫn sẽ được ở vị trí của việc bổ sung ban đầu
wojtow

Câu trả lời:


206

Có một công thức được đặt hàng ( liên kết mới có thể ) cho cái này được tham chiếu từ Tài liệu Python 2 . Điều này chạy trên Py2.6 trở lên và 3.0 trở lên mà không có bất kỳ sửa đổi nào. Giao diện gần giống hệt như một bộ bình thường, ngoại trừ việc khởi tạo nên được thực hiện với một danh sách.

OrderedSet([1, 2, 3])

Đây là Mutableset, vì vậy chữ ký .unionkhông khớp với tập hợp, nhưng vì nó bao gồm __or__một cái gì đó tương tự có thể dễ dàng được thêm vào:

@staticmethod
def union(*sets):
    union = OrderedSet()
    union.union(*sets)
    return union

def union(self, *sets):
    for set in sets:
        self |= set

6
Tôi đã chọn câu trả lời của riêng mình vì tham chiếu từ tài liệu này gần với câu trả lời chính thức
Casebash

49
Giao diện là không chính xác giống như các đối tượng thiết lập bình thường, nhiều phương pháp thiết yếu bị thiếu như update, union, intersection.
xApple

5
FYI, tôi nhận thấy rằng một phiên bản sửa đổi một chút của công thức được trích dẫn trong câu trả lời này đã được thêm vào PyPi dưới dạng "set-set"
Geoffrey Hing

7
Tôi khá chắc chắn rằng bạn không được phép có hai phương thức được gọi uniontrong cùng một lớp. Người cuối cùng sẽ "chiến thắng" và người đầu tiên sẽ không tồn tại trong thời gian chạy. Điều này là do OrderedSet.union(không có parens) phải tham chiếu đến một đối tượng duy nhất .
Kevin

3
Ngoài ra còn có gói "orderset" dựa trên cùng một công thức nhưng được triển khai trong Cython - pypi.python.org/pypi/orderedset .
mbdevpl

149

Một bộ được đặt hàng về mặt chức năng là một trường hợp đặc biệt của một từ điển được đặt hàng.

Các khóa của một từ điển là duy nhất. Do đó, nếu người ta bỏ qua các giá trị trong một từ điển có thứ tự (ví dụ: bằng cách gán chúng None), thì về cơ bản, nó có một tập hợp có thứ tự.

Như Python 3.1collections.OrderedDict. Sau đây là một ví dụ triển khai của Orderedset. (Lưu ý rằng chỉ có một vài phương thức cần được xác định hoặc ghi đè: collections.OrderedDictcollections.MutableSetthực hiện việc nâng vật nặng.)

import collections

class OrderedSet(collections.OrderedDict, collections.MutableSet):

    def update(self, *args, **kwargs):
        if kwargs:
            raise TypeError("update() takes no keyword arguments")

        for s in args:
            for e in s:
                 self.add(e)

    def add(self, elem):
        self[elem] = None

    def discard(self, elem):
        self.pop(elem, None)

    def __le__(self, other):
        return all(e in other for e in self)

    def __lt__(self, other):
        return self <= other and self != other

    def __ge__(self, other):
        return all(e in self for e in other)

    def __gt__(self, other):
        return self >= other and self != other

    def __repr__(self):
        return 'OrderedSet([%s])' % (', '.join(map(repr, self.keys())))

    def __str__(self):
        return '{%s}' % (', '.join(map(repr, self.keys())))

    difference = __sub__ 
    difference_update = __isub__
    intersection = __and__
    intersection_update = __iand__
    issubset = __le__
    issuperset = __ge__
    symmetric_difference = __xor__
    symmetric_difference_update = __ixor__
    union = __or__

1
@Casebash: yes, người ta có thể muốn xác định một lớp OrderedSetmà lớp con OrderedDictabc.Setvà sau đó xác định __len__, __iter____contains__.
Stephan202

1
@ Stephan202: Đáng tiếc, bộ sưu tập ABC sống collections, nhưng nếu không thì là một gợi ý hay
u0b34a0f6ae

4
Điều này là đúng, nhưng kết quả là bạn có rất nhiều không gian bị lãng phí, dẫn đến hiệu suất dưới mức tối ưu.
Daniel Kats

3
Một sự bổ sung; bộ sưu tập.OrderedDict cũng có sẵn trong python 2.7.
Nurbldoff

2
Làm OrderedSet([1,2,3])tăng một TypeError. Làm thế nào để các nhà xây dựng thậm chí làm việc? Thiếu ví dụ sử dụng.
xApple

90

Câu trả lời là không, nhưng bạn có thể sử dụng collections.OrderedDicttừ thư viện chuẩn Python chỉ với các khóa (và các giá trị như None) cho cùng một mục đích.

Cập nhật : Kể từ Python 3.7 (và CPython 3.6), tiêu chuẩn dictđược đảm bảo để duy trì trật tự và có hiệu suất cao hơn OrderedDict. (Tuy nhiên, để tương thích ngược và đặc biệt là khả năng đọc, bạn có thể muốn tiếp tục sử dụng OrderedDict.)

Dưới đây là một ví dụ về cách sử dụng dictnhư một bộ được đặt hàng để lọc ra các mục trùng lặp trong khi duy trì trật tự, từ đó mô phỏng một bộ được đặt hàng. Sử dụng dictphương thức lớp fromkeys()để tạo ra một dict, sau đó chỉ cần yêu cầu mặt keys()sau.

>>> keywords = ['foo', 'bar', 'bar', 'foo', 'baz', 'foo']

>>> list(dict.fromkeys(keywords))
['foo', 'bar', 'baz']

4
Có lẽ đáng nói là điều này cũng hoạt động (nhanh hơn) với vani dict.fromkeys(). Nhưng trong trường hợp đó, thứ tự chính chỉ được bảo toàn trong các triển khai CPython 3.6+, vì vậy đây OrderedDictlà một giải pháp di động hơn khi đơn hàng có vấn đề.
Jez

1
sẽ không hoạt động nếu các giá trị không phải là chuỗi
Anwar Hossain

4
@AnwarHossain keys = (1,2,3,1,2,1) list(OrderedDict.fromkeys(keys).keys())-> [1, 2, 3], trăn-3.7. Nó hoạt động.
raratiru

1
Chúng ta có thể suy luận rằng Set trong Python 3.7+ giữ trật tự không?
dùng474491

2
@ user474491 Không giống như dict, settrong Python 3.7+ không may không giữ được thứ tự.
cz

39

Tôi có thể làm cho bạn một tốt hơn so với một OrderedSet: boltons có một tinh khiết-Python, 2/3 tương thích IndexedSetkiểu đó là không chỉ là một tập có thứ tự, mà còn hỗ trợ lập chỉ mục (như với danh sách).

Chỉ cần pip install boltons(hoặc sao chép setutils.pyvào cơ sở mã của bạn), nhập IndexedSetvà:

>>> from boltons.setutils import IndexedSet
>>> x = IndexedSet(list(range(4)) + list(range(8)))
>>> x
IndexedSet([0, 1, 2, 3, 4, 5, 6, 7])
>>> x - set(range(2))
IndexedSet([2, 3, 4, 5, 6, 7])
>>> x[-1]
7
>>> fcr = IndexedSet('freecreditreport.com')
>>> ''.join(fcr[:fcr.index('.')])
'frecditpo'

Tất cả mọi thứ là duy nhất và được giữ lại theo thứ tự. Tiết lộ đầy đủ: Tôi đã viết IndexedSet, nhưng điều đó cũng có nghĩa là bạn có thể lỗi tôi nếu có bất kỳ vấn đề nào . :)


39

Triển khai trên PyPI

Trong khi những người khác đã chỉ ra rằng không có triển khai tích hợp nào trong bộ bảo quản thứ tự chèn trong Python (tôi), tôi cảm thấy rằng câu hỏi này thiếu một câu trả lời trong đó nêu lên những gì cần tìm thấy trên PyPI .

Có các gói:

Một số trong những triển khai này dựa trên công thức được đăng bởi Raymond Hettinger lên ActiveState , cũng được đề cập trong các câu trả lời khác ở đây.

Một số khác biệt

  • đặt hàng (phiên bản 1.1)
    • lợi thế: O (1) để tra cứu theo chỉ số (ví dụ my_set[5])
  • oset (phiên bản 0.1.3)
    • lợi thế: O (1) cho remove(item)
    • nhược điểm: rõ ràng O (n) cho tra cứu theo chỉ số

Cả hai triển khai đều có O (1) cho add(item)__contains__(item)( item in my_set).


2
Một ứng cử viên mới là bộ sưu tập_extends.setlist . Các chức năng như set.unionkhông hoạt động trên đó mặc dù nó kế thừa collections.abc.Set.
timdiels

3
OrderedSethiện hỗ trợremove
warvariuc 19/03/2016

17

Nếu bạn đang sử dụng bộ được đặt hàng để duy trì thứ tự được sắp xếp, hãy xem xét sử dụng triển khai bộ được sắp xếp từ PyPI. Các sortedcontainers mô-đun cung cấp một SortedSet chỉ cho mục đích này. Một số lợi ích: Python thuần túy, triển khai nhanh như C, phạm vi kiểm tra đơn vị 100%, kiểm tra căng thẳng hàng giờ.

Cài đặt từ PyPI rất dễ dàng với pip:

pip install sortedcontainers

Lưu ý rằng nếu bạn không thể pip install, chỉ cần kéo xuống các tệp sortlist.py và sortset.py từ kho lưu trữ nguồn mở .

Sau khi cài đặt, bạn có thể chỉ cần:

from sortedcontainers import SortedSet
help(SortedSet)

Mô-đun sortcontainers cũng duy trì so sánh hiệu suất với một số triển khai thay thế.

Đối với nhận xét được hỏi về loại dữ liệu túi của Python, thay vào đó là loại dữ liệu Sắp xếp Danh sách có thể được sử dụng để triển khai hiệu quả túi.


Lưu ý rằng SortedSetlớp ở đó yêu cầu các thành viên phải tương đương và có thể băm.
gsnedder

4
@gsnedder Các nội trang setfrozensetcũng yêu cầu các yếu tố có thể băm được. Ràng buộc tương đương là sự bổ sung cho SortedSet, nhưng nó cũng là một ràng buộc rõ ràng.
gotgenes

2
Như tên cho thấy, điều này không duy trì trật tự. Không có gì ngoài việc sắp xếp (bộ ([chuỗi])) làm cho tốt hơn?
ldmtwo

@ldmtwo Tôi không chắc chắn mà bạn đang đề cập đến nhưng chỉ để được rõ ràng, SortedSet như một phần của Sắp xếp Container không duy trì trật tự sắp xếp.
GrantJ

2
@GrantJ - Đó là sự khác biệt giữa việc nó duy trì thứ tự chèn hay thứ tự sắp xếp . Hầu hết các câu trả lời khác liên quan đến thứ tự chèn. Tôi nghĩ rằng bạn đã nhận thức được điều này dựa trên câu đầu tiên của bạn, nhưng có lẽ đó là những gì ldmtwo đang nói.
Justin

8

Trong trường hợp bạn đã sử dụng gấu trúc trong mã của mình, Indexđối tượng của nó hoạt động khá giống một bộ được đặt hàng, như trong bài viết này .

Ví dụ từ bài viết:

indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])

indA & indB  # intersection
indA | indB  # union
indA - indB  # difference
indA ^ indB  # symmetric difference

Bạn có thể bao gồm một ví dụ trong câu trả lời này? Liên kết có xu hướng bị phá vỡ sau một thời gian.
Alechan

1
đối với sự khác biệt giữa các bộ, bạn thực sự cần sử dụng indA.difference(indB), dấu trừ thực hiện phép trừ tiêu chuẩn
gg349

7

Hơi muộn một chút với trò chơi, nhưng tôi đã viết một lớp setlistnhư một phần của collections-extendedviệc thực hiện đầy đủ cả hai SequenceSet

>>> from collections_extended import setlist
>>> sl = setlist('abracadabra')
>>> sl
setlist(('a', 'b', 'r', 'c', 'd'))
>>> sl[3]
'c'
>>> sl[-1]
'd'
>>> 'r' in sl  # testing for inclusion is fast
True
>>> sl.index('d')  # so is finding the index of an element
4
>>> sl.insert(1, 'd')  # inserting an element already in raises a ValueError
ValueError
>>> sl.index('d')
4

GitHub: https://github.com/mlenzen/collections-extends

Tài liệu: http://collections-extends.lenzm.net/en/latest/

PyPI: https://pypi.python.org/pypi/collections-extends


7

Không có OrderedSettrong thư viện chính thức. Tôi tạo ra một bộ quần áo đầy đủ tất cả các cấu trúc dữ liệu để bạn tham khảo.

DataStructure = {
    'Collections': {
        'Map': [
            ('dict', 'OrderDict', 'defaultdict'),
            ('chainmap', 'types.MappingProxyType')
        ],
        'Set': [('set', 'frozenset'), {'multiset': 'collection.Counter'}]
    },
    'Sequence': {
        'Basic': ['list', 'tuple', 'iterator']
    },
    'Algorithm': {
        'Priority': ['heapq', 'queue.PriorityQueue'],
        'Queue': ['queue.Queue', 'multiprocessing.Queue'],
        'Stack': ['collection.deque', 'queue.LifeQueue']
        },
    'text_sequence': ['str', 'byte', 'bytearray']
}

3

Các ParallelRegression gói cung cấp một setlist () ra lệnh cho bộ lớp đó là phương pháp hoàn tất hơn các tùy chọn dựa trên công thức ActiveState. Nó hỗ trợ tất cả các phương thức có sẵn cho danh sách và hầu hết nếu không phải tất cả các phương thức có sẵn cho các bộ.


2

Như các câu trả lời khác đề cập, như đối với python 3.7+, dict được sắp xếp theo định nghĩa. Thay vì phân lớp, OrderedDictchúng ta có thể phân lớp abc.collections.MutableSethoặc typing.MutableSetsử dụng các khóa của dict để lưu trữ các giá trị của chúng ta.

class OrderedSet(typing.MutableSet[T]):
    """A set that preserves insertion order by internally using a dict."""

    def __init__(self, iterable: t.Iterator[T]):
        self._d = dict.fromkeys(iterable)

    def add(self, x: T) -> None:
        self._d[x] = None

    def discard(self, x: T) -> None:
        self._d.pop(x)

    def __contains__(self, x: object) -> bool:
        return self._d.__contains__(x)

    def __len__(self) -> int:
        return self._d.__len__()

    def __iter__(self) -> t.Iterator[T]:
        return self._d.__iter__()

Sau đó, chỉ cần:

x = OrderedSet([1, 2, -1, "bar"])
x.add(0)
assert list(x) == [1, 2, -1, "bar", 0]

Tôi đặt mã này trong một thư viện nhỏ , vì vậy bất cứ ai cũng có thể chỉ cần pip installnó.


-4

Đối với nhiều mục đích chỉ đơn giản là gọi sắp xếp sẽ đủ. Ví dụ

>>> s = set([0, 1, 2, 99, 4, 40, 3, 20, 24, 100, 60])
>>> sorted(s)
[0, 1, 2, 3, 4, 20, 24, 40, 60, 99, 100]

Nếu bạn định sử dụng điều này nhiều lần, sẽ có chi phí phát sinh bằng cách gọi hàm được sắp xếp để bạn có thể muốn lưu danh sách kết quả, miễn là bạn đã hoàn tất việc thay đổi tập hợp. Nếu bạn cần duy trì các yếu tố duy nhất và được sắp xếp, tôi đồng ý với đề xuất sử dụng OrderedDict từ các bộ sưu tập có giá trị tùy ý như Không có.


43
Mục đích của OrderedSet là có thể lấy các mục theo thứ tự mà chúng được thêm vào tập hợp. Ví dụ của bạn có thể được gọi là Sắp xếp ...
Bảo trì định kỳ

-4

Vì vậy, tôi cũng có một danh sách nhỏ nơi tôi rõ ràng có khả năng giới thiệu các giá trị không độc đáo.

Tôi đã tìm kiếm sự tồn tại của một danh sách duy nhất của một số loại, nhưng sau đó nhận ra rằng việc kiểm tra sự tồn tại của phần tử trước khi thêm nó hoạt động tốt.

if(not new_element in my_list):
    my_list.append(new_element)

Tôi không biết có cách nào để tiếp cận phương pháp đơn giản này không, nhưng nó giải quyết được vấn đề của tôi.


Vấn đề chính với cách tiếp cận này là việc thêm chạy trong O (n). Có nghĩa là nó trở nên chậm hơn với danh sách lớn. Các bộ dựng sẵn của Python rất tốt trong việc thêm các phần tử nhanh hơn. Nhưng đối với các trường hợp sử dụng đơn giản, nó chắc chắn có tác dụng!
Draconis
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.