Diberikan daftar bilangan bulat, saya ingin mencari nomor mana yang paling dekat dengan nomor yang saya berikan input:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Apakah ada cara cepat untuk melakukan ini?
Jawaban:
Jika kita tidak yakin bahwa daftar diurutkan, kita bisa menggunakan built-in
min()
fungsi , untuk menemukan elemen yang memiliki jarak minimum dari jumlah yang ditentukan.Perhatikan bahwa ini juga berfungsi dengan dikt dengan kunci int, seperti
{1: "a", 2: "b"}
. Metode ini membutuhkan waktu O (n).Jika daftar sudah diurutkan, atau Anda bisa membayar harga untuk mengurutkan array sekali saja, gunakan metode pembagian dua bagian yang diilustrasikan dalam jawaban @ Lauritz yang hanya membutuhkan waktu O (log n) (perhatikan namun memeriksa apakah daftar yang sudah diurutkan adalah O (n) dan pengurutan adalah O (n log n).)
sumber
O(n)
, di mana sedikit peretasanbisect
akan memberi Anda peningkatan besar-besaranO(log n)
(jika array input Anda diurutkan).min
, jalankan di atas kamus (items()
) alih-alih daftar, dan kembalikan kunci alih-alih nilai pada akhirnya.numpy.argmin
alih-alihmin
untuk mendapatkan indeks alih-alih nilai.Saya akan mengganti nama fungsi
take_closest
agar sesuai dengan konvensi penamaan PEP8.Jika yang Anda maksud adalah eksekusi cepat dan bukan penulisan cepat,
min
seharusnya tidak menjadi senjata pilihan Anda, kecuali dalam satu kasus penggunaan yang sangat sempit. Themin
solusi perlu memeriksa setiap nomor dalam daftar dan melakukan perhitungan untuk setiap nomor.bisect.bisect_left
Sebaliknya, menggunakan hampir selalu lebih cepat."Hampir" berasal dari fakta yang
bisect_left
mengharuskan daftar harus diurutkan untuk bekerja. Mudah-mudahan, use case Anda sedemikian rupa sehingga Anda dapat mengurutkan daftar sekali dan kemudian membiarkannya sendiri. Meskipun tidak, selama Anda tidak perlu menyortir sebelum setiap kali Anda menelepontake_closest
,bisect
modul tersebut kemungkinan akan keluar di atas. Jika Anda ragu, coba keduanya dan lihat perbedaan dunia nyata.Bisect bekerja dengan berulang kali membagi dua daftar dan mencari tahu bagian mana yang
myNumber
harus diambil dengan melihat nilai tengahnya. Ini berarti ia memiliki waktu berjalan O (log n) sebagai kebalikan dari O (n) waktu berjalan jawaban tertinggi yang dipilih . Jika kami membandingkan kedua metode dan menyediakan keduanya dengan diurutkanmyList
, ini adalah hasilnya:Jadi dalam tes khusus ini,
bisect
hampir 20 kali lebih cepat. Untuk daftar yang lebih panjang, perbedaannya akan lebih besar.Bagaimana jika kita meratakan lapangan bermain dengan menghapus prasyarat yang
myList
harus diurutkan? Katakanlah kita mengurutkan salinan daftar setiap kalitake_closest
dipanggil, sementara membiarkanmin
solusinya tidak berubah. Menggunakan daftar 200-item dalam tes di atas,bisect
solusinya masih yang tercepat, meskipun hanya sekitar 30%.Ini adalah hasil yang aneh, mengingat langkah penyortirannya adalah O (n log (n)) ! Satu-satunya alasan
min
yang masih kalah adalah bahwa penyortiran dilakukan dalam kode c yang sangat dioptimalkan, sementaramin
harus bersusah payah memanggil fungsi lambda untuk setiap item. SeiringmyList
bertambahnya ukuran,min
solusinya pada akhirnya akan lebih cepat. Perhatikan bahwa kami harus menumpuk segalanya agarmin
solusi dapat menang.sumber
a=range(-1000,1000,2);random.shuffle(a)
Anda akan menemukan itutakeClosest(sorted(a), b)
akan menjadi lebih lambat.getClosest
mungkin dipanggil lebih dari sekali untuk setiap jenis, ini akan lebih cepat, dan untuk kasus penggunaan semacam-sekali, itu adalah no-brainer.myList
sudahnp.array
maka gunakannp.searchsorted
di tempatbisect
lebih cepat.Sebuah lambda adalah cara khusus menulis "anonim" fungsi (fungsi yang tidak memiliki nama). Anda dapat menetapkan nama apa saja yang Anda inginkan karena lambda adalah ekspresi.
Cara "panjang" untuk menulis hal di atas adalah:
sumber
Kode ini akan memberi Anda indeks nomor terdekat dalam daftar.
Solusi yang diberikan oleh KennyTM adalah keseluruhan terbaik, tetapi dalam kasus Anda tidak dapat menggunakannya (seperti brython), fungsi ini akan melakukan pekerjaan
sumber
Iterate atas daftar dan bandingkan nomor terdekat saat ini dengan
abs(currentNumber - myNumber)
:sumber
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
. Lebih baik menyimpan nilai itu sebelumnya.Penting untuk dicatat bahwa ide saran Lauritz menggunakan bisect sebenarnya tidak menemukan nilai terdekat di MyList ke MyNumber. Sebagai gantinya, membagi dua menemukan nilai berikutnya dalam urutan setelah MyNumber di MyList. Jadi dalam kasus OP Anda benar-benar mendapatkan posisi 44 kembali, bukan posisi 4.
Untuk mendapatkan nilai yang paling dekat dengan 5 Anda bisa mencoba mengubah daftar menjadi array dan menggunakan argmin dari numpy seperti itu.
Saya tidak tahu seberapa cepat ini akan terjadi, tebakan saya akan "tidak terlalu".
sumber
np.searchsorted
sebagai gantibisect_left
. Dan @ Kanat benar - solusi Lauritz tidak termasuk kode yang memilih yang mana dari dua kandidat lebih dekat.Memperluas jawaban Gustavo Lima. Hal yang sama dapat dilakukan tanpa membuat daftar yang sama sekali baru. Nilai-nilai dalam daftar dapat diganti dengan diferensial saat
FOR
loop berlanjut.sumber
Jika saya dapat menambahkan jawaban @ Lauritz
Agar tidak ada galat run, jangan lupa menambahkan kondisi sebelum
bisect_left
baris:jadi kode lengkapnya akan terlihat seperti:
sumber