Implementasi Trie yang efisien untuk string unicode

12

Saya telah mencari implementasi String trie yang efisien. Sebagian besar saya telah menemukan kode seperti ini:

Implementasi referensial di Jawa (per wikipedia)

Saya tidak menyukai implementasi ini karena sebagian besar dua alasan:

  1. Mereka hanya mendukung 256 karakter ASCII. Saya perlu membahas hal-hal seperti cyrillic.
  2. Mereka sangat tidak efisien memori.

Setiap node berisi array 256 referensi, yaitu 4096 byte pada mesin 64 bit di Jawa. Masing-masing node dapat memiliki hingga 256 subnode dengan masing-masing 4096 byte referensi. Jadi Trie lengkap untuk setiap string karakter ASCII 2 akan membutuhkan sedikit lebih dari 1MB. Tiga string karakter? 256MB hanya untuk array dalam node. Dan seterusnya.

Tentu saja saya tidak berniat untuk memiliki semua 16 juta tiga karakter string dalam Trie saya, jadi banyak ruang yang terbuang sia-sia. Sebagian besar array ini hanyalah referensi nol karena kapasitasnya jauh melebihi jumlah sebenarnya dari kunci yang dimasukkan. Dan jika saya menambahkan unicode, array menjadi lebih besar (char memiliki nilai 64k bukannya 256 di Java).

Apakah ada harapan membuat trie yang efisien untuk string? Saya telah mempertimbangkan beberapa peningkatan atas jenis implementasi ini:

  • Alih-alih menggunakan array referensi, saya bisa menggunakan array tipe integer primitif, yang mengindeks menjadi array referensi ke node yang ukurannya dekat dengan jumlah node aktual.
  • Saya dapat memecah string menjadi 4 bagian bit yang akan memungkinkan untuk array node ukuran 16 dengan biaya pohon yang lebih dalam.
RokL
sumber

Jawaban:

2

Untuk apa Anda menggunakan trie ini? Berapa jumlah total kata yang ingin Anda pegang, dan seberapa jarang karakter pendukungnya? Dan yang paling penting, adakah trie yang sesuai (versus peta awalan sederhana untuk daftar kata)?

Gagasan Anda tentang tabel perantara dan penggantian pointer dengan indeks akan berhasil, asalkan Anda memiliki serangkaian kata pendek yang relatif kecil dan rangkaian karakter yang jarang. Kalau tidak, Anda berisiko kehabisan ruang di tabel perantara Anda. Dan kecuali Anda melihat sekumpulan kata yang sangat kecil, Anda tidak akan benar-benar menghemat banyak ruang: 2 byte untuk yang pendek versus 4 byte untuk referensi pada mesin 32-bit. Jika Anda menggunakan JVM 64-bit, penghematan akan lebih banyak.

Gagasan Anda tentang memecah karakter menjadi potongan-potongan 4-bit mungkin tidak akan menyelamatkan Anda banyak, kecuali semua karakter yang Anda harapkan berada dalam kisaran yang sangat terbatas (mungkin OK untuk kata-kata yang terbatas pada huruf besar US-ASCII, tidak mungkin dengan corpus Unicode umum ).

Jika Anda memiliki set karakter yang jarang, maka HashMap<Character,Map<...>>mungkin implementasi terbaik Anda. Ya, setiap entri akan jauh lebih besar, tetapi jika Anda tidak memiliki banyak entri, Anda akan mendapatkan kemenangan secara keseluruhan. (sebagai catatan: saya selalu berpikir itu lucu bahwa artikel Wikipedia tentang Tries menunjukkan - mungkin masih - contoh berdasarkan struktur data hash, benar-benar mengabaikan pengorbanan ruang / waktu dari pilihan itu)

Akhirnya, Anda mungkin ingin menghindari trie sama sekali. Jika Anda melihat kumpulan kata-kata normal dalam bahasa manusia (10.000 kata digunakan secara aktif, dengan kata-kata panjangnya 4-8 karakter), Anda mungkin akan jauh lebih baik dengan a HashMap<String,List<String>, di mana kuncinya adalah seluruh awalan.

parsifal
sumber
- Referensi 8 byte pada 32-bit, 16 byte pada mesin 64-bit - Ini untuk fungsionalitas autocomplete - Mayoritas karakter dalam string berada dalam kisaran ASCII, tetapi ada beberapa karakter Eropa Tengah yang dilemparkan. Inilah sebabnya saya ingin bercabang lebih kecil dari 256, karena itu akan memotong sejumlah besar karakter. Saya tidak melihat HashMap <String, List <String>> menjadi lebih baik atau lebih cepat atau lebih sedikit memakan memori, meskipun sangat mudah untuk menulis dan menggunakan. Tapi saya akan menerima ide <Character, Map> HashMap. Akan ok untuk karakter lebih dari 128 (jarang dalam kasus saya - akan buruk untuk teks Cina).
RokL
4

jika Anda menyandikan string ke UTF8 Anda dapat menggunakan trie bercabang 256 standar dan masih kompatibel dengan unicode

Anda juga harus mencatat bahwa hanya sekitar 70 karakter dari kemungkinan 128 karakter ascii (yang semuanya dikodekan ke 1 byte dalam UTF8) akan ditemukan paling banyak yang dapat Anda optimalkan untuk itu (seperti menyertakan digraf umum di tempat karakter kontrol yang tidak digunakan) )

aneh ratchet
sumber
Saya tahu bahwa UTF8 dapat direpresentasikan seperti itu. Namun ini masih belum menyelesaikan konsumsi memori yang masih cukup tinggi. Bertukar karakter ke kisaran 256 dasar akan membutuhkan sedikit kalimat ganti, saya ragu itu akan sia-sia. Sejauh UTF-8 berjalan ... ini sebenarnya adalah masalah yang saya pikirkan saat ini. String Java menggunakan karakter UTF-16, yang bisa saya dapatkan dengan mudah, saya bisa mengkodekan byte ini dengan byte. Atau saya dapat mengonversi ke UTF-8 dan menggunakannya. Pada titik ini tidak jelas bagi saya apakah biaya konversi dari UTF-16 ke UTF-8 mahal atau tidak.
RokL
apa bahasa yang Anda bayangkan menggunakan ini di sebagian besar waktu? mencoba untuk mengoptimalkan untuk semuanya tidak mungkin (atau itu sudah dilakukan) jadi optimalkan untuk kasus umum
ratchet freak
1
Ini adalah salah satu dari sedikit kasus penggunaan di mana CESU-8 akan lebih disukai daripada UTF-8: ini keuntungan besar di sini adalah sepele untuk mendapatkan dari codepoint UTF-8 ke codepoint CESU-8 yang sesuai (sedangkan Anda perlu untuk memecahkan kode 1-2 UTF-16 codepoint untuk mendapatkan codepoint UTF-8 yang sesuai).
Joachim Sauer
1
@ratchetfreak Java. Meskipun saya pikir pertanyaannya dapat digeneralisasi ke sebagian besar bahasa. Saya kira di C Anda bisa saja melemparkan pointer ke byte*untuk mengkodekan semua jenis dalam trie bitwise.
RokL
@UMad Saya maksudkan bahasa apa yang akan digunakan oleh string input (Inggris, Prancis, Jerman, ...)
ratchet freak