Apakah Python memiliki set yang dipesan?


477

Python memiliki kamus yang diurutkan . Bagaimana dengan set yang dipesan?


18
bagaimana dengan yang sebaliknya, sekantong barang? (unordered and non-unique)
wim

19
@ wim collections.Counteradalah tas Python.
flornquake

1
Bagaimana jika sesuatu ditambahkan dua kali? Bagaimana seharusnya posisi itu?
McKay

2
@McKay - jika ingin mengikuti perilaku koleksi. OrderDict masih akan berada di posisi penambahan awal
wojtow

Jawaban:


206

Ada resep yang diatur (mungkin tautan baru ) resep untuk ini yang dirujuk dari Dokumentasi Python 2 . Ini berjalan pada Py2.6 atau lebih baru dan 3.0 atau lebih baru tanpa modifikasi apa pun. Antarmuka hampir persis sama dengan set normal, kecuali bahwa inisialisasi harus dilakukan dengan daftar.

OrderedSet([1, 2, 3])

Ini adalah MutableSet, jadi tanda tangan untuk .uniontidak cocok dengan yang ditetapkan, tetapi karena menyertakan __or__sesuatu yang serupa dapat dengan mudah ditambahkan:

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

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

6
Saya memilih jawaban saya sendiri karena referensi dari dokumentasi ini mendekati jawaban resmi
Casebash

49
Antarmuka yang tidak persis sama dengan set objek normal, banyak metode penting yang hilang seperti update, union, intersection.
xApple

5
FYI, saya melihat bahwa versi modifikasi dari resep dikutip dalam jawaban ini telah ditambahkan ke PyPI sebagai "memerintahkan-set"
Geoffrey Hing

7
Saya cukup yakin Anda tidak diizinkan memiliki dua metode yang disebut uniondalam kelas yang sama. Yang terakhir akan "menang" dan yang pertama akan gagal ada saat runtime. Ini karena OrderedSet.union(tanpa parens) harus merujuk ke satu objek.
Kevin

3
Ada juga paket "orderedset" yang didasarkan pada resep yang sama tetapi diimplementasikan dalam Cython - pypi.python.org/pypi/orderedset .
mbdevpl

149

Set yang dipesan secara fungsional merupakan kasus khusus dari kamus yang diurutkan.

Kunci-kunci kamus itu unik. Jadi, jika seseorang mengabaikan nilai-nilai dalam kamus yang diurutkan (misalnya dengan menetapkannya None), maka pada dasarnya ia memiliki kumpulan yang diurutkan.

Pada Python 3.1 ada collections.OrderedDict. Berikut ini adalah contoh implementasi dari OrderedSet. (Perhatikan bahwa hanya beberapa metode yang perlu didefinisikan atau diganti: collections.OrderedDictdan collections.MutableSetlakukan pengangkatan berat.)

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: ya, satu mungkin ingin mendefinisikan kelas OrderedSetyang subclass OrderedDictdan abc.Setkemudian menentukan __len__, __iter__dan __contains__.
Stephan202

1
@ Stephan202: Sayangnya, koleksi ABC tinggal collections, tetapi sebaliknya saran yang bagus
u0b34a0f6ae

4
Ini benar, tetapi Anda memiliki banyak ruang terbuang sebagai akibatnya, yang mengarah pada kinerja suboptimal.
Daniel Kats

3
Tambahan; collections.OrderedDict juga tersedia dalam python 2.7.
Nurbldoff

2
Melakukan OrderedSet([1,2,3])memunculkan TypeError. Bagaimana cara kerja konstruktor? Contoh penggunaan tidak ada.
xApple

90

Jawabannya adalah tidak, tetapi Anda dapat menggunakan collections.OrderedDictdari pustaka standar Python hanya dengan kunci (dan nilai sebagai None) untuk tujuan yang sama.

Update : Pada Python 3.7 (dan CPython 3.6), standar dictini dijamin untuk mempertahankan ketertiban dan lebih performant dari OrderedDict. (Namun, untuk kompatibilitas mundur dan terutama keterbacaan, Anda mungkin ingin terus menggunakan OrderedDict.)

