Apakah ada cara yang lebih elegan untuk mengekspresikan ((x == a dan y == b) atau (x == b dan y == a))?

108

Saya mencoba untuk mengevaluasi ((x == a and y == b) or (x == b and y == a))dengan Python, tetapi sepertinya agak bertele-tele. Apakah ada cara yang lebih elegan?

LetEpsilonBeLessThanZero
sumber
8
Tergantung pada jenis objeknya x,y, a,b: apakah int / float / string, objek arbitrer, atau apa? Jika mereka adalah tipe builtin dan dimungkinkan untuk menjaga keduanya x,ydan a,bdalam urutan, maka Anda dapat menghindari cabang kedua. Perhatikan bahwa membuat himpunan akan menyebabkan masing-masing dari empat elemen x,y, a,bmenjadi hash, yang mungkin atau mungkin tidak sepele atau memiliki implikasi kinerja tergantung sepenuhnya pada jenis objek apa mereka.
smci
18
Ingatlah orang-orang yang bekerja dengan Anda dan jenis proyek yang Anda kembangkan. Saya menggunakan Python kecil di sana-sini. Jika seseorang mengkodekan salah satu jawaban di sini saya benar-benar harus google apa yang terjadi. Persyaratan Anda dapat dibaca cukup banyak apa pun yang terjadi.
Areks
3
Saya tidak akan repot dengan penggantian. Mereka lebih singkat, tetapi tidak sejelas (IMHO) dan saya pikir semua akan lebih lambat.
Barmar
22
Ini adalah masalah XYAB klasik.
Tashus
5
Dan secara umum jika Anda berurusan dengan koleksi objek yang tidak tergantung pesanan, gunakan set / dicts / etc. Ini benar-benar masalah XY, kita perlu melihat basis kode yang lebih besar itu diambil. Saya setuju bahwa ((x == a and y == b) or (x == b and y == a))mungkin terlihat yukky, tetapi 1) maksudnya sangat jelas dan dapat dipahami oleh semua programmer non-Python, tidak samar 2) interpreter / kompiler akan selalu menanganinya dengan baik dan pada dasarnya tidak pernah dapat menghasilkan kode non-performant, tidak seperti alternatif. Jadi, 'lebih elegan' juga dapat memiliki kelemahan serius.
smci

Jawaban:

151

Jika elemen hashable, Anda bisa menggunakan set:

{a, b} == {y, x}
Dani Mesejo
sumber
18
@ Ya Tuhan tidak, karena ada tepat dua item di set kanan. Jika keduanya a, tidak ada b, dan jika keduanya b, tidak ada a, dan dalam kedua kasus set tidak bisa sama.
hobbs
12
Di sisi lain, jika kami memiliki tiga elemen di setiap sisi dan kami perlu menguji apakah mereka dapat dicocokkan satu lawan satu, maka set tidak akan berfungsi. {1, 1, 2} == {1, 2, 2}. Pada titik itu, Anda perlu sortedatau Counter.
user2357112 mendukung Monica
11
Menurut saya ini sulit untuk dibaca (jangan baca "{}" sebagai "()"). Cukup berkomentar - dan kemudian tujuannya hilang.
Édouard
9
Saya tahu itu python tetapi membuat dua set baru hanya untuk membandingkan nilai-nilai yang tampak seperti berlebihan bagi saya ... Cukup tentukan fungsi yang melakukannya dan panggil bila diperlukan.
marxin
5
@marxin Sebuah fungsi akan lebih besar daripada 2 konstruksi himpunan sederhana. Dan kurang terbaca dengan 4 argumen.
Gloweye
60

Saya pikir yang terbaik yang bisa Anda dapatkan adalah mengemasnya menjadi tupel:

if (a, b) == (x, y) or (a, b) == (y, x)

Atau, mungkin membungkusnya dalam satu set pencarian

if (a, b) in {(x, y), (y, x)}

Hanya karena disebutkan oleh beberapa komentar, saya melakukan beberapa pengaturan waktu, dan tupel dan set muncul untuk tampil secara identik di sini ketika pencarian gagal:

from timeit import timeit

x = 1
y = 2
a = 3
b = 4

>>> timeit(lambda: (a, b) in {(x, y), (y, x)}, number=int(5e7))
32.8357742

>>> timeit(lambda: (a, b) in ((x, y), (y, x)), number=int(5e7))
31.6169182

Meskipun tuple sebenarnya lebih cepat ketika pencarian berhasil:

x = 1
y = 2
a = 1
b = 2

>>> timeit(lambda: (a, b) in {(x, y), (y, x)}, number=int(5e7))
35.6219458

>>> timeit(lambda: (a, b) in ((x, y), (y, x)), number=int(5e7))
27.753138700000008

Saya memilih untuk menggunakan set karena saya sedang melakukan pencarian keanggotaan, dan secara konseptual satu set lebih cocok untuk case-use daripada tuple. Jika Anda mengukur perbedaan yang signifikan antara kedua struktur dalam use case tertentu, gunakan yang lebih cepat. Saya tidak berpikir kinerja adalah faktor di sini.

