Mengubah daftar menjadi sekumpulan mengubah urutan elemen

119

Baru-baru ini saya perhatikan bahwa ketika saya mengubah a listke seturutan elemen diubah dan diurutkan berdasarkan karakter.

Pertimbangkan contoh ini:

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

Pertanyaan saya adalah -

  1. Mengapa ini terjadi?
  2. Bagaimana saya bisa melakukan operasi set (terutama Set Difference) tanpa kehilangan urutan awal?
d.putto
sumber
8
Mengapa Anda tidak ingin kehilangan urutan awal, terutama jika Anda melakukan operasi set? "keteraturan" adalah konsep yang tidak berarti untuk himpunan, tidak hanya dalam Python tetapi juga dalam matematika.
Karl Knechtel
131
@KarlKnechtel - Ya "pesanan adalah konsep yang tidak berarti untuk set ... dalam matematika" tetapi saya memiliki masalah dunia nyata :)
d.putto
Pada CPython 3.6+ unique = list(dict.fromkeys([1, 2, 1]).keys()). Ini berfungsi karena dicts menyimpan urutan penyisipan sekarang.
Boris

Jawaban:

106
  1. A setadalah struktur data yang tidak berurutan, sehingga tidak mempertahankan urutan penyisipan.

  2. Ini tergantung pada kebutuhan Anda. Jika Anda memiliki daftar normal, dan ingin menghapus beberapa set elemen sambil mempertahankan urutan daftar, Anda dapat melakukannya dengan pemahaman daftar:

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

    Jika Anda memerlukan struktur data yang mendukung uji keanggotaan cepat dan pelestarian urutan penyisipan , Anda dapat menggunakan kunci kamus Python, yang mulai dari Python 3.7 dijamin akan mempertahankan urutan penyisipan:

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

    btidak perlu dipesan di sini - Anda juga bisa menggunakan a set. Perhatikan bahwa a.keys() - b.keys()mengembalikan perbedaan set sebagaiset , jadi itu tidak akan mempertahankan urutan penyisipan.

    Di Python versi lama, Anda bisa menggunakan 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)])
Sven Marnach
sumber
3
Tidak ada objek yang berharga 16 byte. Jika hanya ada OrderedSet () default. :(
Sean
2
@ Sean tidak, mereka tidak. Noneadalah bahasa tunggal yang dijamin. Di CPython, biaya sebenarnya hanyalah penunjuk (meskipun biaya itu selalu ada, tetapi untuk sebuah dict, Anda hampir dapat mempertimbangkan Nonedan single lain atau referensi bersama "gratis"), jadi kata mesin, kemungkinan 8 byte pada komputer modern . Tapi ya, itu tidak seefisien ruang seperti yang bisa dilakukan satu set.
juanpa.arrivillaga
2
Pada CPython 3.6+ Anda bisa melakukannya dict.fromkeys([1, 2, 1]).keys()karena biasa dictmenjaga ketertiban juga.
Boris
@Boris Ini hanya menjadi bagian dari spesifikasi bahasa mulai dari Python 3.7. Meskipun implementasi CPython sudah mempertahankan urutan penyisipan di versi 3.6, ini dianggap sebagai detail implementasi yang mungkin tidak diikuti oleh implementasi Python lainnya.
Sven Marnach
@ Bahkan saya mengatakan CPNS. Saya memposting ini di mana-mana, saya hanya bosan menulis "CPython 3.6 atau implementasi lain yang dimulai dengan Python 3.7". Tidak masalah, semua orang menggunakan CPython
Boris
53

Di Python 3.6, set()sekarang harus menjaga urutannya, tetapi ada solusi lain untuk Python 2 dan 3:

>>> x = [1, 2, 20, 6, 210]
>>> sorted(set(x), key=x.index)
[1, 2, 20, 6, 210]
Tiger-222
sumber
8
Dua catatan tentang pelestarian pesanan: hanya pada Python 3.6, dan bahkan di sana, ini dianggap sebagai detail implementasi, jadi jangan mengandalkannya. Selain itu, kode Anda sangat tidak efisien karena setiap kali x.indexdipanggil, pencarian linier dilakukan. Jika Anda baik-baik saja dengan kompleksitas kuadrat, tidak ada alasan untuk menggunakan a setsejak awal.
Thijs van Dien
27
@ThijsvanDien Ini salah, set()tidak dipesan dengan Python 3.6, bahkan sebagai detail implementasi, Anda memikirkan dicts
Chris_Rands
8
@ThijsvanDien Tidak, mereka tidak diurutkan, meskipun kadang-kadang muncul begitu karena intsering
bercirikan
3
Coba x=[1,2,-1,20,6,210]dan jadikan satu set. Anda akan melihat itu tidak dipesan sama sekali, diuji dengan Python 3.6.
GabrielChu
3
Saya tidak mengerti mengapa jawaban ini memiliki begitu banyak suara positif, tidak menjaga urutan penyisipan, juga tidak mengembalikan satu set.
Igor Rodriguez
20

Menjawab pertanyaan pertama Anda, kumpulan adalah struktur data yang dioptimalkan untuk operasi kumpulan. Seperti himpunan matematika, ia tidak menegakkan atau mempertahankan urutan elemen tertentu. Konsep abstrak dari suatu himpunan tidak memaksakan keteraturan, jadi implementasi tidak diperlukan. Saat Anda membuat set dari daftar, Python memiliki kebebasan untuk mengubah urutan elemen untuk kebutuhan implementasi internal yang digunakannya untuk sebuah set, yang mampu melakukan operasi set secara efisien.

lvella
sumber
9

Hapus duplikat dan pertahankan ketertiban dengan fungsi di bawah ini

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

periksa tautan ini

Sana
sumber
Bagus, jauh lebih baik daripada solusi saya :)
Tiger-222
8