Berikut adalah contoh cara menggunakan dictset yang dipesan untuk memfilter item duplikat sambil mempertahankan pesanan, sehingga meniru set yang dipesan. Gunakan dictmetode kelas fromkeys()untuk membuat dict, lalu cukup minta keys()kembali.

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

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

4
Mungkin patut disebutkan bahwa ini juga berfungsi (lebih cepat) dengan vanila dict.fromkeys(). Tetapi dalam kasus itu, urutan kunci hanya dipertahankan dalam implementasi CPython 3.6+, sehingga OrderedDictmerupakan solusi yang lebih portabel ketika pesanan penting.
jez

1
tidak akan berfungsi jika nilainya tidak string
Anwar Hossain

4
@AnwarHossain keys = (1,2,3,1,2,1) list(OrderedDict.fromkeys(keys).keys())-> [1, 2, 3], python-3.7. Berhasil.
raratiru

1
Bisakah kita menyimpulkan bahwa Set dengan Python 3.7+ juga mempertahankan pesanan?
user474491

2
@ user474491 Tidak seperti dict, setdalam Python 3.7+ sayangnya tidak mempertahankan ketertiban.
cz

39

Aku bisa melakukan Anda salah satu lebih baik daripada OrderedSet: Kensington memiliki murni-Python, 2/3-kompatibel IndexedSetjenis yang tidak hanya set memerintahkan, tetapi juga mendukung pengindeksan (seperti dengan daftar).

Cukup pip install boltons(atau salin setutils.pyke basis kode Anda), impor IndexedSetdan:

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

Semuanya unik dan tetap teratur. Pengungkapan penuh: Saya menulis IndexedSet, tetapi itu juga berarti Anda dapat menggangguku jika ada masalah . :)


39

Implementasi pada PyPI

Sementara yang lain telah menunjukkan bahwa tidak ada implementasi built-in dari susunan urutan penyisipan diatur dalam Python (belum), saya merasa bahwa pertanyaan ini tidak ada jawaban yang menyatakan apa yang bisa ditemukan di PyPI .

Ada paketnya:

Beberapa implementasi ini didasarkan pada resep yang diposting oleh Raymond Hettinger ke ActiveState yang juga disebutkan dalam jawaban lain di sini.

Beberapa perbedaan

  • dipesan-set (versi 1.1)
    • keuntungan: O (1) untuk pencarian berdasarkan indeks (misalnya my_set[5])
  • oset (versi 0.1.3)
    • Keuntungan: O (1) untuk remove(item)
    • kerugian: tampaknya O (n) untuk pencarian berdasarkan indeks

Kedua implementasi memiliki O (1) untuk add(item)dan __contains__(item)( item in my_set).


2
Pesaing baru adalah collections_extended.setlist . Fungsi seperti set.uniontidak berfungsi meskipun, meskipun mewarisi collections.abc.Set.
timdiels

3
OrderedSetsekarang mendukungremove
warvariuc

17

Jika Anda menggunakan set yang dipesan untuk mempertahankan pesanan yang diurutkan, pertimbangkan untuk menggunakan implementasi yang diurutkan dari PyPI. The sortedcontainers modul menyediakan SortedSet hanya untuk tujuan ini. Beberapa manfaat: implementasi murni-Python, cepat-sebagai-C, cakupan uji unit 100%, jam pengujian stres.

Menginstal dari PyPI mudah dengan pip:

pip install sortedcontainers

Perhatikan bahwa jika Anda tidak bisa pip install, cukup tarik ke bawah file sortlist.py dan sortir.py dari repositori open-source .

Setelah diinstal Anda cukup:

from sortedcontainers import SortedSet
help(SortedSet)

Modul wadah yang diurutkan juga mempertahankan perbandingan kinerja dengan beberapa implementasi alternatif.

Untuk komentar yang bertanya tentang tipe data tas Python, ada alternatifnya tipe data SortedList yang dapat digunakan untuk mengimplementasikan tas secara efisien.


Perhatikan bahwa SortedSetkelas di sana mengharuskan anggota untuk dapat dibandingkan dan hashable.
gsnedders

