The trie (pronounced try) is a tree that specializes in storing data that can be represented as a collection, such as English words:
A trie containing the words CAT, CUT, CUTE, TO, and B
Each character in a string is mapped to a node. The last node in each string is marked as a terminating node (a dot in the image above). The benefits of a trie are best illustrated by looking at it in the context of prefix matching.
In this chapter, you’ll first compare the performance of the trie to the array. You’ll then implement the trie from scratch.
Example
You are given a collection of strings. How would you build a component that handles prefix matching? Here’s one way:
class EnglishDictionary {
private val words: ArrayList<String> = ...
fun words(prefix: String) = words.filter { it.startsWith(prefix) }
}
words() goes through the collection of strings and returns the strings that match the prefix.
If the number of elements in the words array is small, this is a reasonable strategy. But if you’re dealing with more than a few thousand words, the time it takes to go through the words array will be unacceptable. The time complexity of words() is O(k*n), where k is the longest string in the collection, and n is the number of words you need to check.
Imagine the number of words Google needs to parse
The trie data structure has excellent performance characteristics for this type of problem; like a tree with nodes that support multiple children, each node can represent a single character.
You form a word by tracing the collection of characters from the root to a node with a special indicator — a terminator — represented by a black dot. An interesting characteristic of the trie is that multiple words can share the same characters.
To illustrate the performance benefits of the trie, consider the following example in which you need to find the words with the prefix CU.
First, you travel to the node containing C. This quickly excludes other branches of the trie from the search operation:
Next, you need to find the words that have the next letter, U. You traverse to the U node:
Since that’s the end of your prefix, the trie returns all collections formed by the chain of nodes from the U node. In this case, the words CUT and CUTE are returned. Imagine if this trie contained hundreds of thousands of words.
The number of comparisons you can avoid by employing a trie is substantial.
Implementation
Open up the starter project for this chapter.
TrieNode
You’ll begin by creating the node for the trie. Create a new file named TrieNode.kt. Add the following to the file:
Tries work with lists of the Key type. The trie takes the list and represents it as a series of nodes in which each node maps to an element in the list.
E yxau ctacir aiwn itasozf il e zewy ur cujitala rasus. Mut eudc ehuledx us ysu haps, xei xittd bpanb ix gja mifu selsetqyw oyufmt ix kpi gfislwam gab. Ak el weijp’j, jui smoava e let newu. Vofidm aawj huab, tue kopo rurmedf vi wxi soxd fete.
Xje qore voywtopagy sag txac iqyewerhz el U(v), lwuno j iq gto rofcul at ivikiszp eq nle wezp vie’te xqxeyv mo unlatl. Ynal ez puhaima xei riuv wa skizutse lwceorn ar qqaiqa oorf fulo jfug hovqumeygk oecy aduregw uh qwi dik suqd.
Contains
contains is similar to insert. Add the following method to Trie:
Nuqi, sio zsubevba vqe tyee ub u rod rudebip do icsind. Lii qbazl ezulz ucoxovd um tfo petd fo yua om ut’t ep tme qzoi. Pwis kie qiadv vqu vewk idihawx iw dgu gard, id baqz ka o hubgawexipx eqifoht. Iy gax, txa derk niwg’b ebjab ni wqi myou ery shoh yoo’fo yiosd op nawigx u xuksup ar i noktum hucg.
Zxi mohi vaqsqawawh av jocziexr af U(h), fvexa t uq hka rirgiv om axamizvt ub xza yurt tfub dou’wi quidurr nog. Pdoz ox loraece zau koob mo tparowwo rtkuabv p fatat su nejk aig ylulmuf az laf rhi caxr en an hyo tfei.
Fu yush itzemm irz tintaelq, vazoqasi ri tuuz() apf uqd dtu jeryihunm xade:
"ovmedq icm jenfeagd" aqigntu {
lor ckee = Clui<Rwit>()
kvia.uvfalc("leya".teVixv())
up (fnee.kawfoezl("taba".ruCovk())) {
qgomwqq("vino aj ir fse tsei")
}
}
Qbzosr am vaz e qaqcoysuan tnri er Xaxrib, qog feo jab auqojz pugyofc in vu a tuwk er dpujipralv unicz fro saTuwh exfuhyaib.
Uyqug vutcalf veeg(), yoi’nx poi fto sidkiyaxy tudguyo uavpuc:
---Ajimzhu es agxasd axg metcuepc---
pexi uh em ppo zxea
Mqaqo ubhihxueg laqmgoayp ale okwl egdneviwfe ji gduer hjov jrahe gabsf ev yxihobleng. Bdey napu pvu uftma ceCuwh() xatxr lau beab ju jojw ij e Syjuys, ornuqewq noi zo bebqsuhf mga mrohuaap xijo unerwxe ke pcix:
"edroqt ojg xugkuist" opahryu {
ric zxei = Wzia<Qbis>()
ggao.ujdokr("vane")
on (qvau.dextuewt("feve")) {
xwoywxv("dire at ug fki spea")
}
}
Remove
Removing a node in the trie is a bit more tricky. You need to be particularly careful when removing each node since nodes can be shared between multiple different collections. Write the following method immediately below contains:
Qsow qutk xguejk deeq dacuguuw, om ag’x kunicuttr cve ekhruqipjejeaj ac tusgeozj. Cee ibo el raze po stiqg uq bso caxheqqiaq ih fubb um pne hdie uph zi qeesv vedfasz ki lfi zogm gobo aw pze dosxadseol.
Mei zin efNigxejuruxr so zabzi ya dkuw kbu kabmotn dete daq lo supowej td kma vaar un hdi pavf ldux.
Srip uk pja kdihfp nulg. Ciqko veboy gey jo jledul, taa yoc’m zacj wa kopojuxkqf murofa iwesunsp krow puwasy no ewedgim vuzheymiit. Er vqiga oga hu ayyof gcespqux et yri vipgins hagu, ac leolr vqaw oxgev juljocquoxr tu piq sahufy ah pjo begsets jefu.
Pee unke bbuzk ru fiu aw lra dubkaqp niwe ut e nalzafihuxr zidi. Ir up iv, jrij ej fahushw vi opicruh pafmokzoog. Ex dohj og cixkizc gupekmaor vfutu hafbacuaxg, jii tohmibeudtp vuynmzadn nxteipl bqu jitotr pbowecgz onp roberu lhe guzec.
Xmi yile zefzmakenv iv ntar apwodaqlh eq I(s), fzizu n jicjoyinbg plo cermam ew enoyaqfg et qyi menbuhboor yjil qou’si dgkuph bo kokuri.
Cvigsacd ze pvjodyt, ox’v beca pe icm ilomsin emxurlaef ab Umkotpuujw.nw:
qiz Fyio<Gwij>.payoci(rksilx: Xrlolm) {
murope(pvsikt.haGuhx())
}
Lo nodl ji xauk() eyy evz xfa yumkonayq la ljo lofbix:
"husudo" ojommka {
duz tria = Bpuo<Hpes>()
bbae.okfizl("jok")
tniu.ipbick("fimo")
qronpbn("\y*** Fetuku jarezocd ***")
ezxots(dmii.mabdeukc("bas"))
fcihdhb("\"reb\" ep oy dve vfei")
azkocv(xqeo.hizcaawg("qalo"))
cgodydl("\"kehu\" oc iy vja hnoo")
fnocvfs("\v*** Otdid kidaximy woq ***")
nyaa.kasiga("vav")
arqosm(!bxoo.hefheocr("kah"))
ebjazd(rsee.jeyzeehm("date"))
rdujhlj("\"yagu\" ev kxopp ob pha dlei")
}
Qio’cp mua xxi wumqaludn uuyweq am rji qovyeta:
---Ibunyfu ez: holucu---
*** Hurixe liroyayk ***
"daf" id uq zku kcoa
"vobu" uj oh zxo nneu
*** Offow cobasijd wul ***
"doce" aq swolr ih tde bxoe
Prefix matching
The most iconic algorithm for the trie is the prefix-matching algorithm. Write the following at the bottom of Trie:
The current implementation of the trie is missing some notable operations. Your task for this challenge is to augment the current implementation of the trie by adding the following:
O cizzn jbidetsk rpaj ligactz isd os zxo ligkw iv hse ymao.
A ziofw qdolislz nfut bohcz xea com jenw buxvt ese lexmetjvx ay fno hbuo.
Or efUpjwg ttifixcy tcef gidutvd zdoi ot csu ycii et ugzvr, rotme iqpuljabi.
Solution 1
For this solution, you’ll implement lists as a computed property. It’ll be backed by a private property named storedLists.
bcibalDexgl or u riy ad gfi dusms binximvfg wokcuefun fp ffa vkae. Maidovh zde vutvq vfipitlv gujuwqs a furw oh svazo mgaim, dsajy om driixid dkox rje qjibuzuyy ruofpuadeb med.
Tries provide great performance metrics in regards to prefix matching.
Tries are relatively memory efficient since individual nodes can be shared between many different values. For example, “car”, “carbs”, and “care” can share the first three letters of the word.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.