การใช้ Trie ที่มีประสิทธิภาพสำหรับสตริง Unicode


12

ฉันกำลังมองหาการใช้งาน String Trie ที่มีประสิทธิภาพ ส่วนใหญ่ฉันพบรหัสเช่นนี้:

การใช้งานอ้างอิงใน Java (ต่อวิกิพีเดีย)

ฉันไม่ชอบการใช้งานเหล่านี้ด้วยเหตุผลสองประการ:

  1. รองรับอักขระได้เพียง 256 ตัวเท่านั้น ฉันต้องครอบคลุมสิ่งต่าง ๆ เช่นไซริลลิก
  2. พวกเขาไม่มีประสิทธิภาพหน่วยความจำอย่างมาก

แต่ละโหนดมีอาร์เรย์ของการอ้างอิง 256 รายการซึ่งเป็น 4096 ไบต์บนเครื่อง 64 บิตใน Java แต่ละโหนดเหล่านี้สามารถมีโหนดย่อยได้มากถึง 256 โหนดโดยมี 4096 ไบต์สำหรับการอ้างอิงแต่ละโหนด Trie แบบเต็มสำหรับสตริงอักขระ ASCII 2 ทุกตัวจะต้องมีขนาดเกิน 1MB สามสายอักขระ? 256MB สำหรับอาร์เรย์ในโหนดเท่านั้น และอื่น ๆ

แน่นอนว่าฉันไม่ได้ตั้งใจที่จะมีสายอักขระทั้งหมด 16 ล้านสายใน Trie ของฉันดังนั้นพื้นที่ว่างจำนวนมากจึงสูญเปล่า อาร์เรย์เหล่านี้ส่วนใหญ่เป็นเพียงการอ้างอิงที่เป็นโมฆะเนื่องจากความจุของพวกมันมีจำนวนเกินกว่าจำนวนคีย์ที่แทรก และถ้าฉันเพิ่มยูนิโค้ด, อาร์เรย์จะมีขนาดใหญ่ขึ้น (ถ่านมีค่า 64k แทนที่จะเป็น 256 ใน Java)

มีความหวังในการสร้างคู่ชีวิตที่มีประสิทธิภาพสำหรับสายอักขระหรือไม่? ฉันได้พิจารณาการปรับปรุงสองสามอย่างเกี่ยวกับการใช้งานประเภทนี้:

  • แทนที่จะใช้อาเรย์ของการอ้างอิงฉันสามารถใช้อาเรย์ของประเภทจำนวนเต็มดั้งเดิมซึ่งดัชนีลงในอาร์เรย์ของการอ้างอิงไปยังโหนดที่มีขนาดใกล้เคียงกับจำนวนของโหนดจริง
  • ฉันสามารถแบ่งสตริงออกเป็นส่วน 4 บิตซึ่งจะช่วยให้แถวลำดับขนาด 16 ที่ราคาของต้นไม้ลึก

คำตอบ:


2

คุณใช้ trie นี้ทำอะไร จำนวนคำทั้งหมดที่คุณวางแผนจะเก็บไว้คืออะไรและความแตกต่างของอักขระที่เป็นส่วนประกอบของพวกเขาคืออะไร และที่สำคัญที่สุดมีคู่ชีวิตที่เหมาะสม (เทียบกับแผนที่คำนำหน้าอย่างง่ายเพื่อแสดงรายการคำ)?

ความคิดของคุณเกี่ยวกับตารางกลางและการแทนที่ตัวชี้ด้วยดัชนีจะใช้งานได้โดยที่คุณมีชุดคำศัพท์สั้น ๆ และชุดอักขระที่กระจัดกระจาย มิฉะนั้นคุณอาจเสี่ยงต่อการขาดพื้นที่ในตารางกลาง และหากคุณไม่ได้มองชุดคำที่เล็กมากคุณจะไม่ประหยัดพื้นที่มากขนาดนั้น: 2 ไบต์สำหรับสั้น ๆ กับ 4 ไบต์สำหรับการอ้างอิงบนเครื่อง 32- บิต หากคุณใช้ JVM แบบ 64 บิตการประหยัดจะมีมากขึ้น

ความคิดของคุณเกี่ยวกับการแบ่งอักขระออกเป็นชิ้น ๆ 4 บิตอาจไม่ช่วยให้คุณประหยัดมากนักเว้นแต่ตัวละครที่คาดหวังของคุณจะอยู่ในช่วงที่ จำกัด อย่างมาก (อาจตกลงสำหรับคำที่ จำกัด ตัวพิมพ์ใหญ่ US-ASCII ไม่น่าเป็นไปได้ )

หากคุณมีชุดอักขระเบาบางแล้ว a HashMap<Character,Map<...>>อาจเป็นการใช้งานที่ดีที่สุดของคุณ ใช่แต่ละรายการจะมีขนาดใหญ่กว่ามาก แต่ถ้าคุณไม่มีหลายรายการคุณจะได้รับชัยชนะโดยรวม (เป็นหมายเหตุด้าน: ฉันมักจะคิดว่ามันตลกที่บทความ Wikipedia เกี่ยวกับ Tries แสดงให้เห็นว่า - อาจจะยังคง - ตัวอย่างจากโครงสร้างข้อมูลที่ถูกแฮชโดยไม่สนใจการแลกเปลี่ยนพื้นที่ / เวลาของตัวเลือกนั้นอย่างสมบูรณ์)