Carcigenicate
sumber
12
Metode tuple terlihat sangat bersih. Saya akan khawatir tentang dampak kinerja menggunakan set. Anda bisa melakukannya if (a, b) in ((x, y), (y, x))?
Brilliand
18
@Brilliand Jika Anda khawatir tentang dampak kinerja, Python bukan bahasa untuk Anda. :-D
Sneftel
Saya sangat suka metode pertama, dua perbandingan tuple. Sangat mudah untuk menguraikan (satu klausa pada satu waktu) dan setiap klausa sangat sederhana. Dan yang paling penting, itu harus relatif efisien.
Matthieu M.
Apakah ada alasan untuk memilih setsolusi dalam jawaban untuk solusi tuple dari @Brilliand?
user1717828
Satu set mensyaratkan bahwa objek dapat di hashable, sehingga tuple akan menjadi solusi yang lebih umum dengan dependensi yang lebih sedikit. Performa, walaupun mungkin secara umum tidak penting, mungkin juga terlihat sangat berbeda ketika berhadapan dengan objek besar dengan hashing mahal dan pemeriksaan kesetaraan.
Dukeling
31

Tuples membuatnya sedikit lebih mudah dibaca:

(x, y) == (a, b) or (x, y) == (b, a)

Ini memberikan petunjuk: kami memeriksa apakah urutannya x, ysama dengan urutannya a, btetapi mengabaikan pemesanan. Itu hanya menetapkan kesetaraan!

{x, y} == {a, b}
Thomas
sumber
,membuat tuple, bukan daftar. begitu (x, y)dan (a, b)tupel, sama dengan x, ydan a, b.
kissgyorgy
Maksud saya "daftar" dalam arti "urutan elemen yang dipesan", bukan dalam arti listtipe Python . Diedit karena memang ini membingungkan.
Thomas
26

Jika item tidak hashable, tetapi mendukung perbandingan pemesanan, Anda dapat mencoba:

sorted((x, y)) == sorted((a, b))
jasonharper
sumber
Karena ini berfungsi dengan item hashable juga (kan?), Ini solusi yang lebih global.
Carl Witthoft
5
@CarlWitthoft: Tidak. Ada beberapa tipe yang dapat di hash tetapi tidak dapat diurutkan:, complexmisalnya.
dan04
26

Cara yang paling elegan, menurut saya, adalah

(x, y) in ((a, b), (b, a))

Ini adalah cara yang lebih baik daripada menggunakan set, yaitu {a, b} == {y, x}, seperti ditunjukkan dalam jawaban lain karena kita tidak perlu berpikir jika variabel-variabel tersebut dapat di hashable.

Wagner Macedo
sumber
Apa bedanya dengan jawaban sebelumnya ?
scohe001
5
@ scohe001 Menggunakan tuple di mana jawaban sebelumnya menggunakan set. Jawaban sebelumnya memang mempertimbangkan solusi ini, tetapi menolak untuk menyebutkannya sebagai rekomendasi.
Brilliand
25

Jika ini angka, Anda bisa menggunakannya (x+y)==(a+b) and (x*y)==(a*b).

Jika ini adalah barang yang sebanding, Anda bisa menggunakannya min(x,y)==min(a,b) and max(x,y)==max(a,b).

Tetapi ((x == a and y == b) or (x == b and y == a))jelas, aman, dan lebih umum.

lhf
sumber
2
Haha, polinomial simetris ftw!
Carsten S
2
Saya pikir ini menciptakan risiko kesalahan overflow.
Razvan Socol
3
Ini adalah jawaban yang benar, khususnya kalimat terakhir. Sederhananya, ini seharusnya tidak memerlukan beberapa iterables baru atau sesuatu seperti itu. Bagi mereka yang ingin menggunakan set, lihat implementasi dari set objek dan kemudian bayangkan mencoba menjalankan ini dalam loop yang ketat ...
Z4-tier
3
@RazvanSocol OP tidak mengatakan tipe data apa, dan jawaban ini memenuhi syarat solusi yang bergantung pada tipe.
Z4-tier
21

Sebagai generalisasi untuk lebih dari dua variabel yang bisa kita gunakan itertools.permutations. Itu bukan

(x == a and y == b and z == c) or (x == a and y == c and z == b) or ...

kita bisa menulis

(x, y, z) in itertools.permutations([a, b, c])

Dan tentu saja dua versi variabel:

(x, y) in itertools.permutations([a, b])
seorang tamu
sumber
5
Jawaban yang bagus Perlu ditunjukkan di sini (bagi mereka yang tidak melakukan banyak hal dengan generator sebelumnya) bahwa ini sangat hemat memori, karena hanya satu permutasi yang dibuat pada satu waktu, dan cek "in" akan berhenti dan kembali Benar segera setelah kecocokan ditemukan.
robertlayton
2
Perlu juga ditunjukkan bahwa kompleksitas metode ini adalah O(N*N!); Untuk 11 variabel, ini bisa memakan waktu lebih dari satu detik untuk selesai. (Saya memposting metode yang lebih cepat, tetapi masih membutuhkan waktu O(N^2), dan mulai mengambil alih satu detik pada variabel 10k; Jadi sepertinya ini dapat dilakukan dengan cepat atau secara umum (wrt. Hashability / keteraturan), tetapi tidak keduanya: P)
Aleksi Torhamo
15