Dalam matematika, ada himpunan dan himpunan terurut (osets).

  • set : wadah tak berurutan dari elemen unik (Diimplementasikan)
  • oset : wadah terurut dari elemen unik (NotImplemented)

Di Python, hanya set yang diimplementasikan secara langsung. Kita dapat meniru osets dengan tombol dict biasa ( 3.7+ ).

Diberikan

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

Kode

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

Demo

Replikasi dihapus, urutan penyisipan dipertahankan.

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

Operasi set-like pada tombol dikt.

oset - b
# {1, 20, 210}

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

oset & b
# {2, 6}

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

Detail

Catatan: struktur tak berurutan tidak menghalangi elemen berurutan. Sebaliknya, ketertiban yang terjaga tidak dijamin. Contoh:

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

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

Seseorang mungkin senang mengetahui bahwa list dan multiset (mset) adalah dua struktur data matematika yang lebih menarik:

  • daftar : wadah berurutan dari elemen yang memungkinkan replikasi (Diimplementasikan)
  • mset : wadah elemen tak berurutan yang memungkinkan replikasi (NotImplemented) *

Ringkasan

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

* Sebuah multiset dapat secara tidak langsung ditiru dengan collections.Counter(), pemetaan multiplisitas seperti diktik (hitungan).

pylang
sumber
4

Seperti yang dilambangkan dalam jawaban lain, himpunan adalah struktur data (dan konsep matematika) yang tidak mempertahankan urutan elemen -

Namun, dengan menggunakan kombinasi set dan kamus, Anda dapat mencapai apa pun yang Anda inginkan - coba gunakan cuplikan berikut:

# 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
jsbueno
sumber
1

Berdasarkan jawaban Sven, saya menemukan menggunakan collections.OrderedDict seperti itu membantu saya mencapai apa yang Anda inginkan dan memungkinkan saya menambahkan lebih banyak item ke 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)])

Jika Anda ingin menambahkan item tetapi tetap memperlakukannya seperti satu set, Anda dapat melakukannya:

z['nextitem']=None

Dan Anda bisa melakukan operasi seperti z.keys () pada dict dan mendapatkan setnya:

z.keys()
[1, 2, 20, 6, 210]
jimh
sumber
yang perlu Anda lakukan list(z.keys())untuk mendapatkan keluaran daftar.
jxn
dengan Python 3, ya. tidak dengan Python 2, meskipun saya harus menentukan.
jimh
0

Penerapan konsep skor tertinggi di atas yang mengembalikannya ke daftar:

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

Diuji (secara singkat) pada Python 3.6 dan Python 2.7.

Mike Stucka
sumber
0

Jika Anda memiliki sejumlah kecil elemen di dua daftar awal yang ingin Anda lakukan operasi setel perbedaan, alih-alih menggunakan collections.OrderedDictyang mempersulit implementasi dan membuatnya kurang dapat dibaca, Anda dapat menggunakan:

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

Kompleksitas waktunya tidak begitu bagus tetapi rapi dan mudah dibaca.

Ultrablendz
sumber
0

Menarik bahwa orang selalu menggunakan 'masalah dunia nyata' untuk membuat lelucon tentang definisi dalam ilmu teoritis.

Jika set memiliki urutan, Anda harus terlebih dahulu mencari tahu masalah berikut. Jika daftar Anda memiliki elemen duplikat, bagaimana urutannya saat Anda mengubahnya menjadi satu set? Apa urutannya jika kita menggabungkan dua set? Berapakah urutannya jika kita memotong dua himpunan dengan urutan berbeda pada elemen yang sama?

Plus, set jauh lebih cepat dalam mencari kunci tertentu yang sangat baik dalam operasi set (dan itulah mengapa Anda memerlukan set, tetapi bukan daftar).

Jika Anda benar-benar peduli tentang indeks, simpan saja sebagai daftar. Jika Anda masih ingin melakukan operasi set pada elemen dalam banyak daftar, cara paling sederhana adalah membuat kamus untuk setiap daftar dengan kunci yang sama di set bersama dengan nilai daftar yang berisi semua indeks kunci dalam daftar asli.

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}
Po-Yao Niu
sumber
-8

Berikut cara mudah melakukannya:

x=[1,2,20,6,210]
print sorted(set(x))
Aappu Shankar
sumber
3
Ini tidak perlu mempertahankan urutan.
David Boshton