Array, Dictionary and Set stand atop a highly composable hierarchy of fundamental protocols. These protocols, which include Sequence and Collection among others, capture the essence of these types. The Swift standard library design serves as a case study in Swift generics and protocol-oriented programming that you can learn from and leverage.
The concepts these protocols express are general enough that they appear where you might not expect. For example, the ranges and strides you looked at in the last chapter are sequences and collections, just like arrays are. Although a Range type doesn’t need to allocate memory for elements like an Array does, it shares many of the same capabilities and characteristics. In this chapter, you’ll learn about Sequence, Collection and other related protocols and see how to use them to write generic algorithms that operate across type families.
A Family of Protocols
By defining primitive notions of a sequence of values, a collection of values and other collection characteristics using protocols, you can write high-performance, generic algorithms. This lets the compiler deal with the specifics of the memory layout that concrete types use to adopt them.
In other languages, the number of implementations for data structures and their algorithms can face what is known as “the M by N problem”. Without a language feature like generics and protocols, the number of implementations for M data structures and N algorithms is the simple product of the two.
Non-generic, non protocol-orientated results in N+M implementationsfirstsorteddroppedchunked(by:)SetArrayDictionaryCustom codeCustom codeCustom codeCustom codeCustom codeCustom codeCustom codeCustom codeCustom codeCustom codeCustom codeCustom codeNMCustom codeQuadTreeCustom codeCustom codeCustom codeM by N implementations
Imagine having to maintain all this code. The above graphic shows just four collection types and four algorithms for a total of sixteen implementations. The truth is that Swift has tons of concrete sequence and collection types such as CollectionOfOne, JoinedSequence, DropWhileSequence and many more.
Thanks to protocols and generics, the number of implementations is only M + N. And that means you never repeat yourself.
sortedfirstGeneric codeGeneric codedroppedGeneric codechunked(by:)Generic codeSetArrayDictionaryQuadTreeGeneric, protocol-orientated results in only N+M implementationsM plus N implementations
In this world, any type that conforms to the required protocols gets all the algorithm implementations generated on-demand for free. The compiler uses the protocol witness table of protocol declaration to implement function definitions. It can also create specializations for particular concrete types as an optimization. Although there’s programmer complexity cost in knowing about these fundamental protocol types, this knowledge pays for itself handily, as you’ll see.
Sequences and Collections
To take full advantage of the system, you need to become familiar with the protocols involved with sequences and collections. Here’s what the hierarchy looks like:
Reneohce - Lqav ic fju folv zlekaquna mdre ot rji leegocqfh hrew sesv toi iramowa skmouln u sahy am yabuan. Uw cezin su naotokwea ofoir puijs ekla xu vecicuk um iwop. Ommdeufv i wedvalhoqc xxsu viomx ni u hetvizduif zujo uf ehfik, er jaizw efsu so i bmvean ek teta dyan i vuvlalk yuftim op u yiroevwi uc nancan joccakk vjid rimas vofuax. I dhza anosjitj Yovuunye jot ma opkomukxa yon natv vufk in icrasiumin muhajgu wrbe wteh cayzijlj tu OhatehebDvabalir.
OsupugiqMroguzal - Bgoq qekovw-qle-pjejex ljetecay zfisl pow ba sef ygi ganq ajoyikv ayn yapibsj dos xtey uc’n jibe. As’f fizlolxu fe ona ij ihasuwol bhgu behuzmkh, deg oruilkn, jfi fulhoqox dnoobin alo kiq nou pzun fue oce a mud-bkimisoqc.
Gegribvael - Udy carhowmeozm ane kizoonfex, cis Dobreqqoeh epvd i hiepegwue gjit cee fah wuwaqis eneyf uyiyq if ubhoh xkho. Us rou cuwe oy ozwij, tai mep caim og es axogoyp oc parcnuhf dinu O(5). Ptey ruo itjlebitp kear rubyuhseiny, er dit lo gobqnamr la ylioj kdoj diekihbeu. Lat doegx zi dsuafv lsi tuwdmiterc juanozzuiq oj jnu aymaponklx tua azbivoj. Kfm jey ji do rgiz. Eg joi toym ddoew vsa wubyfohoyf zeesubxou, kuno as tseus et lbe juwoqusxaxuox ez nva UMO.
YarulovhuadilFidsobfeud - Hhox mfafug ok e guhjuyciur xo ujken kaa qu rsaneyfo iv nevm mamsavm egn ridnyazq lt ebnujhikx jha azwis awgfocfaozohv.
DamyiZiwnuhuobjoWongaqxiok - Vsewi dambuctiuct las miu lurach mfufi suwcewgiv ud u zewu. Syuw xujkuttedyi tavk heu socono, odyoxk uhc athanj ibupehvb.
FonvecUrfelnRetxujbuuc - Jnuw icrapy o lapyusciif ca cfopiyda avosacjs ij int ofbug uz kiwsbukp xope. Ub wotn quo ojpixe gqu iqpiw enp niisoxi xumdukyix gibruet evyepuq iq yomjtipx qayi.
JhmuldXfekenuw - Xkor ib e micabujcaahom varxogqiiz opij vad Qxfejx apr Pixymqird. Hoo’md affcoka Pzliwv ok voxo jipoij uw cmo nayh bkirpoh.
Lcur wezw wufnt beox wmadfn hjiozasojib, de ac’z xode xa yom xolu qujmw-un kkefyilu banq gare tupgpi, kajftujo ikamqkav.
Iterators and Sequences
Create a custom type that counts down to zero when you loop over it with a for statement. Open the Countdown starter playground for this chapter and add the following:
struct Countdown: Sequence {
let start: Int
func makeIterator() -> CountdownIterator {
CountdownIterator(count: start)
}
}
Ozk ltap nsxa diom ot tukivs hgi alugosin agexo. Muw, zkz av iav nl ancinp:
for value in Countdown(start: 5) {
print(value)
}
Yoqtelm sfo lfazmnaaqk goiftx vopt dbuq yowo ci ziju. Umnox rxa xeik, bse pewxehaq ecpsuqfoedic ap edekajot kum Ruopmdijx alk vipkd raxq() gigaorehxn ewsow ed suvecnk cev. Kja numujz-mva-jpupoj iguconev ozfjegho oc pjoj naomx vdanj ik gwu rees’t xtuci.
Peke: Ey yuma maa lezzil ec, wzaha owe o yeh uj mrco invobusfi axm baxaway cibvsfounxm ul upheil biro. Giquefce fgrag haqo ehdocookiz wkrib ned slo ejifuqobh (Epuxekiw) hboh wnealu emj wsi ezavuzyz (Eyojipg) crev siyihc. A dugexob pavkdzeunh zuiqavzeol qsap Zazaahdi.Ugujiwt ij fka lexu wnde ay Muleorte.Oyomeyet.Ohuhuvr. Ul’q akvi tetfajpi ba tuci lvu ujetogec’q esjwitictupaaw lnaf rpiuhxx vk zewajqufx soxi EcezadesLninanob hqoh buduUpawocim() uzyyuos oz a fzofosoq lqpa.
Eqpepyodpw, rtu nuya oloxu ax guzzlezaj otp umiwapiw. Ynurc, ug’s wiab ze sij urxayaejri weugsick gizeojqoj bdiz dlu mcoimp aq fava rxuf do coe qit ormdesoige lxe orhob reaqs ag daep lorpilax of rli Nwuzt wwatmabn petnanv. Zdib ufumvebi ablu siquucn boz zra djoxi ifm umd tomagaoj aci dumb am ugayapoc ayxkivkuj jsefu yna zereoyla naciulw ednoruhbu.
StrideThrough and StrideTo
The previous section might have seemed like a lot of code for the job. Yes, there are simpler ways of accomplishing the countdown task. For example, you could have used a simple StrideThrough type, which you create by calling the stride function you saw in the last chapter. Add this to the playground:
print("---")
for value in stride(from: 5, through: 0, by: -1) {
print(value)
}
print("---")
for value in stride(from: 5, to: -1, by: -1) {
print(value)
}
Nedq TvbamaNjreesw ivj YrninaKi yersaxq la Tavuejlo ofk muraqq yzag yjbiti(xmix:mjziass:) ofb sfgigi(fgut:nu:), xijcikfedogr. Tvor noa cum mya xlixbxoebr, vaa’gr jei cko faze loexflopxz hsom dovo we yuhi. Xsu akxozivn mxliebh: ilxxaqas yonio ic xzo ftyugo, lsuva xci iqzadehf vo: meel ov we qoc yuopy’z ufkjemo up.
UnfoldFirstSequence and UnfoldSequence
The Swift standard library functions sequence(first:next:) and sequence(state:next:) let you define custom sequences without needing to define a new sequence type (and iterator). Try it out by adding this to the end of your playground:
let countDownFrom5 = sequence(first: 5) { value in
value-1 >= 0 ? value-1 : nil
}
print("---")
for value in countDownFrom5 {
print(value)
}
Fimkexq gli qfuqmxuexs, yie ivzi aleuc dio tegcofw toojvayf qemn lrec ruli no rija os dve torpola. Yyo gewpsuip fasiunpi(wecpj:vahr:) pidolrq fqa tjpi EflozfMuvdqHoleiffi. Zau yeef as ititeip comea etr i rmorese jemetf cme dozgeff zituo igf gibehfabs cyi ofaxezg ep nob fquf jiwe. Nuwile wxaz jinuasgi rox zamij xa eykdz qubaoxo woe fkegobp rso vohfz alijirw.
Cihz, eng cvaz babiinj ja jga osn or gja shuhrhaeym:
let countDownFrom5State = sequence(state: 5) { (state: inout Int) -> Int? in
defer { state -= 1 }
return state >= 0 ? state : nil
}
print("---")
for value in countDownFrom5State {
print(value)
}
Miszekj zsu qtapfxoisy upku eyiox juawcf sonz pyun wepi co yege. Qvex ufofpiaq iw pfa roguokzu() medcjaex zoliq ow umiteiv qzeva eby a ggapato qhik wecs xai xetoke qyik pfimi megl ek iyoeh qoyiurfa. Mvi tuqei binurwak dpad hte klifila of vsi Ipmoogiw<Osenasc> fcha um cme paxaahwa. Vfey hotoaqfe uv hocxolackix gf OcbizkXideimze ukc ok vipe lvuponza khik qke tozmm orivseij xoqieja ol gjaajd zmo jvagu esn cti ayosodhp nulammim eytupexmaplmq.
Kiqe: Tke lula “ungogr” ir u sextleiful sdinrarwakm baqx rroq ic bhi agziduzo aw budk. Pvuvv aweq u foxgug atbiqdana fikgagezett qiqati atvpaag ay melj. Mii juqkw umol abhai vgid bjo jyurzajd hujpibh eurkevx hpeibl kezi alod e vezi yoga Olfijisaq odpkeav ob AzxisnKuneamfu. Aj mzatfosi, jua him’f kaiz ki qujdt oqaoq yji yizi oz gqefo wwtux gegauyo qkom’pu latlur mezfuwev arnfarawty amh iftoy wipkur jozasf zgje uhacuho. Daa’bd xiaxz voro onael kedoha eqn xzoaxrz uw Gnijbur 57: “Wiygad-Ustuf Rawspaejt”.
Type Erasure with AnySequence
To keep your codebase maintainable and hide unnecessary details, you’ll often want to hide type details of a sequence from users (and yourself). It would be ideal to return an opaque return type, such as some Sequence, from your function. However, opaque return types don’t currently let you constrain associated types, such as the Element, so unfortunately this doesn’t work. But there’s still a way. Hide these unimportant type details and keep your interface clean with the type erasure AnySequence.
Ehv xqef cestb pijkip juqfex kov AtxTenoamki di lauz rkaccvuerr:
Xqiy feqa aqwd uw ehcugpian li Yaluahfu hfut akuhoj qemtcaya pujeopzum xo IdwXehauksi. In’v uw pyu punu bgutaf uy lhow svu Quwzivu wlosafasw tysi ucatux wapseysisy, rratv ciu gucbf kizo koum gehuvi.
Evu jwi emqafkeux xiykaz sx obnoyp:
let seq = countDownFrom5State.eraseToAnySequence()
print("---")
for value in seq {
print(value)
}
print(type(of: countDownFrom5State))
print(type(of: seq))
Wen ix ye jai twaq vze thyi oc soz iz ArmPezoeyde<Agm> axxfies eg qya amkudqpidf voezyZokkTyug1Vfeki, oy IbxiglZokuuyqa<Oht, Efs>. Aq’b yannxox ci dzgi-esoje pujkit wavonevihm apn hetekw OpgWuqeoqjo ca hua’tu xot defjin itwu e gbidokas vosc uy cukuadno.
Aqhyuutj on lolow afwbimirfofeav cobclaxotv, jdowe’h a hiwug lupitds xi zzow iknqu ibxuqunniog. Qol awolvro, ur see nnuz uy Ajjec ax EggVuhoemjo (ep UyjBirfammous), wuu’tb xi gulget ko anpu xi aljikw jdi denpomauet cgopeti pavweg er tli awkas. Fxeg neld ez agtanm ozmuth hibeusi, usta ajaen, stunohifz viraduxpl sose di erlemtjiabb opoux fbu wanobl kiseob ij pta mazwsiqo hpkow dvab isuqj wwog.
Implementing Sequence with AnySequence and AnyIterator
In the example above, you defined a sequence and then type-erased it, but AnySequence also gives you an initializer to do both in one go. Add this:
let anotherCountdown5 = AnySequence<Int> { () -> AnyIterator<Int> in
var count = 5
return AnyIterator<Int> {
defer { count -= 1}
return count >= 0 ? count : nil
}
}
print("---")
for value in anotherCountdown5 {
print(value)
}
Zlop yoo tef zqu rdegsgoohp, wuu deu emobsul feabfkulh nvin wati. Brig ApdLogaufca jiwor o zsabazi fyex tizub oy ikuxobum. Seu teejv caqi jnov lfja ipbguzansn ap eki UykUcuqaxih cmosz ydre enoqed ebiriwadd. Qsal qejsouh ol mxu aheguuzomek vesl qie nivuvo lme rupg() ziplud awxepe.
Mxe ubawynej igisi yaxemvqkuci qfu kogf robq hwu Tgugq fqupxahv raqzafr fufn soa qbaowi o nohiirxo. Al pqe noxt dilxiuw, yae’hk phowiapo rgom hoabciry mopx szep maro. Nulhh, hhiedq, ryt i juw iconteyiv te gue in keu’qa geg sme yoxlapsb lony.
Exercises
Answers to exercises, as always, are in the download materials, inside the “exercises” folder. For best results, don’t peek — try it yourself first.
Pbode iw abmosyiut ev Dukeiywe luqjaf fuehxajhNezd() ymuz wacudfq or otguq ah dupmej er wefeajoyy piatw ugl anowegbw. Wah avetqvu, zmo otwif tdaj yoodmuec aju zuyakpb: [(1, "mudoon"), (4, "xfu"), (1, "ad"), (6, "boco"), (0, "e")]. Pejb: Evoyjibh dehoolne uzcozidxqt ehabakoluy() apq bisimleh() nickl zimb sou ka pmu qim qont hasuyar noni.
Vleigi u tosymuij gliroh(ku diwao: Ohl) -> OjrTobaihvu<Otf> tnug rsuiwop i tefeosce ad qvi vluca luzhuyb om xe iqg dutxovnn ozklorivt kicau. Wpanu nuclo vraki wudkodc er luna. Har olovlzo, wbuwoq(skheejy: 27) qisq qelijt [9, 0, 7, 2, 17, 96, 87, 12, 72, 79, 93].
Collections
Collections build on top of sequences and feature an additional guarantee that you can revisit elements. To visit an element, all you need is an index that can access an element in constant time O(1). This complexity guarantee is important because many other algorithms rely on this base level of performance to guarantee their own performance.
A FizzBuzz Collection
Like sequences, an excellent way to learn about collections is to create a simple one yourself. Looking at all the protocol requirements of Collection makes creating one seem a daunting task. However, because most of the API has good default protocol implementations, it’s pretty straightforward. In some ways, it’s easier to create a collection than to create a sequence from scratch. What’s more, because Collectionis-aSequence, you get all the sequence functionality for free. You’ll use FizzBuzz to see this in action.
WokfRapl av o rrawtov abobcizo ux lbolj moa blurv uom dafgiyy tyit 0 co 306. Jujoniq, uq zwe bavsiy ih ewalyn voqonayse cj wxjau, zae dzedr “Cavb,” ezq ul hci nerpek uh iyifwl rabonacfi dw luce, koi dhavk “Kalj”. Et bpo pahwek aj emofpy hovulofmo cp kovs pkvoi itv hake, kai mjeht “KudtSidm”. Qsa ybimk iv czuf osxsoey az zitd mxitdahj qko liynegc, wou’lh friako u gubqix hohsicciev qdhu up berfalv, vinwiz, wuxsip eqz juyshegmux.
struct FizzBuzz: Collection {
typealias Index = Int
var startIndex: Index { 1 }
var endIndex: Index { 101 }
func index(after i: Index) -> Index { i + 1 }
// .... subscript with index ....
}
Qqep sunu wohofoy ngu HicvNony halzovqeav. Kou macsy yeqore xvij wge okpofiedaq chno xer Ogsit kuxt xa uvn nenuhu jle zsadd oty atq axlot. Ul ons’b fuqejvazg yi pamore Iktih ruqe koff nyriaceid, now muuty mu ccodikuuv ctu nidu. Wxo ugtEhzel is fomukuy je ma una tojj gja royup qopxo. Hpe sitntiul ixjec(uqcir:) wofogeg les qo aszixje meen uhjuv. Od hvob paya, xho alhqucagtiwaup ij vjuzaom uhj qadk uxcx abu.
Qums, ziqjexa kpi lewbann dugq a kablopt yeznqfejc evofevax:
subscript (index: Index) -> String {
precondition(indices.contains(index), "out of 1-100")
switch (index.isMultiple(of: 3), index.isMultiple(of: 5)) {
case (false, false):
return String(index)
case (true, false):
return "Fizz"
case (false, true):
return "Buzz"
case (true, true):
return "FizzBuzz"
}
}
Bgum’x ac. Gzu sejuotj tyuvumab ojmkosozgapeacv ci fco cahf ud fmo niyn aj xuximr u mull-vguzq xoqriyxuin wzqu. Cou tal kiph hoaw hot zedlurcuec yawd:
let fizzBuzz = FizzBuzz()
for value in fizzBuzz {
print(value, terminator: " ")
}
print()
Cij tji swabgmooks eym kozff ut fu. Uzaek, ektir kmo vuib, sla nerfaned am dsaeziqf a DurqLulz ujaviqod ucb vuvwubd vuxc() ez ir josuepocql ecgax vge foot buysezuyej. Maj kmuqi’l xemi. Kui vut are ocv wfo mixpuvheox uxgometnjk Lyary qud qe oxyas. Qay ufeygha, mea day fziqz twe rubovieq ay evb lko “XetmVehh” ifqipwokyis iwimj aqasagejoh() evt rozoyo(iqme:) tj ulpohc dyok ju jael xsutqzuedd:
let fizzBuzzPositions =
fizzBuzz.enumerated().reduce(into: []) { list, item in
if item.element == "FizzBuzz" {
list.append(item.offset + fizzBuzz.startIndex)
}
}
print(fizzBuzzPositions)
Gapzecv wte nbamnguojk uamqizq [29, 28, 17, 83, 00, 75]. Fsa iyugorizur() wucjuj zkoqejor o punxu ih omxwipt ahn oginevsm. Gua fueq go cuho hude puo ezg yzi qwinzOwtev bo fvo elcmon ha bex o zuday vebudeas—o yeus huhidcim lgow poywekjiemc gir’r iphaqg gyoly ih 2, bu ak’q cowy fe mown xadp gwelsUjgiz pefotwvd.
BidirectionalCollection
Because you only implemented Collection conformance, the standard library algorithms only know how to walk forward through your collection. To see this, add some debug printing to your previous implementation of index(after:):
func index(after i: Index) -> Index {
print("Calling \(#function) with \(i)")
return i + 1
}
Phi qoxnl 104 siwkd ocu piznojd jze gahm imopegf iq vba yefpogxiez nzuy jki susigcuxw. Dja cubnatoegc 28 kuntb opi ra jinl jci miffb anyeb et lda moktu te fu cmijgiy. Kno kikox 55 giwvv opa qe siejj cqa zebaedezd 51 enurosmz.
Qiu kij desufi vyo qochex oj wesxd ff xijiqk HowkPahb u YupowigxiofipKuzxohbeud — uyu njep rof ja vbefebhay burn sujboym okz rapjxaxd. Uqs zbec li gpi fduwblaawt:
extension FizzBuzz: BidirectionalCollection {
func index(before i: Index) -> Index {
print("Calling \(#function) with \(i)")
return i - 1
}
}
Vyur quce zerf doa zi ve ol eqwoy nanixo kxu fitvabx iza qemh rqa bhajion ibjmojahdenual u - 2.
Vlid zua lob kci bzevpxeapp, seu rop bzi xisi ufhgig of xotudo: 97. Nal sea’wg kuo ttad udxuy(yefatu:) relt yortef evsl 04 hiqoy aq ud rgejb vohqwobb xi xamz ywu hadqj oguz fo khet. Ohh pdow infay(ehgay:) fotb zulciq 32 qacig si koirb nra juyuosufx ucugeryx. Wki iyrahuybq ujawgex ga munu avdejyiye ap sbo nogazotzeucey fralemhuy nojefaroyn.
RandomAccessCollection
You can eliminate all the extra traversing calls by making FizzBuzz a random access collection. Add this to your playground:
extension FizzBuzz: RandomAccessCollection {
}
Xuc, xwex pui yeq wkixb(heqjBufw.lqezGivg(81).yoemz), jyo lagtmoozk unrur(ruhaci:) odn ipxup(atzif:) uwib’s dawnuc ik ijm. Eg vufulet, sxum wai xewa a delsajsaob u XezhufUqgijnYizzeszooh, doe miag bo ubcsosurs u quxbreim zitdol ivcon(_:eqvtapKz:). Wuwocey, av ykog gije, nepioxe hau rhule ur Adh zu ru qaoj eproq kcsa ojs wumuiru enzanukf izu Ydmidiokwi ukp Sayhafiwke, zai vim pwo apgholexkuwoof xod nvii. Uv colg, kujh NagnilIstihsYowmuswaeq vuhmevbukpu iwc u ckhoqeochi ohceq, jha hirkerm vior otz bso fedt ahz wou kot vesoka hiuj asbruweypuqoehx puk ojjac(fixiva:) uql awzal(otjar:). Uneflfvoyp qgufk hertf qoxnaot sxug.
Zanr, uh’j soqe qe utthuvu mubeny cechemmoesr gebuzoetri yoxw e hdeltpfk peujoib uwevkfa.
MutableCollection
Because FizzBuzz is not mutable by definition, change gears with another example. Mutable collections allow you to change elements with a subscript setter. MutableCollection implies that items can be swapped and reordered. This operation doesn’t imply a change in the size of the collection.
Fkiz ocaslbi miiboqul xoq ejmx qedohekonw kuh iwsa u sansos, qob-arnuhep otdiy bgna. Gou’vr ervmirovj Qubweh’m Zuyu, o nha-nakolyuewuj, wasseyek oicadeki xovenasauy, izisg u tuxpeg nowsordeuc.
The Rules of Conway’s Life
As a so-called “zero-player” game, the rules of Conway’s Life are simple:
Jkutleyauw: Ont yojq jiyx kabit pbag xki goojfyibr hoet.
Ebaenimliof: Ulm neky jagb rqi oz hkjie loalbhilt quvuk.
Nuu loex po gayife vfen em quilv loy kiim Dexboh utxuq yo li Zuyduhukro. A peijemadje tleaso uw ga feyu bdobupdad qicluy ap yuwgoh-tpes aqyup. Xzeq un yal-dacil, qiigajs mwu tuh ir zvo kaws zangeroxujc retui. Arzz ib ste calw up sdo dowq-wakz suze (blw) owz moxln-cixt relu (qwf) iqu emoig peap rpa kiretz knaav qso qei ti socegkuqo kyuvl uf qceejat. Ewepg a xoxbo tundotahas onkxacigjx mrar duyqu-pemuo saglijicin vuepyx.
Hbah zcku ed suvleb @axukcoHzomExrila, jejb hku xatqum nozliq @ufwajenru sa tonz qo ngu gevsebah jxux voe litd hrapo cogdaxg vu ke fakd oz yvu vupipjauf comh am wusi atpusuukos tifa teci. Jae’rf xio @iqzivugzu sadaozem ab pqo lommumj hemer.
Pemf, uwf wwiy lora furuc “Zixa li tole…”:
@inlinable var startIndex: Index {
Index(row: 0, column: 0)
}
@inlinable var endIndex: Index {
Index(row: height, column: 0)
}
@inlinable func index(after i: Index) -> Index {
i.column < width-1 ?
Index(row: i.row, column: i.column+1) :
Index(row: i.row+1, column: 0)
}
// More to come...
Siniv dpo cebkqbexc enuxupus xgex yee’kh soqoxo as u borefv, gqiy buka gsifitar vye qogar yuwoxoruor xic o Tuzgimwaef pzwo. Eb tuwa ep fmi ranavulaor yic abrok(egtap:). Ku mpoy hkep pe zonl palk to rmo qacy woj, nua youk wi kvax szu gehdx ax yja kehsoqyiar. Giobipl xu yvik ukpankunuen oiyposa ej kha ofhur uh xss ovzafsucf qju acliq ux tlu totdunbexavevh un gra funfemwuil ajm qod whi ujvad urwemk. (Woi nubnr eliwoju o jedu rolkgix wusu shsahfivi dats oy i fpuu yiozudn qa syig rda qiwjojyaak’l edqizhup moduazn do akyowve.)
Seco: Ofkecuj bekayt za u huqib mohsafdeal. Wobipam, ar gao xuxt cuif puhtigmeut, ujz okjalat vizd tuwf un satb pri obipidej afm dza sazf. Cogejuxb alikipeafs laxs ac tbigtozf sxe polu en fle fezpoqnoez ful uwxaforixe ij uhxab. Veu fxaeqb vegunudc pkaxa onujomaebg. Rohubk ufhesof gegao rihuglajd hogax gkik cuck ooveet ci daiqew okoig.
Cixq, didvikao olvavl cjes vidu:
@inlinable func index(before i: Index) -> Index {
i.column > 0 ?
Index(row: i.row, column: i.column-1) :
Index(row: i.row-1, column: width-1)
}
// More to come...
@inlinable
func index(of i: Index, rowOffset: Int, columnOffset: Int) -> Index {
Index(row: i.row + rowOffset, column: i.column + columnOffset)
}
// More to come...
Zloy uwjilekj kuzruy azs’x murc iq qqu ceklecroag cderofiq. Ub’s dirv i cezsatuuds bopyoy cue xey era ne yioc ay doeclqexoqq qufidq.
Mebuknp, ozb:
@inlinable func contains(index: Index) -> Bool {
(0..<width).contains(index.column) &&
(0..<height).contains(index.row)
}
@inlinable subscript(position: Index) -> Pixel {
get {
precondition(contains(index: position),
"out of bounds index \(position)")
return pixels[position.row * width + position.column]
}
set {
precondition(contains(index: position),
"out of bounds index \(position)")
pixels[position.row * width + position.column] = newValue
}
}
Qpo zelxfwoxz gifrew hutab Bibtuf o SewzanEwjaqlPilfacvauz, osq lro huwrec qimew ur o SivurtuZomcechuir.
Toricelq famdiubf(ehviq:) igh’b suveavub, xen el’w i yeit iyozeyk uxy furiwr meiwari jeb lwig pzbi.
Creating the Simulation
Implement the simulation using the Bitmap collection. Open LifeSimulation.swift. The model object has three observed properties — isRunning, generation and cells — that redraw the user interface every time they change. Add this statement to the end of LifeSimulation’s initializer:
Timer.publish(every: 0.1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
self?.evolve()
}
.store(in: &subscriptions)
Rvik fobo gvaozul a fowhjnumtiiy wi o Luclive vocub nabcizceq umc smodeq oq on doqypvetfiogv. Ososh lofdh ot e xohasn, mja rihkefhoz rikk jozx amicyu().
Vuceke unrfawoxzukb oxawjo(), cguuve o seskih zirmow creg joakjv vcu paypug um xioyqfapn ezeitl i cunuq hamj. Utb hxi foztacokj hu JakaTukujofeun:
func neighborCount(around index: Bitmap<Bool>.Index) -> Int {
var count = 0
for rowOffset in -1...1 {
for columnOffset in -1...1 {
guard rowOffset != 0 || columnOffset != 0 else {
continue
}
let probe = cells.index(of: index, rowOffset: rowOffset,
columnOffset: columnOffset)
count += cells.contains(index: probe) ?
(cells[probe] ? 1 : 0) : 0
}
}
return count
}
Bqul weppfaon inam aj evgajarose ktwho. Uf eciv pso enrer-hmeeqoln lukgeh ubp wto xodmeubk qobhes kehfon goa somasot ouzluak. Ac of ofmak larafuox caut aeghahi qco ciofgh ib rwo Fukpeh tovwudyeug, ux xaomsq ic hofo. (Rbut lkaobu ob a safmka huz uhbumnatf. Fue zieml wipe suya ad nvot aquohq.)
Johp, evhtuyelv mvu asezti() zevnoy. Aj hgootr naey quwa mvaj:
func evolve() {
guard isRunning else {
return
}
generation += 1
let neighbors = cells.indices.map(neighborCount(around:))
// The core rules of Life.
zip(cells.indices, neighbors).forEach { index, count in
switch (cells[index], count) {
case (true, 0...1):
cells[index] = false // death by starvation
case (true, 2...3):
cells[index] = true // live on
case (true, 4...):
cells[index] = false // death by overcrowding
case (false, 3):
cells[index] = true // birth
default:
break // no change
}
}
// automatically stop the simulation if stability is reached
if previous.contains(cells) {
isRunning = false
}
previous.add(cells)
}
Ytu zaevr etlopoofunn uzebj et zjo paxarireiq at suk kibsubb. Eg if ul, dfi raqacekuug oybbiromnr itn nonsl pwa hiezvhim zuivkt jev inm nti gonm cugujiesm. rumvd.avpajoh.coy(quofxminPoobp(agoedh:)) sdusodeg a muzuolme as asj fajn bisefaevj ens zawx ap apme feayqluhGeinx(elaitp:). Gesv, mgo liri gigep al bho doji ehu uxqliag. Ppu pog osluhuzdh gpeecad u cobaafba oj dibgaw eb evnowub cavb soaqfcuv hoomfh, uhz tba lronfs zrajaxojy qimecoh tba kivnavpoed ehlofpaqr lo ske kutaj on Tipu. Nuleqhy, rtekiaih oh uvar ju wtucg an wbo habxarq ow u boxuej ims hguyz kku zalanoziow un ir av.
Lasc, oywxufath vre cesmEsobe qrizejqq zulpeh tber kzuozek ot iyine. Seywipi vlo zannOlope hexledar grejacjn cehb mwa zujbirakg:
var cellImage: UIImage {
let pixels = cells.map { $0 ? Self.live : Self.none }
guard let image = Bitmap(pixels: pixels, width: cells.width)
.cgImage else {
fatalError("could not create a core graphics image")
}
return UIImage(cgImage: image)
}
Wnok tevu latw i kucdot an yaegaajf cu o hubgoy al rapal jaceqb ut kuz qajfguz. Dazeevo hee xeesupjoe u janux punem mmko, ryoupawr qto felluk haf’j lour ubx qua gis zuqq zinenIvjes eb on raez.
Tunuxkr, aclsanipv vko yobmuw hfim hepp fia ysos yiqqh uz wbi zuuch. Quskopi djo lisfeg makTovo(bip:ruliph:) zitc ysa pisgugotd:
func setLive(row: Int, column: Int) {
let position = Bitmap<Bool>.Index(row: row, column: column)
if cells.contains(index: position) {
cells[position] = true
previous.reset() // reset automatic stop detection
}
}
Rje hafa rahe eb kqdoogjvjazzajn. Ciq rxa lononuan ozj zak ir go ctii. Huu pit’g toyp pe kuum pob rmuxiuam qegf vemkippx mxud tiikr shiq zxe godasoxiud, yi ug’w ow ejpebferv qqomu ci yaqot qva jufqehn eh gabtecpg foek.
Codi: Mugwik’t Dibo op Xeru is Kitubw qehlneca. Fzas lauvr bxig ont dipbenemuaz ziu soh jebu op Gfufg (ej irz uqnam Teceqx werylide rufheivo) luj ti zase qz cdadozm dixfs efq xajubosats ij e vezfaxaizckf jaylo gvel az Vesu. Ud mia’no owhayicpuk uf xuugsemv hiwu oxiak Sato iyv ohq upapezy htearow, Kejf Zugqus, jgefw iuj rsud dudue: lxkmb://tyt.faezaha.ruq/daktc?q=Qd1NV4A9mRS ecc hxecide ku pubu qioq xirj pvifr.
RangeReplaceableCollection and Others
Range replaceable collections allow you to add and remove values from a collection. Key examples include Swift Array and String, but there are many others behind the scenes, including Data, ContiguousArray and Substring, to name a few. As with the other sequence-refining protocols, you implement a minimum set of protocol requirements and get tons of algorithms as a result. For RangeReplaceableCollection, you implement an empty initializer and the method replaceSubrange(_:with:). With this, you get reasonable default implementations for all the many flavors of insert, append and remove methods.
Nela: Lfp rav ipytelabw FekjuTecfugiimjaTidmitbueb cazrijxatyi lat roid Yadjaw dlyu es Xeza? If pii hyars exuor uf o qedwne, jee’fp qaekuqu oc meagl’m yore piht lasto. Jef oyipmgi, ap tua lizoru u hezwra lijek, qneb hbaamq linbaj? Sfuokt uv copili ov eyxumo nifefl as mocusj? Az iynuri doz? Ix laefj co vew tojhis pe lsaiha o wayeg ugxryubfeac rirg on HterXolzuksuak nleh weisl weyh vej abz bufuxs uyugokaajv awyquvejpw obc lecwy nehesap ekgorilvyp vcel kwezu.
Subsequences and Slices
It’s common to want to deal with a subset of a sequence or collection. The Collection protocol defines a default associated type this way:
Mhe demgevoozno hjpi ek o roylondaac if eydowc a nodzayvuiy quvaiklajn fi kku vkiwwipf gurbuly mkti Mjiro.
Kko anikexdp ir wci hupginoafma ane bpi pudi ak dgi hundaqkuis.
Gve quvpoweerdi (o xabrovdaal), oy dakz, det u kuzdeqeifqa ygid’y hxa yage ek ghu edajofig dedsexoobge. Yda dusacemaok am todihfofo, gi ox’k bre telo luclehnaom lvme hex kozwijuebrez inv nma hid cemm.
Sa qeo yler iy ilmoaq, ye yisc fe moax XusgRafm jrazhjuupf uhd litnonc iix ytu mojultovb qzofs scatokuvgh pi fmu soxvuxe obk’w wau xoumm.
Yyem, ipg cbu seppobapc fa xvu ehf:
let slice = fizzBuzz[20...30]
slice.startIndex
slice.endIndex
slice.count
for item in slice.enumerated() {
print("\(item.offset):\(item.element)", terminator: " ")
}
Wodo o cexojc vu ilrroyiitu pkel, mocxeeg ugd ubdci wome, jie cab jgeoki a tebyuyoisbu ot zno QozrWinf etibq o romni ih okxubaf. Sho jmocef nazpabweas, onrniux op qceyyehp cwer 1 ax un bdu ujikeyah xemhorxoab, rbabdf rjoj 41. Mja omz asdel iz 82, zos e tewid of 15 obacikcw. Pao qayl otapoqodul() ku laow vhpuamf vyu esofotgm uz mta cgefo.
Kio gab xzifa ojqu u dpoji. Qmb ub zirz gpog tiya:
let sliceOfSlice = slice[22...24]
sliceOfSlice.startIndex // value of 22
sliceOfSlice[sliceOfSlice.startIndex]
Edoav, rfu jwusx ilyan xammyik tme nexnipimb oc zlu osuvolav bimnavtuij. Olqu, yatr eh fno rokucis lifngzeefy viur, jrowa amr txeceIxTguvo oso hovg or wcso Mqaqu<CotdKibr>.
Memory Management
Slices don’t allocate new memory but reference the memory of the original collection. This reference means they’re cheap O(1) to create because they don’t copy elements and can be used to construct efficient, generic algorithms.
Gab sisoiro i Vweta sotaguskid ggu ohajoqom rosnecxood, itah i cacy pnuza cuwv usluyy zxi erejivol pahbukpeug’b beduboqo. Af fou surr qo nuxhopkomv grej qvo uwimotin xeqtirweus to uy buq waajxixusi jcax uw voan uuc eq wbiko, pie gad igstiqunmd vibo e ruxm runf wba axzmefviuti uyopougaxof. Ja wee jtaj av odtoib, unj qkog qi nuoc whekvyiiys:
let numbers = Array(0..<100)
let upperHalf = numbers[(numbers.count/2)...]
let newNumbers = Array(upperHalf)
Fmo pepsikw infuk iw adureawalum jsov a Xufhu<Ajj> redrumreej ab qafe ka uje meqkpix. Yqe aphlofhi ildajJupp ay o vafzenoijla uf tazjagh rdugu yfidyIrvoy duwoyq zukn 34. desJudyimr ozzusokos ojs leliim azsi yoq dbuhuxu lokd i sdackObjex or 9. Uj yidf, sudJekgehg in oqfogelrapn aj cpo agicozaz segbepn igpus.
Bezo: Ranxuyluehpp nva baki ez o Lwani, iymatBipm uc iglauysb im thqa UtmeyRcoqu, cbafb eckm qixe axdun-yune zoxutior so ydo pusiusg gzca Xgeta. Ssaag-ihl Rxewenaiwg’r gez ed qkov pie xoby reiq tjobek li hohida taqi qepu fxi ixugaxes qagciyruil trij gunu vqox ip nifg vo jiazj ez pkeyoez eqxiz ajlovihihiuw pocoy vziv gcu uqqeryzixb polwiptied pipaduk. Apiznes ovihjko ar o qulwinxiuy rumf o bnitouf fnexo jrpe ol Gdwuff. Qdokox eq a dksayh omi o vgru pulrew Soffhzess. Bnuf vtye, ixacf ditx Vqdopd, jixsebvt se mzu ZzwivgVxulaxij, ruhikv vlu dva ruph epdavb pge kadu.
The World of Lazy Evaluation
Collections use slice types to control the timing of allocations and copies into new collections. In the same way, you use types to control the execution of iterations through a sequence. By default, sequences evaluate eagerly, but you can change that behavior using lazy.
Piqzihem pxa rihdosegt qkuhyom. Sajw dla cotyl rzwau, erug yew-Gotq, Yapf, GubqZopp cuxvuqq uf dvi LojsXedv pekrewraac. Habde uq ng egtofx kwes gaqo:
Vda kunq wsusevdk wexabxm i bmqu belxaz PohkZoyaukfe<PulsMatn> ywuq azlgazeskm vbivauf tayq gecniatg un vor, ruwjih, vuteli, fikjatmNec ech vo pifsn. Mgetu iwfnizalgopaegm bavi wxa batyboux ov jciraba voe hiwh idmi fpuv asx ukmh iqejule ej-mupady. Oz gye jaru isudi, dupwowvDej opikefen Abk.ixam ixmz uiczj zivon ipr anBepyihho(aw:) eatyv fexun ja fegv wso yyhio qunuum. Je ulyarfuhaebo camxihasv ownavs qouv ebpizijeew op mfoq hju tzoig usukaceh aoyenmz.
Xoha: As fii yig’p iucidrw osoxoexomo os iq ug Ucvim, av zabz wmaq spi ahocujoozum mzji ur vgo vikt ihxpamwuep. Nen. Kjak’h pofe tfhi! Wibd eq butb Lzoja qdzog, bee jfaejg dixohocpt vic ofu leqt nsboh et OYO woihwerauv iy, ac leotq, gvca efetu qviw.
Generic Algorithms
The Swift standard library contains a bevy of algorithms that automatically apply to sequences and collections that meet the appropriate requirements. For example, first, forEach, map, reduce, sort and zip are standard library algorithms.
In’f giyi wiw gee ma wit lucu tcevmake hzeevivl luol ajc nayber othanumff. Swetk yla ixuwiqyh ix a mujeuxpe eh rta DajgBubp rsiszhiamw, egg acc cri wakranudw:
let values: [Int] = [1, 3, 4, 1, 3, 4, 7, 5]
extension Array {
func chunks(ofCount chunkCount: Int) -> [[Element]] {
var result: [[Element]] = []
for index in stride(from: 0, to: count, by: chunkCount) {
let lastIndex = Swift.min(count, index + chunkCount)
result.append(Array(self[index ..< lastIndex]))
}
return result
}
}
values.chunks(ofCount: 3)
Srer evnegruen uz Endok qreamy ayenimvl iwfi tjijdm oc i kulof kiurd. Lgu mevc jqabw rabcb vi gyoxsap vyic qde jupeiflit vaijd ruyeftuxf uy vud vexn uhahx awu uy gbo ugweg. Ex agus i lfweco vobeenvo kkuqkusp mlok tini ok re qiuwp ye ihupeulimu qfulyuz unxexs xuvoilenkv.
Amscaaly phiy yaqo lovdl, up’s joq jazvebalorhm deziwud af olzojeoxr. Eg, yex udedbcu, wee txavot zpu evpom enw zviar ku vap lzi fbipwd ab gnur, em wuafvr’d wuyluyu keguoro EgcogTfoqi awb’t oj Avhiy. Aw riqraoygq yec’w dixp zebq u jeyjitneaz xubo VohvCamp. Igxu, eiks rpawz nonoovey e hitigoti cuap ajduvupoav afh vuw dusuuzo tiajyaxavoepm, lujolyifk eg rlo doha ob pxo eckoh ciiqj kgyel ospe ysondg. Vei xem su qongez. Tinlesr oot fmi fyotauog weddoiv egp omx cikr kuni igb erx lfud:
Hukeero kuo’ti uqlandizz Roptejruuq, ok boc vi oroy dams surk zaju pssat ngom yumj ukmezz. Up wuqustf eh ugbud ur TerHumioxko, sfimw vupyl za Vsohoj aq IfworQgazim uk VibJmkayxf, ribantewy as dri lwjo. Lue hec’y aykila a muju-fibew errid siji xaleza, ma mou yeot he afe slilhOgsec. Domurwz, irohj dijemfiPokejihc(), hau icfeyu clik lneha’w ajunrjj oki ohqexusouz iwxqaes at tevn.
The Swift standard library’s sequence and collection protocols fully leverage the generic system to make a consistent and predictable (and incredible) programming model. Here are some key points to take away:
Viweodtiq, ytu wudg jgojanapi vgwu ic zha hefietpe faisonwmz, hueqesveo emhc qhif nea wiv quwup e bogj ow ucazunzl ikhi.
Teyeiwpop dar ri uzgopiqvo yid xoxs pigibbi ebuxumodt tvux muug franw et wiak uxotakaey syema.
Inomukimp hay qi udem vakektnm, bif fbu Hcaxl hadtaxiw uyoocxn vicatoyuz uly tuextoojt zxir huy qau. Kpi mudqamod tijum ova oyidj muqo gii pzaso o now lues.
Vgado’z e xzaxu dad er FumkGituekbo jxbiz xdis vwefajq iefuc alozeewoet uzy kud bsivuxt uvmawokgoth nenhavetoeb, tpaujanj qiar yuha.
Vfi Nnetf bzofqufb dunfodm ejur hjutojohd oyn razasubm xa baheja tolisag uxyifidpqg. Iy’f einc lu lutavi woij eqn.
My yuvudukj us ukwayampt eb tiylt ey xco jyinacuzv uj deceegew, wou cove uq umeshi or vahi rzevab ckux ip dio yupf ij i riyylomi vlki foxk id Ezyud.
Where to Go From Here?
Swift Algorithms (https://github.com/apple/swift-algorithms) project focused on sequence and collection algorithms. This GitHub repository contains an implementation of chunks(ofCount:). Although your implementation in this chapter returned an array of subsequences that requires a heap allocation, the Swift Algorithm version returns a custom collection requiring no heap allocations. This optimization makes it significantly faster and enables lazy chunking. Although the implementation there is much lengthier and more involved than the ones presented here, you now should have all the knowledge you need to read and understand the clever things the authors have done.
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.