Menemukan elemen yang paling banyak terjadi dalam file yang sangat besar

12

Saya sudah sering mendengar pertanyaan wawancara ini dan saya berharap mendapatkan pendapat tentang jawaban yang bagus: Anda memiliki file besar 10+ GB dan Anda ingin mengetahui elemen mana yang paling banyak terjadi, apa cara yang baik untuk melakukan ini?

Iterasi dan melacak di peta mungkin bukan ide yang baik karena Anda menggunakan banyak memori, dan melacak sebagai entri bukan pilihan terbaik karena ketika pertanyaan ini diajukan file biasanya sudah ada.

Pikiran lain saya termasuk memisahkan file yang akan diiterasi dan diproses oleh beberapa utas dan kemudian hasilnya digabungkan, tetapi masalah memori untuk peta masih ada.

Menepuk
sumber
2
Apa sajakah elemen file? Apakah mereka string? Jika Anda mengambil karakter untuk elemen, maka peta tidak akan memiliki masalah memori. Jika elemen adalah kata-kata, sekali lagi saya pikir itu tidak akan menjadi masalah. Jika Anda memiliki semua substring yang mungkin, maka Anda dapat memiliki masalah ...
Nejc
1
Jika kondisinya "elemen yang muncul lebih dari setengah elemen total" maka ada solusi linier.
st0le
Saya yakin elemen-elemennya biasanya berupa string. Tapi saya tidak melihat bagaimana peta itu tidak menjadi masalah. Dalam kasus terburuk di mana setiap elemen unik, bukankah Anda baru saja menggandakan kebutuhan memori Anda?
Pat
1
Jika algoritma kandidat mayoritas Boyer-Moore berlaku, itu berjalan dalam waktu linier dan di tempat.
Juho

Jawaban:

6

Ketika Anda memiliki file yang sangat besar dan banyak elemen di dalamnya, tetapi elemen yang paling umum sangat umum - terjadi fraksi waktu - Anda dapat menemukannya dalam waktu linier dengan kata-kata ruang ( konstanta pada notasi sangat kecil, pada dasarnya 2 jika Anda tidak menghitung penyimpanan untuk hal-hal tambahan seperti hashing). Selain itu, ini berfungsi baik dengan penyimpanan eksternal, karena file diproses secara berurutan satu elemen pada satu waktu, dan algoritme tidak pernah "melihat ke belakang". Salah satu cara untuk melakukan ini adalah melalui algoritma klasik oleh Misra dan Gries, lihat catatan kuliah ini . Masalahnya sekarang dikenal sebagai masalah pemukul berat (elemen yang sering menjadi pemukul berat).O ( k ) O ( )>1/kO(k)O()

Asumsi bahwa elemen yang paling sering muncul fraksi dari waktu untuk sejumlah kecil mungkin tampak kuat tetapi diperlukan! Yaitu jika Anda akan memiliki akses berurutan ke file Anda (dan jika file tersebut adalah akses acak besar akan terlalu mahal), algoritma apa pun yang selalu menemukan elemen paling sering dalam jumlah lintasan yang konstan akan menggunakan spasi linear dalam jumlah elemen . Jadi jika Anda tidak berasumsi sesuatu tentang input Anda tidak bisa mengalahkan tabel hash. Asumsi bahwa unsur yang paling sering sangat sering adalah mungkin cara paling alami untuk menyiasati hasil negatif.k>1/kk

Berikut ini adalah sketsa untuk , yaitu ketika ada elemen tunggal yang muncul lebih dari separuh waktu. Kasus khusus ini dikenal sebagai algoritma suara terbanyak dan disebabkan oleh Boyer dan Moore. Kami akan menyimpan satu elemen dan satu hitungan. Inisialisasi penghitungan ke 1 dan menyimpan elemen pertama file. Kemudian proses file secara berurutan:k=2

  • jika elemen file saat ini sama dengan elemen yang disimpan, tambah hitungannya menjadi satu
  • jika elemen file saat ini berbeda dari elemen yang disimpan, kurangi jumlah per satu
  • jika jumlah yang diperbarui adalah 0, "keluarkan" elemen yang disimpan dan simpan elemen file saat ini; tambah hitungan menjadi 1
  • lanjutkan ke elemen file selanjutnya

Sedikit pemikiran tentang prosedur ini akan meyakinkan Anda bahwa jika ada elemen "mayoritas", yaitu elemen yang terjadi lebih dari separuh waktu, maka elemen tersebut akan menjadi elemen yang disimpan setelah seluruh file diproses.

Untuk umum , Anda tetap elemen dan jumlah, dan Anda menginisialisasi elemen untuk pertama elemen yang berbeda dari file dan jumlah untuk jumlah kali masing-masing elemen muncul sebelum Anda melihat th elemen yang berbeda. Kemudian Anda menjalankan prosedur yang sama: jumlah elemen meningkat setiap kali ditemukan, semua jumlah elemen berkurang jika elemen yang tidak disimpan ditemukan, dan ketika beberapa jumlah adalah nol, elemen tersebut dikeluarkan untuk mendukung elemen file saat ini. Ini adalah algoritma Misra-Gries.k - 1 k - 1 k kkk1k1kk

