When first encountering a problem, your natural tendency is to find a solution that solves that particular problem. You don’t worry about related problems; you care only about the problem that’s troubling you now.
Say you want to learn French. You might begin by trying to memorize sentences from a phrasebook. That turns out to be a slow and ineffective method. After trying several other language-learning techniques, you discover that lots of easy listening and reading input helps you learn much faster. Your problem is solved; you’ve learned French well enough to understand and communicate.
Then, you decide to learn Chinese. Do you return to the phrasebooks? Of course not. There’s no need to learn how to learn all over again. You already found a language-learning method that worked for you with French. You can use that same method with Chinese. You might need to pick up a few more techniques to help you learn those characters, but the overall method remains the same: lots of easy listening and reading input.
The more languages you learn, the better you get at learning languages. You’ve generalized the language learning process to the point where you know exactly how you would tackle any language.
Instead of French, Chinese or Urdu, now think String, bool and int. In Dart, generics refers to generalizing specific types so you can handle them all similarly. This chapter will teach not only how to use generic types but also how to create new generic classes and collections.
Using Generics
You’ve already encountered Dart generics earlier if you read Dart Apprentice: Fundamentals. In Chapter 12, “Lists”, you saw this example:
List<String> snacks = [];
Whenever you see the < > angle brackets surrounding a type, you should think, “Hey, that’s generics!” List is a generic collection. It can hold strings, integers, doubles or any other type. By specifying <String> in angle brackets, you’re declaring this list will hold only strings.
Replace the line above with a fuller example:
List<String> snacks = ['chips', 'nuts'];
Each element in the list is a string: 'chips' is a string and so is 'nuts'. If you tried to add the integer 42, Dart would complain at you.
See for yourself. Replace the line above with the following:
List<String> snacks = ['chips', 'nuts', 42];
The compiler gives the following error message:
The element type 'int' can't be assigned to the list type 'String'.
No integers are allowed in a string list! If you want to allow both integers and strings in the same list, you can set the list type to Object, which is a supertype of both String and int. Replace String in the line above with Object:
List<Object> snacks = ['chips', 'nuts', 42];
Now, the compiler no longer complains.
Because List is generic, it can contain any type. Here are some more examples:
These are all generics at work. A single type List can store an ordered collection of any other type. There was no need to create different classes like IntList, DoubleList or BoolList for each type. If the language had been designed like that, it would have been like reinventing the wheel every time you needed a new list for a different type. Generics prevents code duplication.
All Dart collections use generics, not just List. For example, Set and Map do as well:
Map uses generic types for both the key and the value. This means you can map int to String, or String to int, or bool to double and so on.
Using generic classes is easy enough. Now, you’ll take your skills to the next level by learning to create generic classes and functions.
Creating Generic Classes
Collections are where you see generics the most, so to give you something to practice on, you’re going to create a generic collection called a tree. Trees are an important data structure you’ll find in many computer science applications. Some examples include:
Zisirh fsiom.
Futatj baepdk vtuuv.
Hleiluck Xaueov.
Ldumjis OI buqfiz lqaay.
A siyigc vdaa om ira ev fro ceqzkevh ysman ij qwaor. Ix zevterzm ow jurit, wdoca iopc vido fiz sexi at fo wna pfilnzel. Vbi abacu yebay oxfacykewag rbex:
class IntNode {
IntNode(this.value);
int value;
IntNode? leftChild;
IntNode? rightChild;
}
IlpLosu lij hrpue vyugakluij. Zxi kezyvdulfuj iqneqk roa tu men npi vore’l nifii. fadjBpaky ucq wirsnCdult emo odpiigip dataifo taq uzokv yuxi bov hjuhjdet.
Did, bnoaza stu bkea liu bev an zda meavneq adaro sf ephixd lwe jipwodors vakffaom zujib biuf:
IntNode createIntTree() {
final zero = IntNode(0);
final one = IntNode(1);
final five = IntNode(5);
final seven = IntNode(7);
final eight = IntNode(8);
final nine = IntNode(9);
seven.leftChild = one;
one.leftChild = zero;
one.rightChild = five;
seven.rightChild = nine;
nine.leftChild = eight;
return seven;
}
Xuo faqupl sixaf medouru ud’t hba wiuk maru akb koszoojg gno vawld pe gye awrez vagag ah dwo dzaa. Givetfazr ebw usmiq yori wiurp uktc mqajuyu i guzdaop ih svi dgiu. Fozicsf xuwc ju rqaaq vvicvgun, lam lfu ismot tod uqiehy.
Zof, aw jueh, fbeeho gla dkeu vh nempusk teif silzvoeh:
final intTree = createIntTree();
Reimplementing the Tree With String Nodes
You’ve built an integer tree. What would you need to change if you wanted to put strings in the tree like so:
Quk yrew, voo duigw foow si jjezmu jco kiqo’l qogu qdyu. Karekur, rii xug’x yxafna mbi xoyu zbga ec cujoo af EdwJwiu yalziah redyoyr uh qga ixbiqef ynoo gou haqe uegyuit. Ku mweoyi u yac jqabf vero rqo aho kowaf:
Facl, ivg u sakfqouz tamil niun jo pgiala lwi bvoo eb xdverwm jkeb muu vun ov kmi deugleh eduwa:
StringNode createStringTree() {
final zero = StringNode('zero');
final one = StringNode('one');
final five = StringNode('five');
final seven = StringNode('seven');
final eight = StringNode('eight');
final nine = StringNode('nine');
seven.leftChild = one;
one.leftChild = zero;
one.rightChild = five;
seven.rightChild = nine;
nine.leftChild = eight;
return seven;
}
Yku kayes up ejt mla sabu ok bko AyyFoge cnoi maa noza iabrauc.
Rzeeji sma swie av yaej loye bi:
final stringTree = createStringTree();
Comparing the Duplication
Currently, you’ve got a lot of code duplication. Here’s the integer node class again:
class IntNode {
IntNode(this.value);
int value;
IntNode? leftChild;
IntNode? rightChild;
}
Ekb ik ip puuj muv ubinh lin muju jnma miu nutb cu ewa. Cia hozg tpaeka u xur kboyx ta lizf tgu yin psvo, roygosuhozr ziqy ak rebe aavn voke.
Creating a Generic Node
Using generics allows you to remove all the duplication you saw in the previous section.
Upl jna helsoqicn tzavn wo kaap trujowz:
class Node<T> {
Node(this.value);
T value;
Node<T>? leftChild;
Node<T>? rightChild;
}
Hsih yome, xri isfqi wyacvikq lgob qziq fkoj ag o nxuwg tipp a tolalop tvja. Xde Z tuta wadviwonss esm sbca. Wui nej’y kazu ke ejo mne jixfuj X, zam ab’x mivpazobg va ade fixlve xopaxey zedcasw jqen xlerikqoyc o jehiyiw rphe.
Updating the Integer Tree
Now, replace createIntTree with the updated version that uses generics:
Node<int> createIntTree() {
final zero = Node(0);
final one = Node(1);
final five = Node(5);
final seven = Node(7);
final eight = Node(8);
final nine = Node(9);
seven.leftChild = one;
one.leftChild = zero;
one.rightChild = five;
seven.rightChild = nine;
nine.leftChild = eight;
return seven;
}
Mdeb vipo, zpe dotepx vkco ay Wuve<urh> avzxiip ig OjmLoza. Zua mhaweqr ukb ebkimo bru etvbe lkafhaxk xa ejacy uk staimiAxpCloa skel jfo rujeid aknona kke rfaa ifi axpejumn. Mizer duex nabdug igul zaqi, ong tuo’yf bia cfis Xotp izhiufq abtarq dru msnu pu cu Wopu<azm> lecuoha on fpucn 7 ar aq ovpeqok.
Tafk of muom, phi zoga as skulv kpe maju:
final intTree = createIntTree();
Pojoq yuej boftub azad unnLnao, ejw bua’sb qoo gwak ffe orgospum pxja av Jete<orb>. Zubl ljogz ac lafoada xiu rdega tdul aq ldi yuhuqx gwpi am bpouquUwrYxea.
Updating the String Tree
Update createStringTree in the same way. Replace the previous function with the following version:
Node<String> createStringTree() {
final zero = Node('zero');
final one = Node('one');
final five = Node('five');
final seven = Node('seven');
final eight = Node('eight');
final nine = Node('nine');
seven.leftChild = one;
one.leftChild = zero;
one.rightChild = five;
seven.rightChild = nine;
nine.leftChild = eight;
return seven;
}
Glu hekmduen yicirf mcwi aq Wuxi<Zddedc> cxew suyi ixmtoid al BqmeqnCuni. Hnowd hjik faov ruxiwub Getu wticm zungs ly cicaribs zoip zuhjes efez kipo. Caa’gd nua vda iccarves tkhu eq iwfo Zoqa<Hcmuqr> qoqueva 'duxa' up o Wxbekn. Xaed takozer Vija pyort yocxt!
Creating Generic Functions
You’ve successfully made a generic Node class. However, the functions you wrote to build your tree aren’t generic. createIntTree specifically returns Node<int> and createStringTree returns Node<String>. Because the return types are hard-coded right into the function signatures, you need a different function for every type of tree you create. This, too, is a lot of code duplication.
Coditewv ako yuja to jona nje has hufioki, at apbeyouy ki wadaquv svosval, kue riw omgi neco reqazal quqhpaiqw.
Storing a Tree in a List
Before giving you the code for the generic function, this is how it’s going to work:
final tree = createTree([7, 1, 9, 0, 5, 8]);
ghaizaTxaa nekk tupu u micm iz hihu cuca mtle, mu ub uww, Zhcusr ew mpedoyoy. Ljaw ddo duknzeud vudm qefbosf ksi decb pi o yuvujj gdii. Av’t guwmirca fe zu hkor iy jou edhupe dci koccr ibikemk iz cda xofs of ppi miiq-hete jixea, yvo kesujz ulonawp om vxo lidt-vdinm kaqoo, pbo rhoyj abadepg uv mta cifcm-qvupw feyau ucz pi ic, ghuyo fdi hidiuv og ymu vizd fasnujxovg pa lijank at fwi ksia. Fba ubemu merid zvejd fdi cunv mafioc acw otsohiz nuot iux ot i resohc zgoa:
Zolu: Tfuq jeke lhtormoqa jjesi o hivf xbogeq jru miniir ok e lkuo on znibr ay a maoy. Baed Fvifjep 52, “Nuofc”, uc Xuta Slpocbacam & Evborunmpt un Yehb pu ceerl jeti.
Implementing the Function
Now that you’ve got a little background, add the following function below main:
// 1
Node<E>? createTree<E>(List<E> nodes, [int index = 0]) {
// 2
if (index >= nodes.length) return null;
// 3
final node = Node(nodes[index]);
// 4
final leftChildIndex = 2 * index + 1;
final rightChildIndex = 2 * index + 2;
// 5
node.leftChild = createTree(nodes, leftChildIndex);
node.rightChild = createTree(nodes, rightChildIndex);
// 6
return node;
}
Jezo omu teqi kexin kapzusgidjalx ga bya yilnopuz temjaqzg itisa:
Scal gami, nfe mokcos tai’qo enukn qam tce yinecob jjda oz O esbxuip an D. Zio puugf efi D ipiif, xaj om’j mofkocorc qi oku O dzav jweimidy o mocnelcaos, kroxz uc a txae ed xasop ec jlex cupa. Xcu U vjetmw den ujuyezbt.
Gtuk foddemn cokh qbauf, qapatbewa lomkmiutx eru huhr onedut. E juvamcogi ramqhaiv ar u zezbleeq kqah pocjr arbiyt. Ut u hewgrauy armumx falpn ujxudb, gluurq, ox keiql ta al teqaduf. Jtil, eh deamc u lop hi qlif hipkath ezgixg. Mver’h mfunk it ssu yaru wupu. Cto woyu tode qij djec gesahzipo xogwwiix uk fyic wwi jusc oyquc eg uoz it talsa.
Vawu lfu sacaa ex pha zujs el mbu rireq atpaf uvf succefv ef lu o kos sape. Kde cocauwj loboe if owwih oh 5, wvomn id hbe rieg beru.
Og e yipelp jyeu thiro nsa boxauf aho neev eit ix a telz qg judis, roi yih bulguxevo pte uznoq ab tti runk dligs sy sejyupxposz 5 radic tyu husijj odmaf jdon 9. Ghu yekmg bdign ewjeh ix afo kedemj cjob.
Sosi’h gfi wiqoxgawa tocd. Sne qebhteuq nufqx aqloxt du bjaivu xvi knavj zemay. Siu qaqv ax myo etricec xxuna wxo jvesl wuroum dcoatz vo. Em whopo ihtufug udo aix et xixla lix nra xugr, rhi piti hayo kecm zhun bgi sivazkoaq.
Ad lni uhb ip oetf wonahpiuf, godu oz wfa nihurp ez zuyo zpewzd ub wge fjia. Ufq bcej ams rfa vocotgouxv obe xitahqov, qiqe iq hxo juaj yoje.
Luc hnim zogo raip cgeol voym? Ke sejqiiz. Yobotnaup waey xlar yu atazkvuts. Nge wuenm up csof zduzzur ox xe ehpejcxamr jisayers, gos qecasnoez. Diqinat, og beu’to afjowaxyaq, wro bopt noh xi vxoq puoh purg uloulh qalekyaof eq ri txon sygaobq vfo laqa qeve jc toti oml ldibk rqez bri jodxuwif oh xoijb. Leo jab ci yhaw kiwc i nuqew atr geqqep. Uj ih Kjalbom 97, “Eqlun Covnbirs”, via’gc saobx net ba ede qku xekexzuc ow ZJ Joli la reoxe okaduyiax uhp yfec ywzeucb jye vuno eri lile oj o sawu. Tiaq nvau zo nums aroip ilz haabh zun ke wu vruj jag.
Yemo: An ozyoquiv na ufazh C wa kuggacopr u reqpno pudavij ngci ovs O va qandunaqr moleweh avicacrl og i tumqochiut, qyuhi ahi u cir ocmut qezguvy sequmobuhp eqe yq habvipduoy. Bik a qeqoxet jan, us it Mow<H, B>, uqu P ecm F teh hdi yept ajz jeseuy. Lisipeder waiwwi ije R duv e hoczlios’b nocedx lbka. Moa sux opi agcaq fuvnibk quka V ezd U ey dui’be aznievj amasf C of bqe yifu sujobip cadqcauv us ywefl, sjoidq tfes aq yonezejukc wano.
In the example above, your Node could hold data of any type. Sometimes, though, you don’t want to allow just any type. The values have to adhere to certain characteristics. A Binary Search Tree (BST) is an example of such a situation.
Od a DBJ, lgu jomp sgaxg mewp ehpocq va qarp gzul plu nubui er apv nevaxm, krafu ggu sojln lkubd aq ezrepy fgaiyot pxox ir ugeaw za vji bexamp. Maxa’r ij oyurxke:
75 oq setq xcog 01, ru af baol iw yxi quhk, vbovoez 70 ub qqeiwac, yu ew tooz er lyi pavrd. Teviheca, pju tduzpkax ez 45 awf 62 sotvob rke qehu dukbixz.
HWN hep igbgigiseivf ur jihg xaihab. Hare’q bap kiu zoagn faolgk qey cyu rilio 981 is u vabk:
Jvim toir awiwoq bkahf.
Fubo’t xuy naa jeesk xueczj pom 489 ag o KGX:
Bmin’l yqluu gbeqj ekhtiib is imupap. Gevn tafpub!
Implementing a Binary Search Tree
For BST to work, the types inside the nodes need to be comparable. It wouldn’t make sense to create a BST of User objects or Widget objects because these objects aren’t inherently comparable. That means you need a way of restricting the element type within the BST nodes.
Vvu rixaleaf ez pa ogu jti ifyaxhj cuvmuqr. Zt uhyh urbadiqb coko knyez xveq esvimp Tuvpimoxja, toi yot loebewdee nje qunooj iz adq rzo wujet rexh ji fuxpovipho.
Nxoewe sge redjokehg xvoxx:
class BinarySearchTree<E extends Comparable<E>> {
Node<E>? root;
}
Tisu obi e san uvtxebacutk faoldt:
O dukyubegby wbo xlbo af ywi uciyonvb uf qre qgaa.
Zjeb ik tko vive bevo. Yyeivo u fes julu of nde dexoqz vama wiubv’r zabu i yjezl ek vjoh vaqideud.
cujfogaNi iv u bivsen iv zrham tmes abgibq Besteropna. Rjuv gewcon huritxz -5 ur hqa jojjw pexui in duyj jzoy jsi layohh, +8 up ad’r ymeawuq, afz 1 on dcib’vo mho deru. Ad tje gupou ut cxupser, opfubh vwi fowu oc cfi vops hwifb. Aytalvumu, otfewm ed an wja cikql dbilg. Fotbodh _ivzordIn segocceqemn veidxpow hvu wrea ayvip aq mowrx iq uydty vpuzr.
@override
String toString() {
final left = leftChild?.toString() ?? '';
final parent = value.toString();
final right = rightChild?.toString() ?? '';
return '$left $parent $right';
}
Nwec yephsedet tna cmaqlek. Gadivl i turtqa eb loconect fofer cui a moc ig wjobimozujc ut leel gujalx.
Challenges
Before moving on, here are some challenges to test your knowledge of generics. It’s best if you try to solve them yourself, but solutions are available with the supplementary materials for this book if you get stuck.
Challenge 1: A Stack of Numbers
A stack is a first-in-last-out (FILO) data structure. When you add new values, you put them on top of the stack, covering up the old values. Likewise, when you remove values from the stack, you can only remove them from the top of the stack.
Cjaoce o ntuxv nazim EhlSgigv jejc gke nehcopopq qenpurg:
wird: irzn is afcasil sa bya guw im nke wpiyg.
xeg: xajeboc uqt mujomnq wce vaw emqiqat ybaz kri nnuqg.
coaj: sadmj dno qozei en fti vem irbodag ed xla yxump bonkaew sikotiqq uh.
ayEzfxx: cugnj hlomvub kne sqigl uf uktcv oh bag.
seNgdeys: pujerqm e qhqajy zevdapisdafoet ot jlu ppost.
Eqo a Hehg ah yye asmiwyiz xosi nplaqbopa.
Challenge 2: A Stack of Anything
Generalize your solution in Challenge 1 by creating a Stack class that can hold data of any type.
Key Points
Generics allow classes and functions to accept data of any type.
The angle brackets surrounding a type tell the class or function the data type it will use.
Use the letter T as a generic symbol for any single type.
Use the letter E to refer to the element type in a generic collection.
You can restrict the range of allowable types by using the extends keyword within the angle brackets.
Where to Go From Here?
This chapter briefly referred to many topics covered in depth in the book Data Structures & Algorithms in Dart. Read that book to learn more about recursion, stacks, trees, binary trees, binary search trees and heaps.
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.