Anda dapat menggunakan tupel untuk mewakili data Anda dan kemudian memeriksa set inklusi, seperti:

def test_fun(x, y):
    test_set = {(a, b), (b, a)}

    return (x, y) in test_set
Nils Müller
sumber
3
Ditulis sebagai one-liner dan dengan daftar (untuk item yang tidak dapat diacak), saya akan menganggap ini sebagai jawaban terbaik. (Meskipun saya seorang pemula Python lengkap).
Édouard
1
@ Édouard Sekarang ada jawaban lain yang intinya (hanya menggunakan tuple dan bukan daftar, yang lebih efisien).
Brilliand
10

Anda sudah mendapatkan solusi yang paling mudah dibaca . Ada cara lain untuk mengekspresikan ini, mungkin dengan karakter yang lebih sedikit, tetapi mereka kurang mudah dibaca.

Bergantung pada nilai yang benar-benar mewakili taruhan terbaik Anda adalah untuk membungkus cek dalam fungsi dengan nama yang berbicara . Sebagai alternatif atau tambahan, Anda dapat memodelkan objek x, y dan a, b masing-masing dalam objek kelas tinggi khusus yang kemudian dapat Anda bandingkan dengan logika perbandingan dalam metode pemeriksaan kesetaraan kelas atau fungsi kustom khusus.

Frank Hopkins
sumber
3

Tampaknya OP hanya peduli dengan kasus dua variabel, tetapi karena StackOverflow juga untuk mereka yang mencari pertanyaan yang sama nanti, saya akan mencoba untuk menangani kasus generik di sini secara rinci; Satu jawaban sebelumnya sudah menggunakan jawaban umum itertools.permutations(), tetapi metode itu mengarah ke O(N*N!)perbandingan, karena masing-masing ada N!permutasi dengan Nitem. (Ini adalah motivasi utama untuk jawaban ini)

Pertama, mari kita simpulkan bagaimana beberapa metode dalam jawaban sebelumnya berlaku untuk kasus umum, sebagai motivasi untuk metode yang disajikan di sini. Saya akan menggunakan Auntuk merujuk (x, y)dan Bmerujuk (a, b), yang bisa berupa tupel dengan panjang yang sewenang-wenang (tapi sama).

set(A) == set(B)cepat, tetapi hanya berfungsi jika nilainya hashable dan Anda dapat menjamin bahwa salah satu tupel tidak mengandung nilai duplikat apa pun. (Mis. {1, 1, 2} == {1, 2, 2}, Seperti yang ditunjukkan oleh @ user2357112 di bawah jawaban @Daniel Mesejo)

Metode sebelumnya dapat diperluas untuk bekerja dengan nilai duplikat dengan menggunakan kamus dengan jumlah, alih-alih set: (Ini masih memiliki batasan bahwa semua nilai harus dapat hashable, jadi mis. Nilai yang dapat diubah seperti listtidak akan berfungsi)

def counts(items):
    d = {}
    for item in items:
        d[item] = d.get(item, 0) + 1
    return d

counts(A) == counts(B)

sorted(A) == sorted(B)tidak memerlukan nilai hashable, tetapi sedikit lebih lambat, dan sebaliknya membutuhkan nilai yang bisa dipesan. (Jadi mis. Tidak complexakan bekerja)

A in itertools.permutations(B)tidak memerlukan nilai hashable atau orderable, tetapi seperti yang telah disebutkan, ia memiliki O(N*N!)kompleksitas, sehingga bahkan dengan hanya 11 item, dapat membutuhkan waktu lebih dari satu detik untuk menyelesaikannya.

Jadi, apakah ada cara untuk menjadi umum, tetapi apakah itu jauh lebih cepat? Mengapa ya, dengan "secara manual" memeriksa bahwa ada jumlah yang sama dari setiap item: (Kompleksitas yang satu ini O(N^2), jadi ini juga tidak baik untuk input besar; Pada mesin saya, 10k item dapat membutuhkan waktu satu detik - tetapi dengan input yang lebih kecil, seperti 10 item, ini sama cepatnya dengan yang lain)

def unordered_eq(A, B):
    for a in A:
        if A.count(a) != B.count(a):
            return False
    return True

Untuk mendapatkan kinerja terbaik, orang mungkin ingin mencoba dictmetode berbasis pertama, kembali ke sortedmetode berbasis jika itu gagal karena nilai-nilai yang tidak dapat dihancurkan, dan akhirnya jatuh kembali ke countmetode berbasis jika itu juga gagal karena nilai-nilai tidak teratur.

Aleksi Torhamo
sumber