Anda tentu saja dapat menggunakan tabel hash untuk mengindeks elemen yang disimpan . Pada penghentian, algoritma ini dijamin untuk mengembalikan elemen yang terjadi lebih dari fraksi waktu. Ini pada dasarnya adalah yang terbaik yang dapat Anda lakukan dengan algoritma yang membuat jumlah terus-menerus melewati file dan hanya menyimpan kata-kata .1 / k O ( k )k11/kO(k)

Satu hal terakhir: setelah Anda menemukan calon "pemukul berat" (yaitu elemen sering), Anda dapat membuat satu lagi melewati file untuk menghitung frekuensi setiap elemen. Dengan cara ini Anda dapat memberi peringkat elemen di antara satu sama lain dan memverifikasi apakah semuanya terjadi lebih dari fraksi waktu (jika ada kurang dari elemen tersebut, beberapa elemen yang dikembalikan oleh algoritma mungkin positif palsu ).1 / k k - 1k1/kk1

Sasho Nikolov
sumber
Anda tidak dapat menggunakan algoritma Boyer-Moore atau Misra-Gries-Demaine. Masalah yang dinyatakan berbeda: Anda tidak mencari elemen mayoritas, tetapi untuk elemen yang kemunculannya> = dari kemunculan semua elemen. Berikut adalah contoh tandingan sederhana. Biarkan n menjadi jumlah total elemen, sehingga n = 2k + 1 . Biarkan elemen k pertama menjadi 0, elemen k berikutnya menjadi 1 dan elemen terakhir menjadi 2. Algoritma Boyer-Moore akan melaporkan elemen terakhir, 2, sebagai kandidat mayoritas potensial. Tetapi, untuk contoh khusus ini, hasilnya harus 0 atau 1.
Massimo Cafaro
@ MassimoCafaro saya tidak dapat menguraikan frase "yang kejadiannya ... elemen". Bagaimanapun, diketahui bahwa menemukan elemen yang paling sering dalam melewati memori yang diperlukan ! jadi jika Anda ingin footprtint memori kecil, Anda perlu membuat asumsi tambahan, asumsi pemukul berat menjadi yang paling alami bagi saya. Ω ( n )O(1)Ω(n)
Sasho Nikolov
Saya baru saja menunjukkan bahwa jika Anda membuat asumsi yang salah, Anda mungkin mendapatkan hasil yang salah. Apa yang lebih baik, jejak memori yang kecil dan hasil yang berpotensi tidak benar atau hasil yang benar meskipun Anda perlu lebih banyak memori? Jika saya harus memilih hasil yang berpotensi salah, saya akan menggunakan algoritma acak daripada untuk Boyer-Moore dengan asumsi sesuatu yang saya tidak tahu itu sebenarnya benar.
Massimo Cafaro
@ MassimoCafaro yang bukan tradeoff yang perlu Anda ambil. seperti yang saya tunjukkan satu pass melewati file dengan mudah memverifikasi jika asumsi puas!
Sasho Nikolov
@ MassimoCafaro dan ini hanya solusi sepele! asumsi dapat diverifikasi dengan probabilitas tinggi dengan sketsa CM tanpa lintasan tambahan.
Sasho Nikolov
3

Jawaban yang jelas tentu saja untuk menjaga peta hash dan menyimpan counter dari terjadinya elemen saat Anda menelusuri file seperti yang Nejc sudah sarankan. Ini adalah (dalam hal kompleksitas waktu) solusi optimal.

Namun, jika persyaratan ruang Anda ketat, Anda dapat melakukan pengurutan eksternal pada file dan kemudian menemukan menjalankan elemen yang sama berurutan. Yang berikut harus memiliki jejak memori yang konstan dan dapat dilakukan diΘ(nlogn).

Jernej
sumber
Bisakah Anda menguraikan lebih lanjut tentang pendekatan pengkodean Huffman? Saya telah menulis encoder Huffman sebelumnya tetapi sudah beberapa saat, bagaimana tepatnya Anda menggunakannya dalam kasus ini?
Pat
@Pat Nevermind bagian itu masih terlalu pagi dan entah bagaimana saya pikir akan masuk akal untuk mengompres input.
Jernej
1

Jika elemen yang paling umum lebih umum daripada elemen umum berikutnya dengan margin yang substansial, dan jumlah elemen yang berbeda kecil dibandingkan dengan ukuran file, Anda dapat secara acak mencicipi beberapa elemen dan mengembalikan elemen yang paling umum dalam sampel Anda.

adrianN
sumber
Selain itu, jika ada sejumlah kecil elemen yang terjadi berkali-kali, Anda dapat menemukannya dengan pengambilan sampel, dan kemudian hanya menghitung elemen-elemen ini dengan tepat.
Maks