Ефективна реалізація Trie для рядків Unicode


12

Я шукав ефективну реалізацію String trie. Здебільшого я знайшов такий код:

Референційна реалізація на Java (за вікіпедією)

Мені не подобаються такі реалізації з двох причин:

  1. Вони підтримують лише 256 символів ASCII. Мені потрібно висвітлювати такі речі, як кирилиця.
  2. Вони надзвичайно малоефективні.

Кожен вузол містить масив з 256 посилань, що становить 4096 байт на 64-бітній машині в Java. Кожен з цих вузлів може мати до 256 підвузлів з 4096 байтами посилань кожен. Таким чином, повне Trie для кожного символьного рядка ASCII 2 вимагатиме трохи більше 1 Мб. Три символьні рядки? 256 Мб просто для масивів у вузлах. І так далі.

Звичайно, я не маю наміру мати в моєму Трі 16 мільйонів трьох рядків символів, тому багато місця просто витрачено. Більшість цих масивів є лише нульовими посиланнями, оскільки їх ємність значно перевищує фактичну кількість вставлених ключів. І якщо я додаю unicode, масиви стають ще більшими (char має 64k значення замість 256 у Java).

Чи є надія зробити ефективну трійку для струн? Я розглянув декілька вдосконалень щодо таких типів реалізацій:

  • Замість використання масиву посилань я міг би використовувати масив примітивного цілого типу, який індексує масив посилань на вузли, розмір яких близький до кількості фактичних вузлів.
  • Я міг би розбити рядки на 4 бітні частини, що дозволило б створити масиви вузлів розміром 16 ціною глибшого дерева.

Відповіді:


2

Для чого ти використовуєш цю трійку? Яка загальна кількість слів, які ви плануєте дотримати, і яка обмеженість їх складових символів? І найголовніше, чи підходить трійка (проти простої карти префікса до списку слів)?

Ваша ідея проміжної таблиці та заміни покажчиків на індекси спрацює за умови, що у вас порівняно невеликий набір коротких слів та розріджений набір символів. В іншому випадку ви ризикуєте не вистачити місця у проміжному столі. І якщо ви не дивитеся на надзвичайно малий набір слів, ви дійсно не заощадите стільки місця: 2 байти за короткий та 4 байти для посилання на 32-бітній машині. Якщо ви працюєте на 64-бітному JVM, економія буде більше.

Ваша ідея про розбиття персонажів на 4-бітні шматки, ймовірно, не допоможе вам значно, якщо тільки всі ваші очікувані символи не будуть в надзвичайно обмеженому діапазоні (можливо, добре для слів, обмежених великими літерами US-ASCII, мабуть, загальним корпусом Unicode ).

Якщо у вас набір розріджених символів, то це HashMap<Character,Map<...>>може бути найкращою реалізацією. Так, кожен запис буде значно більшим, але якщо у вас не буде багато записів, ви отримаєте загальний виграш. (як бічна зауваження: я завжди вважав смішним, що стаття у Вікіпедії про Триес показала - можливо, і все-таки - приклад, заснований на хешированій структурі даних, повністю ігноруючи проміжок часу / часу цього вибору)

Нарешті, ви можете взагалі уникнути трійки. Якщо ви дивитесь на корпус звичайних слів людською мовою (10000 слів в активному використанні, зі словами 4-8 символів), вам, ймовірно, буде МНОГО краще з а HashMap<String,List<String>, де ключовим є весь префікс.


- Посилання - це 8 байт на 32-бітних, 16 байт на 64-розрядних машинах - Це для функцій автозаповнення - Більшість символів у рядках знаходиться в діапазоні ASCII, але в них закинуто кілька центральноєвропейських символів. Тому я хотів менших розгалужень. ніж 256, оскільки це виріже велику кількість символів. Я не бачу, щоб HashMap <String, List <String>> був кращим або швидшим або менш затратним на пам’ять, хоч і дуже простий для запису та використання. Але я прийму за ідею HashMap <Символ, Карта>. Було б нормально для знаків понад 128 (рідко в моєму випадку - це було б погано для китайського тексту).
RokL

4

якщо ви кодуєте рядки в UTF8, ви можете використовувати стандартне триє розгалуження 256 і все ще бути сумісним з unicode

також слід зауважити, що лише 70 або більше символів з можливих 128 символів Ascii (які всі кодують до 1 байта в UTF8) будуть знайдені найбільш важко, ви можете оптимізувати для цього (наприклад, включіть загальні діаграми замість невикористаних символів керування )


Я знаю, що UTF8 можна представити так. Однак це все ще не вирішує споживання пам'яті, яке все ще досить високе. Заміна символів на базовий діапазон 256 вимагає зовсім небагато пропозицій переключення, я сумніваюся, що це того варто. Що стосується UTF-8 ... це фактично питання, над яким я зараз розмірковую. Java String використовує UTF-16 символів, які я можу легко отримати, я можу кодувати ці байти за байтом. Або я можу конвертувати в UTF-8 і використовувати це. На даний момент мені незрозуміло, чи варто перехід з UTF-16 в UTF-8 непомірний чи ні.
RokL

Яку мову ви передбачаєте, використовуючи це більшу частину часу? намагатися оптимізувати все неможливо (або це вже було б зроблено), тому оптимізуйте для звичайного випадку
храповика виродка

1
Це один з небагатьох випадків використання, коли CESU-8 був би кращим перед UTF-8: тут величезна перевага в тому, що тривіально дістатися з кодової точки UTF-8 до відповідної кодової точки CESU-8 (тоді як вам знадобиться декодувати 1-2 кодові точки UTF-16, щоб дістатися до відповідних кодових точок UTF-8).
Йоахім Зауер

1
@ratchetfreak Java. Хоча я думаю, що питання можна узагальнити на більшості мов. Я думаю, що в C ви можете просто кинути покажчик, byte*щоб кодувати будь-який тип у побітній трійці.
RokL

@UMad Я мав на увазі, якими мовами будуть вхідні рядки (англійська, французька, німецька, ...)
ratchet freak
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.