ในที่สุดคุณอาจต้องการหลีกเลี่ยงคู่ชีวิตทั้งหมด หากคุณกำลังมองหาคลังคำศัพท์ปกติในภาษามนุษย์ (10,000 คำในการใช้งานโดยมีความยาวตัวอักษร 4-8 ตัว) คุณอาจจะรู้สึกดีกว่าด้วย a HashMap<String,List<String>โดยที่สำคัญคือคำนำหน้าทั้งหมด


- การอ้างอิงคือ 8 ไบต์ใน 32- บิต, 16 ไบต์บนเครื่อง 64- บิต - สำหรับฟังก์ชั่นเติมข้อความอัตโนมัติ - ส่วนใหญ่ของตัวละครในสตริงอยู่ในช่วง ASCII แต่มีตัวละครยุโรปกลางสองสามตัวโยนลงมานี่คือเหตุผลว่าทำไม มากกว่า 256 เพราะมันจะตัดอักขระจำนวนมากออก ฉันไม่เห็น HashMap <String, List <String>> ดีกว่าหรือเร็วกว่าหรือใช้หน่วยความจำน้อย แต่ถึงแม้ว่าจะเขียนและใช้งานได้ง่ายจริงๆ แต่ฉันจะยอมรับแนวคิด HashMap <Character, Map> จะโอเคสำหรับตัวอักษรเกิน 128 (หายากในกรณีของฉัน - จะไม่ดีสำหรับข้อความภาษาจีน)
RokL

4

หากคุณเข้ารหัสสตริงลงใน UTF8 คุณสามารถใช้ 256 branching trie และยังคงใช้งานได้กับ unicode

นอกจากนี้คุณควรทราบว่ามีเพียง 70 ตัวหรือมากกว่านั้นจาก 128 Ascii ตัวอักษร (ซึ่งเข้ารหัสทั้งหมด 1 ไบต์ใน UTF8) จะพบมากที่สุดอย่างหนักที่คุณสามารถปรับให้เหมาะสมสำหรับการนั้น (เช่นรวม digraphs ทั่วไปแทนอักขระควบคุมที่ไม่ได้ใช้ )


ฉันรู้ว่า UTF8 สามารถแสดงได้เช่นนั้น อย่างไรก็ตามนี่ยังไม่สามารถแก้ปัญหาการใช้หน่วยความจำซึ่งยังค่อนข้างสูง การแลกเปลี่ยนอักขระในช่วงพื้นฐาน 256 จะต้องใช้ประโยคสลับกันเล็กน้อยฉันสงสัยว่ามันจะคุ้มค่า เท่าที่ UTF-8 ไป ... นี่เป็นปัญหาที่ฉันครุ่นคิดอยู่ตอนนี้ Java String ใช้ตัวอักษร UTF-16 ซึ่งฉันสามารถหาได้ง่ายฉันสามารถเข้ารหัสไบต์เหล่านี้เป็นไบต์ หรือฉันสามารถแปลงเป็น UTF-8 และใช้มัน ณ จุดนี้มันไม่ชัดเจนสำหรับฉันถ้าค่าใช้จ่ายในการแปลงจาก UTF-16 ถึง UTF-8 นั้นเป็นสิ่งต้องห้ามหรือไม่
RokL

ภาษาที่คุณคาดว่าจะใช้ในครั้งนี้คืออะไร? พยายามที่จะปรับให้เหมาะสมสำหรับทุกอย่างเป็นไปไม่ได้ (หรือจะทำไปแล้ว) ดังนั้นปรับให้เหมาะกับกรณีทั่วไป
ratchet freak

1
นี่เป็นหนึ่งในกรณีการใช้งานที่น้อยมากที่CESU-8น่าจะดีกว่า UTF-8: มันเป็นข้อได้เปรียบที่ยิ่งใหญ่ที่นี่คือมันเป็นเรื่องเล็กน้อยที่จะได้รับจาก codepoint UTF-8 ถึง codepoint CESU-8 ที่สอดคล้องกัน (ในขณะที่คุณต้องการ เพื่อถอดรหัส codepoints UTF-16 ถึง 1-2 เพื่อไปยัง codepoints UTF-8 ที่สอดคล้องกัน)
Joachim Sauer

1
@ratchetfreak Java แม้ว่าฉันคิดว่าคำถามนี้สามารถนำไปใช้กับภาษาส่วนใหญ่ได้ ฉันเดาว่าใน C คุณก็แค่ชี้ไปที่byte*เพื่อเข้ารหัสประเภทใด ๆ
RokL

@UMad ฉันหมายถึงสิ่งที่ภาษาที่สายป้อนจะอยู่ใน (อังกฤษ, ฝรั่งเศส, เยอรมัน, ... )
ratchet freak
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.