4
@gsnedders The builtin setdan frozensetjuga membutuhkan elemen yang harus hashable. Batasan yang sebanding adalah tambahan untuk SortedSet, tetapi juga kendala yang jelas.
keturunan

2
Seperti namanya, ini tidak menjaga ketertiban. Itu tidak lain adalah diurutkan (set ([urutan])) yang membuat lebih baik?
ldmtwo

@ldmtwo Saya tidak yakin yang Anda maksud tetapi hanya untuk menjadi jelas, SortedSet sebagai bagian dari Container Diurutkan memang mempertahankan urutan diurutkan.
GrantJ

2
@GrantJ - Ini adalah perbedaan antara apakah ia mempertahankan penyisipan perintah atau semacam order. Sebagian besar jawaban lain adalah mengenai urutan penyisipan. Saya pikir Anda sudah mengetahui hal ini berdasarkan kalimat pertama Anda, tetapi mungkin itulah yang dikatakan oleh ldmtwo.
Justin

9

Jika Anda sudah menggunakan panda dalam kode Anda, Indexobjeknya berperilaku cukup seperti set yang diperintahkan, seperti yang ditunjukkan dalam artikel ini .

Contoh dari artikel:

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

Bisakah Anda memasukkan contoh dalam jawaban ini? Tautan cenderung rusak setelah beberapa waktu.
Alechan

1
untuk perbedaan antara set, Anda benar-benar perlu menggunakan indA.difference(indB), tanda minus melakukan pengurangan standar
gg349

7

Sedikit terlambat ke permainan, tapi saya telah menulis sebuah kelas setlistsebagai bagian dari collections-extendedsepenuhnya mengimplementasikan keduanya SequencedanSet

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

Dokumentasi: http://collections-extended.lenzm.net/en/latest/

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


7

Tidak ada OrderedSetdi perpustakaan resmi. Saya membuat lembar contekan lengkap dari semua struktur data untuk referensi Anda.

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

The ParallelRegression paket menyediakan setlist () memerintahkan kelas set yang lebih metode-lengkap daripada pilihan berdasarkan resep ActiveState. Ini mendukung semua metode yang tersedia untuk daftar dan kebanyakan jika tidak semua metode tersedia untuk set.


2

Seperti jawaban lain menyebutkan, untuk python 3.7+, dikt disusun berdasarkan definisi. Alih-alih subklas OrderedDictkita dapat mensubklas abc.collections.MutableSetatau typing.MutableSetmenggunakan kunci dict untuk menyimpan nilai-nilai kita.

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

Maka cukup:

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

Saya meletakkan kode ini di perpustakaan kecil , jadi siapa pun bisa pip installmelakukannya.


-4

Untuk banyak tujuan, cukup menelepon saja sudah cukup. Sebagai contoh

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

Jika Anda akan menggunakan ini berulang kali, akan ada overhead yang muncul dengan memanggil fungsi yang diurutkan sehingga Anda mungkin ingin menyimpan daftar yang dihasilkan, selama Anda selesai mengubah set. Jika Anda perlu mempertahankan elemen unik dan diurutkan, saya setuju dengan saran untuk menggunakan OrderedDict dari koleksi dengan nilai arbitrer seperti Tidak Ada.


43
Tujuan untuk OrderedSet adalah untuk bisa mendapatkan item dalam urutan yang mereka tambahkan ke set. Contoh Anda mungkin bisa disebut SortedSet ...
Pemeliharaan Berkala

-4

Jadi saya juga punya daftar kecil di mana saya jelas memiliki kemungkinan memperkenalkan nilai-nilai yang tidak unik.

Saya mencari keberadaan daftar unik dari beberapa jenis, tetapi kemudian menyadari bahwa menguji keberadaan elemen sebelum menambahkannya berfungsi dengan baik.

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

Saya tidak tahu apakah ada peringatan untuk pendekatan sederhana ini, tetapi itu memecahkan masalah saya.


Masalah utama dengan pendekatan ini adalah menambahkan berjalan di O (n). Artinya semakin lambat dengan daftar besar. Set built-in Python sangat baik dalam membuat penambahan elemen lebih cepat. Tetapi untuk kasus penggunaan yang sederhana, tentu saja itu berhasil!
Draconis
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.