123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434(******************************************************************************)(* *)(* Sek *)(* *)(* Arthur Charguéraud, Émilie Guermeur and François Pottier *)(* *)(* Copyright Inria. All rights reserved. This file is distributed under the *)(* terms of the GNU Lesser General Public License as published by the Free *)(* Software Foundation, either version 3 of the License, or (at your *)(* option) any later version, as described in the file LICENSE. *)(* *)(******************************************************************************)openPublicTypeAbbreviationsopenPublicSettingsopenPrivateSignaturesmodule[@inline]Make(SChunk:SCHUNK)(C:CAPACITY)=structopenCtype'aschunk='aSChunk.ttype'ameasure='aSChunk.measure=|MUnit:'ameasure|MSWeight:'aschunkmeasureletapply=SChunk.apply(* -------------------------------------------------------------------------- *)(* A shareable chunk sequence is organized in several levels. It can be
thought of as a list of levels, where the constructors [Zero] and [Level]
play the roles of nil and cons. *)(* The number of levels is logarithmic in the number of elements in the
sequence. *)(* The data constructor [Zero] represents an empty sequence. It carries a
[default] element, which is used when creating an empty chunk. *)(* A chunk at depth [depth] has capacity [capacity depth]. *)(* The [Level] constructor represents a nonempty sequence. It carries the
total [weight] of the elements of the sequence, followed with a [front]
schunk, which contains elements, a [middle] sequence, whose elements are
*schunks* of elements, and a [back] schunk, which contains elements. *)(* We impose the following "populated-sides invariant": if the front or back
schunk is empty, then the middle sequence must be empty as well. In the
contrapositive, if the middle sequence is nonempty, then the front and back
schunks must be nonempty. *)(* We impose the following "density invariant": in the middle sequence, the
sum of the lengths of two adjacent schunks must exceed [capacity depth]. We
require that this property also hold of the middle sequence in front of
which a fictitious full schunk has been prepended. As a result, the density
invariant implies that no schunk in the middle sequence is empty. *)(* The constructors [One] and [Short] play no role in this file. They are
added in anticipation of the code in [ShortPersistentSequence], which deals
with a more compact representation of short persistent sequences.
Therefore, many of the functions in this file do not handle these cases.
Some functions do handle these cases, because it is easy to do so here (and
this saves a test elsewhere). These functions are: [default], [dummy],
[weight], [is_empty], [iter_segments], [to_array], [print], [peek]. *)type'at=|Zeroof{default:'a;}|Oneof{default:'a;x:'a}|Shortof{default:'a;a:'aarray}|Levelof{weight:weight;front:'aschunk;middle:'aschunkt;back:'aschunk;}(* The structure of a [Level] is symmetric: front and back can be exchanged,
yielding the same structure. This allows us to share code by parameterizing
certain operations with a point of view. *)(* We use the words [this] and [that] to refer to this side -- the one closest
to us, from our point of view -- and that side -- the other side. *)(* -------------------------------------------------------------------------- *)(* The maximum weight of an element can be computed, based on the function
[C.capacity], as follows:
- at depth 0 the maximum element weight is 1;
- at depth 1 the maximum element weight is [C.capacity 0];
- at depth 2 the maximum element weight is [C.capacity 0 * C.capacity 1];
- and so on. *)(* Although we could recompute this information over and over, we prefer to
tabulate it in a fixed-size array. *)(* We use [Sys.word_size] as a bound on the depth. Because [C.capacity depth]
must be at least 2, this approximation is sound. Overflows can take place
while initializing this array, but the meaningless values thus obtained are
never used thereafter, as that would require building a sequence whose
depth is huge. *)letdepth_bound=Sys.word_sizeletmax_elem_weight:weightarray=letmax_elem_weight=Array.makedepth_bound1infordepth=1todepth_bound-1domax_elem_weight.(depth)<-max_elem_weight.(depth-1)*C.capacity(depth-1)done;max_elem_weightlet[@inline]max_elem_weightdepth=assert(0<=depth&&depth<depth_bound);letweight=max_elem_weight.(depth)inassert(0<weight);(* overflow check *)weight(* -------------------------------------------------------------------------- *)(* Basic accessors. *)let[@inline]defaults=matchswith|Zero{default;_}|One{default;_}|Short{default;_}->default|Level{front;_}->SChunk.defaultfront(* [dummy s] extracts a dummy schunk out of [s], if possible; otherwise, it
creates a fresh one. *)let[@inline]dummys=matchswith|Zero{default;_}|One{default;_}|Short{default;_}->SChunk.dummydefault|Level{middle;_}->(* The default element of the middle sequence is a dummy schunk. *)defaultmiddle(* [weight_of] is so named to avoid a clash with the field name [weight].
It is ultimately exported under the name [weight]. *)let[@inline]weight_ofs=matchswith|Zero_->0|One_->1|Short{a;_}->Array.lengtha|Level{weight;_}->weightlet[@inline]is_emptys=matchswith|Zero_->true|One_|Short_|Level_->false(* -------------------------------------------------------------------------- *)(* Construction and destruction. *)letcreatedefault=Zero{default}let[@inline]create_middledefault=letdefault=SChunk.dummydefaultincreatedefault(* [nonempty_level] is a constructor, parameterized by a point of view. As we
do not allow the constructor [Level] to represent an empty sequence, this
constructor cannot be used in the case where the sequence is empty. *)let[@specialise]nonempty_levelpovweightthismiddlethat=assert(weight>0);letfront,back=exchangepovthisthatinLevel{weight;front;middle;back}(* [level] is also a constructor, parameterized by a point of view. It
does not require the sequence to be nonempty. It does not guarantee
that the populated-sides invariant holds. *)let[@inline]levelpovweightthismiddlethat=ifweight=0thenletdefault=SChunk.defaultthisincreatedefaultelsenonempty_levelpovweightthismiddlethat(* [promote p o] converts the schunk [p] to a sequence. *)letpromotepo=letdefault=SChunk.defaultpinletweight=SChunk.weightpin(* An empty front schunk. *)letfront=SChunk.createdefault(SChunk.capacityp)oin(* An empty middle sequence. *)letmiddle=create_middledefaultin(* The schunk [p] becomes the back schunk. *)letback=p(* or: [SChunk.copy p o], but that would be more expensive *)inlevelFrontweightfrontmiddleback(* -------------------------------------------------------------------------- *)(* Iteration. *)let[@specialise]reciter_segments:'a.pov->'at->'asegments=funpovsyield->matchswith|Zero_->()|One{x;_}->yield([|x|],0,1)|Short{a;_}->yield(a,0,Array.lengtha)|Level{front;middle;back;_}->letthis,that=exchangepovfrontbackinSChunk.iter_segmentspovthisyield;iterpov(funp->SChunk.iter_segmentspovpyield)middle;SChunk.iter_segmentspovthatyieldand[@specialise]iter:'a.pov->('a->unit)->'at->unit=funpovfs->ArrayExtra.iteriter_segmentspovfsletiter_leftgs=iterFrontgsletiter_rightgs=iterBackgsletfold_leftfseeds=Adapters.fold_leftiter_leftfseedsletto_lists=Adapters.to_listiter_rights(* -------------------------------------------------------------------------- *)(* Conversion to an array. *)(* We assume zero depth and unit weight. Thus, the weight of the sequence
is also its length. *)(* The code in the last case, [Level], would in fact work in all cases,
but we can save a little time by dealing explicitly with each case. *)letto_arrays=matchswith|Zero_->[||]|One{x;_}->[|x|]|Short{a;_}->Array.copya|Level_->ArrayExtra.concat_segmentsFront(defaults)(weight_ofs)(iter_segmentsFronts)(* -------------------------------------------------------------------------- *)(* Printing. *)(* The [model] pseudo-field is printed only when [m] is [MUnit],
otherwise this creates too much noise. *)modulePrinting=structopenPPrintopenPPrint.OCamlletrecprint:'a.'ameasure->('a->document)->'at->document=funmelements->matchswith|Zero_->!^"Zero"|One{x;_}->!^"One"^^record"pwseq"["model",flowing_listelement[x]]|Short{a;_}->!^"Short"^^record"pwseq"["model",flowing_listelement(Array.to_lista)]|Level{front;middle;back;weight}->letschunk=SChunk.printmelementin!^"Level "^^record"pwseq"(["weight",intweight;"front",schunkfront;"middle",printMSWeightschunkmiddle;"back",schunkback;]@(ifm=MUnitthen["model",flowing_listelement(to_lists);]else[]))endincludePrinting(* -------------------------------------------------------------------------- *)(* Validation. *)letreccheck:'a.'at->'ameasure->depth->owner->unit=funsmdeptho->assert(SChunk.correct_measuremdepth);matchswith|Zero_->()|One_|Short_->(* The data constructors [One] and [Short] are not allowed to appear
under [Level]. Furthermore, [check] must not be applied to a
sequence that has of these data constructors at the root. *)assertfalse|Level{weight;front;middle;back}->(* Since the sequence is nonempty, its weight must be positive. *)assert(weight>0);(* Check the populated-sides invariant. *)ifSChunk.is_emptyfront||SChunk.is_emptybackthenassert(is_emptymiddle);(* Check that the front and back schunks are well-formed. *)SChunk.checkmofront;SChunk.checkmoback;(* Check the middle sequence (before iterating on it). *)check_middlemiddlemdeptho;(* Check that the weight is correct. *)assert(weight=SChunk.weightfront+fold_left(funsump->sum+SChunk.weightp)0middle+SChunk.weightback)andcheck_middle:'a.'aschunkt->'ameasure->depth->owner->unit=funmiddlemdeptho->(* Check that the sequence is well-formed (before iterating on it). *)checkmiddleMSWeight(depth+1)o;(* Check that every element in the middle sequence is a well-formed
schunk. Note that this is *not* a consequence of the fact that
the middle sequence is well-formed. *)iterFront(SChunk.checkmo)middle;(* Check the density invariant: in the middle sequence, the sum of the
lengths of any two adjacent schunks must exceed [capacity depth]. *)letn=capacitydepthinletprevious_length=refnin(* a fictitious full schunk *)iterFront(funp->assert(!previous_length+SChunk.lengthp>n);previous_length:=SChunk.lengthp)middle(* Ensure [check] has zero cost in release mode. *)let[@inline]checksmdeptho=assert(checksmdeptho;true)(* -------------------------------------------------------------------------- *)(* Peek. *)let[@specialise]peekpovs=matchswith|Zero_->raiseEmpty|One{x;_}->x|Short{a;_}->assert(2<=Array.lengtha);beginmatchpovwith|Front->Array.geta0|Back->letn=Array.lengthainArray.geta(n-1)end|Level{front;back;_}->(* Rename [front] and [back] to [this] and [that],
with a possible exchange, depending on [pov]. *)letthis,that=exchangepovfrontback(* Ensure [front] and [back] are not used below. *)andfront,back=(),()infront;back;ifnot(SChunk.is_emptythis)thenSChunk.peekpovthiselseSChunk.peekpovthat(* -------------------------------------------------------------------------- *)(* Push. *)(* Complexity: O(K.log_K n). *)let[@specialise]recpush:'a.pov->'at->'a->'ameasure->depth->owner->'at=funpovsxmdeptho->assert(SChunk.correct_measuremdepth);matchswith|Zero{default}->(* Create an empty schunk. *)letp=SChunk.createdefault(capacitydepth)oin(* Push [x] into it. *)letp=SChunk.pushpovpxmoin(* Promote it to a sequence. *)promotepo|One_|Short_->(* Not handled here. *)assertfalse|Level{weight;front;middle;back}->(* Rename [front] and [back] to [this] and [that],
with a possible exchange, depending on [pov]. *)letthis,that=exchangepovfrontback(* Ensure [front] and [back] are not used below. *)andfront,back=(),()infront;back;letweight=weight+applymxinifnot(SChunk.is_fullthis)thenbegin(* There is room in the front schunk. Push [x] into it. *)letthis=SChunk.pushpovthisxmoinnonempty_levelpovweightthismiddlethatendelseifSChunk.is_emptythatthenbegin(* The front schunk is full. *)(* The back schunk is empty. Per the populated-sides invariant,
the middle sequence must be empty as well. *)assert(is_emptymiddle);(* Push [x] into the back schunk and exchange the front and back
schunks, so [x] ends up in front, as desired. *)letthis,that=SChunk.pushpovthatxmo,thisinnonempty_levelpovweightthismiddlethatendelsebegin(* The front schunk is full. The middle sequence and back schunk
are nonempty. *)(* Create an empty schunk, push [x] into it, and make it the new
front chunk, while pushing the previous front chunk into the
[middle] sequence. *)letp=SChunk.create(SChunk.defaultthis)(capacitydepth)oinletthis,middle=SChunk.pushpovpxmo,pushpovmiddlethisMSWeight(depth+1)oinnonempty_levelpovweightthismiddlethatend(* [push_front_many] and [push_back_many] are iterated pushes.
They do not take a parameter [m]; it is fixed to [MSWeight].
Accordingly, [depth] must be nonzero.
These functions could be defined as follows:
let[@inline] push_back_many s ps depth o =
List.fold_left (fun s p -> push Back s p MSWeight depth o) s ps
let[@inline] push_front_many ps s depth o =
List.fold_right (fun p s -> push Front s p MSWeight depth o) ps s
We specialize them by hand, as follows. *)letrecpush_back_manyspsdeptho=matchpswith|[]->s|p::ps->lets=pushBackspMSWeightdepthoinpush_back_manyspsdeptholetrecpush_front_manypssdeptho=matchpswith|[]->s|p::ps->lets=push_front_manypssdepthoinpushFrontspMSWeightdeptho(* -------------------------------------------------------------------------- *)(* Pop. *)(* Complexity: O(log_K n). *)(* [pop] is cheaper than [push]: note the absence of the factor [K]. Indeed,
[pop] involves restricting our view on an existing support; it never
requires allocating a new support. *)let[@specialise]recpop:'a.pov->'at->'ameasure->owner->'a*'at=funpovsmo->matchswith|Zero_->raiseEmpty|One_|Short_->(* Not handled here. *)assertfalse|Level{weight;front;middle;back}->(* Rename [front] and [back] to [this] and [that],
with a possible exchange, depending on [pov]. *)letthis,that=exchangepovfrontback(* Ensure [front] and [back] are not used below. *)andfront,back=(),()infront;back;ifSChunk.is_emptythisthenbegin(* The front schunk is empty. Per the populated-sides invariant,
the middle sequence must be empty as well. Thus, the back
schunk cannot be empty. *)assert(is_emptymiddle);assert(not(SChunk.is_emptythat));(* Pop an element [x] off the back schunk. *)letx,that=SChunk.poppovthatmoinletweight=weight-applymxinx,levelpovweightthismiddlethatendelsebegin(* Pop an element [x] off the front schunk. *)letx,this=SChunk.poppovthismoinletweight=weight-applymxin(* This test is identical to the one in [level]. *)ifweight=0thenletdefault=SChunk.defaultthisinx,createdefaultelse(* Restore the populated-sides invariant. *)(* This is a specialized version of [populate]. *)letthis,middle=ifSChunk.is_emptythis&¬(is_emptymiddle)thenpoppovmiddleMSWeightoelsethis,middleinx,nonempty_levelpovweightthismiddlethatend(* -------------------------------------------------------------------------- *)(* Restoring the populated-sides invariant. *)(* These functions use [pop], which is why they are placed here. *)(* [populate pov s o] restores one side of the populated-sides invariant. *)let[@inline]populatepovso=matchswith|Zero_->s|One_|Short_->(* Not handled here. *)assertfalse|Level{weight;front;middle;back}->(* Rename [front] and [back] to [this] and [that],
with a possible exchange, depending on [pov]. *)letthis,that=exchangepovfrontback(* Ensure [front] and [back] are not used below. *)andfront,back=(),()infront;back;ifSChunk.is_emptythis&¬(is_emptymiddle)thenbegin(* The invariant is broken on this side: the front chunk is
empty, yet the middle sequence is nonempty. Restore the
invariant by popping one element off the middle sequence
and making it the front chunk. *)letthis,middle=poppovmiddleMSWeightoinassert(not(SChunk.is_emptythis));nonempty_levelpovweightthismiddlethatendelses(* The smart constructor [populate_nonempty_level] requires the sequence to be
nonempty and restores one side of the populated-sides invariant. *)let[@specialise]populate_nonempty_levelpovweightfrontmiddlebacko=assert(weight>0);lets=Level{weight;front;middle;back}inlets=populatepovsoins(* The smart constructor [populate_level] supports the case where the sequence
is empty and restores one side of the populated-sides invariant. *)let[@inline]populate_levelpovweightfrontmiddlebacko=ifweight=0thenletdefault=SChunk.defaultfrontincreatedefaultelsepopulate_nonempty_levelpovweightfrontmiddlebacko(* -------------------------------------------------------------------------- *)(* Constructors for sequences of a known size [size], operating only at depth
zero, with unit weights. *)(* [create_by_segments default size o create_schunk] builds a sequence
of length [size]. It expects a function [create_schunk] such that
[create_schunk n i k] produces a schunk of capacity [n] for the
subsequence of length [k] that begins at index [i]. *)letcreate_by_segmentsdefaultsizeocreate_schunk=(* We assume the depth is zero. *)letdepth=0in(* A fast path for empty sequences. *)ifsize=0thencreatedefaultelsebeginletn=capacitydepthin(* We assume unit weight. *)letweight=sizein(* This calling convention is more convenient here. *)let[@inline]create_schunk(i,k)=create_schunknikin(* Cut the range into a nonempty front segment, a possibly empty
series of segments of size [n], and a possibly empty back segment.
[cut] guarantees that if the back segment is empty then the middle
sequence is empty as well. *)letfront,foreach_middle_segment,back=ArrayExtra.cutnnsizein(* The front, middle, and back must be created in this order. Indeed,
[create_schunk] may have side effects, and the order of these side
effects matters. *)(* Construct the front. *)letfront=create_schunkfrontin(* Construct the middle. *)letmiddle=ref(create_middledefault)inforeach_middle_segment(funik->middle:=pushBack!middle(create_schunk(i,k))MSWeight(depth+1)o);letmiddle=!middlein(* Construct the back. *)letback=create_schunkbackin(* Build a level. Because [cut] is designed to enforce the populated-sides
invariant by construction, there is no need to call [populate]. *)Level{weight;front;middle;back}end(* Conversion from an array. *)letof_array_segmentdefaultaheadsizeo=assert(Segment.is_valid(a,head,size));create_by_segmentsdefaultsizeo(funnik->(* Construct the segment [i, i + k) of the sequence using the elements in
the segment [head + i, head + i + k) of the array [a]. *)SChunk.of_array_segmentdefaultna(head+i)ko)letmakedefaultsizevo=assert(0<=size);(* Because this is a persistent sequence, we can create a single schunk of
capacity [n] and size [n], filled with the value [v], and re-use it over
and over. *)(* This optimization relies on the assumption that [o] is [Owner.none]. *)assert(o=Owner.none);(* We apply this optimization only at depth 0, not at greater depths (even
though it is possible in principle), because it is easy. *)letdepth=0inletn=capacitydepthinletp=SChunk.makedefaultnnvoincreate_by_segmentsdefaultsizeo(funn_ik->assert(n=SChunk.capacityp);(* We need a schunk of capacity [n] and size [k], filled with [k] copies
of the value [v]. *)(* If [k] is [n], we can use our ready-made schunk. If [k] is less than
[n], we create a new schunk with a smaller view, based on the same
support. *)ifk=nthenpelseletp1,_x=SChunk.takepkMUnit1oinp1)letinitdefaultsizefo=assert(0<=size);create_by_segmentsdefaultsizeo(funnik->(* Allocate a schunk of capacity [n] and size [k],
filled with the values [f i], [f (i+1)], ..., [f (i+k-1)]. *)SChunk.initdefaultnkifo)(* -------------------------------------------------------------------------- *)(* [fuse_back middle p o depth] pushes the schunk [p] onto the back of
[middle], which is a sequence of schunks. If possible, the last schunk of
the sequence and the schunk [p] are fused into a single schunk. This allows
preserving the density invariant. *)(* By convention, pass [depth], not [depth + 1] -- [fuse_back] takes care of
adding one. *)let[@inline]fuse_backmiddlepodepth=ifSChunk.is_emptypthen(* [p] is empty. There is nothing to do. *)middleelseifis_emptymiddlethen(* [middle] is empty. Just push [p] into [middle]. *)pushBackmiddlepMSWeight(depth+1)oelse(* The capacity at the current depth is given by [SChunk.capacity p]. *)letn=SChunk.capacitypinifSChunk.length(peekBackmiddle)+SChunk.lengthp>nthen(* Concatenating the last element of [middle] with [p] is forbidden.
Just push [p] into [middle]. *)pushBackmiddlepMSWeight(depth+1)oelsebegin(* [middle] is nonempty and its last element [last] can be concatenated
with [p]. Then, pop [last] off [middle], concatenate [last] and [p],
and push the result back into [middle]. *)letlast,middle=popBackmiddleMSWeightoinletlast=SChunk.concatlastpoinpushBackmiddlelastMSWeight(depth+1)oend(* -------------------------------------------------------------------------- *)(* [eject this middle that] returns either [this, that] or [that, this].
It allows itself to exchange [this] and [that] only when both [this]
and [middle] are empty, so the model of the sequence is not affected.
Provided the sequence [this/middle/that] is nonempty, we are assured
that the first component of the result is a nonempty schunk. *)let[@inline]ejectthismiddlethat=ifSChunk.is_emptythisthenbeginassert(is_emptymiddle);that,thisendelsethis,that(* -------------------------------------------------------------------------- *)(* Replacement of a boundary element. *)(* [overwrite pov s x m o yweight] replaces the first or last element of the
sequence [s], depending on the point of view [pov], with the element [x].
[s] must be nonempty. The element [y] that is overwritten is not read and
must not be read; it may happen to be [x] itself, which has undergone an
in-place update. Its weight [yweight] must be provided by the caller.
The cost is of this operation is O(K). This is cheaper than a combination
of [pop] and [push]: indeed, in contrast with a combination of [pop] and
[push], [overwrite] never needs to modify the middle sequence. *)letoverwritepovsxmoyweight=matchswith|Zero_|One_|Short_->(* An empty sequence is not allowed; and [One] and [Short] are not
handled here. *)assertfalse|Level{weight;front;middle;back}->(* Rename [front] and [back] to [this] and [that],
with a possible exchange, depending on [pov]. *)letthis,that=exchangepovfrontback(* Ensure [front] and [back] are not used below. *)andfront,back=(),()infront;back;(* Compute an updated weight. *)letweight=weight-yweight+applymxinifnot(SChunk.is_emptythis)thenbegin(* The first element of the sequence is the first element of [this].
Replace it with [x], yielding an updated schunk [this]; then
compute the updated weight of this level and build an updated
level. *)letthis=SChunk.overwritepovthisxmoyweightinnonempty_levelpovweightthismiddlethatendelsebegin(* [this] is empty. Per the populated-sides invariant, the middle
sequence must be empty as well. Therefore, the first element of the
sequence is the first element of [that]. Replace it with [x]. *)assert(is_emptymiddle);letthat=SChunk.overwritepovthatxmoyweightinnonempty_levelpovweightthismiddlethatend(* -------------------------------------------------------------------------- *)(* Concatenation and its auxiliary functions. *)(* [split_first xs] extracts the first element of the nonempty list [xs]. *)let[@inline]split_firstxs=matchxswith|[]->assertfalse|x::xs->x,xs(* [split_last xs] extracts the last element of the nonempty list [xs]. *)letrecsplit_lastxs=matchxswith|[]->assertfalse|[x]->[],x|x::xs->letxs,last=split_lastxsinx::xs,last(* [compress ps o] expects a short list [ps] of nonempty schunks of a common
capacity. It greedily fuses adjacent schunks, from left to right, whenever
their combined length does not exceed this capacity. Thus, in the result
list, the combined length of any two adjacent schunks must exceed the
capacity: that is, the density invariant holds. The first (resp. last)
schunk of the result list is either the first (resp. last) schunk of [ps],
physically unchanged, or a fusion of several schunks of [ps]. *)(* [compress1 p ps o] is equivalent to [compress (p :: ps) o]. *)(* [compress ps o] can update the schunks in the list [ps] in place if this
is allowed by the owner [o]. *)let[@tail_modulo_cons]reccompress1ppso=matchpswith|[]->[p]|q::ps->assert(SChunk.lengthq>0);ifSChunk.lengthp+SChunk.lengthq<=SChunk.capacitypthencompress1(SChunk.concatpqo)psoelsep::compress1qpsolet[@inline]compresspso=matchpswith|[]->[]|p::ps->assert(SChunk.lengthp>0);compress1ppso(* [merge m1 ps m2 o depth] concatenates [m1], [l], and [m2]. [m1] and [m2]
are middle sequences, that is, sequences of schunks. [ps] is a list of
nonempty schunks, whose length is at most 4.
The sequence of schunks formed by the last schunk of [m1], followed with the
schunk in the list [ps], followed with the first schunk of [m2], does not
necessarily satisfy the density invariant. Therefore it must be compressed.
This is done by using [compress] and [overwrite].
The result is a valid middle sequence. In particular, the density invariant
holds: in this sequence, the population of two consecutive schunks exceeds
the capacity of a schunk. *)(* The cost of [compress] and [overwrite] is O(K). In comparison, if instead of
[overwrite] one used [pop] and [push] then the cost would be O(K log_K n). A
(cheap) [pop] operation is needed only in the case where the boundary schunks
of [m1] and [m2] and the schunks of [ps] are compressed into a single schunk.
A bounded number of (expensive) [push] operations are needed only in the
terminal cases where [m1] or [m2] is empty. Thus, the total cost of [merge],
summed over all levels, is O(K log_K n + log_K^2 n). *)(* A subtle point: [compress], which uses [SChunk.concat], may update a schunk
in place, if this schunk is uniquely owned by [o]. This causes the weight
of this schunk to change. We read the original weight of this schunk prior
to calling [compress], and pass this weight as an argument to [overwrite]. *)(* By convention, pass [depth], not [depth + 1] -- [merge] takes care of
adding one. *)letrecmerge:'a.'aschunkt->'aschunklist->'aschunkt->owner->depth->'aschunkt=funm1psm2odepth->assert(List.lengthps<=4);matchm1,ps,m2with|(One_|Short_),_,_|_,_,(One_|Short_)->(* Not handled here. *)assertfalse|Zero_,_,Zero_->(* Both sequences are empty. Compress [ps] and turn it into a sequence
of at most 4 schunks. *)letps=compresspsoinpush_back_manym1ps(depth+1)o|Zero_,[],Level_->m2|Zero_,_,Level_->(* [m1] is empty. This is a terminal case: there is no recursive call to
[merge] in this case. Thus the cost of this case is paid just once,
as opposed to once per level. *)(* The schunks of [ps] must be pushed onto the front of [m2]. To restore
the density invariant at the seam, the first element of [m2], say [q],
must take part in the fusion. Thus, we construct the list [ps @ [q]],
compress this list, extract the last element of the resulting list, say
[q'], and overwrite [q] with [q'] in [m2]. The remaining schunks are
inserted into [m2] by [push_front_many]. This requires a bounded number
of [push] operations, whose cost is O(K.log_K n). *)letq=peekFrontm2inletwq=SChunk.weightqin(* read weight before calling [compress]! *)letps,q'=split_last(compress(ps@[q])o)inletm2=overwriteFrontm2q'MSWeightowqinpush_front_manypsm2(depth+1)o|Level_,[],Zero_->m1|Level_,_,Zero_->(* Symmetric to the previous case. *)letp=peekBackm1inletwp=SChunk.weightpinletp',ps=split_first(compress(p::ps)o)inletm1=overwriteBackm1p'MSWeightowpinpush_back_manym1ps(depth+1)o|Level_,_,Level_->(* A combination of the previous two cases. *)(* The last element of [m1] and the first element of [m2] take part
in the fusion, so that the density invariant holds at the seam. *)letp=peekBackm1andq=peekFrontm2inifps=[]&&SChunk.lengthp+SChunk.lengthq>SChunk.capacitypthen(* Fast path: there is nothing to insert between [m1] and [m2],
and the seam already satisfies the density invariant. *)gluem1[]m2odepthelseletwp=SChunk.weightpandwq=SChunk.weightqinmatchcompress(p::ps@[q])owith|[p']->(* All schunks, including [p] and [q], have been compressed into a
single schunk [p']. Overwrite [p] with [p'] in [m1] and pop [q] out
of [m2]. *)letm1=overwriteBackm1p'MSWeightowpinlet_q,m2=popFrontm2MSWeightoinassert(_q==q);gluem1[]m2odepth|p'::ps->letps,q'=split_lastpsin(* Overwrite [p] with [p'] in [m1] and overwrite [q] with [q'] in
[m2]. There remains to insert the schunks in the list [ps]
between the sequences [m1] and [m2]; this is done by [glue]. *)letm1=overwriteBackm1p'MSWeightowpandm2=overwriteFrontm2q'MSWeightowqingluem1psm2odepth|[]->assertfalse(* [glue] is identical to [merge], but requires the sequence of schunks formed
by the last schunk of [m1], followed with the schunks in the list [ps],
followed with the first schunk of [m2], to satisfy the density invariant. *)andglue:'a.'aschunkt->'aschunklist->'aschunkt->owner->depth->'aschunkt=funm1psm2odepth->assert(List.lengthps<=4);matchm1,m2with|(One_|Short_),_|_,(One_|Short_)->(* Not handled here. *)assertfalse|Zero_,_->push_front_manypsm2(depth+1)o|_,Zero_->push_back_manym1ps(depth+1)o|Level{weight=weight1;front=front1;middle=middle1;back=back1},Level{weight=weight2;front=front2;middle=middle2;back=back2}->(* Compute the weight of the level that we wish to build. *)letweight=weight1+weight2+List.fold_left(funwp->w+SChunk.weightp)0psin(* Exchange the front and back schunks of [m1], if necessary, to
ensure that its front schunk is nonempty. *)letfront1,back1=ejectfront1middle1back1inassert(not(SChunk.is_emptyfront1));(* Similarly, ensure that the back schunk of [m2] is nonempty. *)letback2,front2=ejectback2middle2front2inassert(not(SChunk.is_emptyback2));(* The meta-schunks [back1] and [front2], as well as the schunks in the
list [ps], must find a place in the middle of the new level. We store
the elements of [ps] in [back1] or [front2] if possible, otherwise in
one or more fresh meta-schunks. (Because [ps] has length at most 4, one
fresh meta-schunk is usually enough; however, if the capacity K is only
2 or 3 then two fresh meta-schunks may be needed.) The list [extra],
which we construct below, holds these fresh meta-schunks. *)(* These rearrangements do not affect the density invariant at this level:
indeed the logical sequence of schunks is unchanged. *)letnps=List.lengthpsinletback1,extra,front2=ifnps=0thenback1,[],front2elseifSChunk.lengthback1+nps<=SChunk.capacityback1then(* Push every schunk in the list [ps] into [back1]. *)List.fold_left(funcp->SChunk.pushBackcpMSWeighto)back1ps,[],front2elseifSChunk.lengthfront2+nps<=SChunk.capacityfront2then(* Push every schunk in the list [ps] into [front2]. *)back1,[],List.fold_right(funpc->SChunk.pushFrontcpMSWeighto)psfront2else(* At least one fresh meta-schunk is needed. (Most likely, only one.) *)letn=SChunk.capacityback1inlet[@inline]fresh()=SChunk.create(SChunk.defaultback1)noinletrecpackcps=matchpswith|[]->[c]|p::ps->ifSChunk.lengthc<nthenpack(SChunk.pushBackcpMSWeighto)pselsec::pack(SChunk.pushBack(fresh())pMSWeighto)psinback1,pack(fresh())ps,front2inassert(List.lengthextra<=2);assert(List.lengthextra<=1||SChunk.capacityfront2<4);(* Build the list of (nonempty) meta-schunks that lie between [middle1]
and [middle2] and invoke [merge] one level down. *)letps=ifSChunk.is_emptyfront2then[]else[front2]inletps=extra@psinletps=ifSChunk.is_emptyback1thenpselseback1::psinassert(List.lengthps<=4);letmiddle=mergemiddle1psmiddle2o(depth+1)in(* Build a new level. Because [front1] and [back2] are nonempty,
the populated-sides invariant holds. *)Level{weight;front=front1;middle;back=back2}(* [concat s1 s2 o depth] concatenates the sequences [s1] and [s2]. *)letconcats1s2odepth=matchs1,s2with|Zero_,s|s,Zero_->s|(One_|Short_),_|_,(One_|Short_)->(* Not handled here. *)assertfalse|Level{weight=weight1;front=front1;middle=middle1;back=back1},Level{weight=weight2;front=front2;middle=middle2;back=back2}->(* Add the weights. *)letweight=weight1+weight2in(* Exchange the front and back schunks of [s1], if necessary, to ensure
that its front schunk is nonempty. *)letfront1,back1=ejectfront1middle1back1inassert(not(SChunk.is_emptyfront1));(* Similarly, ensure that the back schunk of [s2] is nonempty. *)letback2,front2=ejectback2middle2front2inassert(not(SChunk.is_emptyback2));(* The schunks [back1] and [front2] must be inserted between the two
middle sequences. Use them (if they are nonempty) to create a list
of schunks [ps] whose length is at most two. *)letps=ifSChunk.is_emptyfront2then[]else[front2]inletps=ifSChunk.is_emptyback1thenpselseback1::psin(* Let [merge] perform the bulk of the work. *)letmiddle=mergemiddle1psmiddle2odepthin(* Build a new level. Because [front1] and [back2] are nonempty,
the populated-sides invariant holds. *)Level{weight;front=front1;middle;back=back2}(* [fuse s1 s2 o depth] concatenates the sequences [s1] and [s2], which are
sequences of schunks, repairing the density invariant at the seam. It is
just [merge] with an empty list [ps] in the middle. *)let[@inline]fuses1s2odepth=merges1[]s2odepth(* -------------------------------------------------------------------------- *)(* Split. *)letrecthree_way_split:'a.'at->weight->'ameasure->depth->owner->'at*'a*'at=funsimdeptho->assert(SChunk.correct_measuremdepth);matchswith|Zero_->assertfalse|One_|Short_->(* Not handled here. *)assertfalse|Level{weight;front;middle;back}->assert(0<=i&&i<weight);letweight_front=SChunk.weightfrontinifi<weight_frontthenbegin(* The desired element lies in the front schunk. *)letfront1,x,front2=SChunk.three_way_splitfrontim(max_elem_weightdepth)oinletweight1=SChunk.weightfront1inlets1=promotefront1oinletweight2=weight-weight1-applymxinlets2=populate_levelFrontweight2front2middlebackoins1,x,s2endelseleti=i-weight_frontinletweight_middle=weight_ofmiddleinifweight_middle<=ithenbeginleti=i-weight_middlein(* The desired element lies in the back schunk. *)letback1,x,back2=SChunk.three_way_splitbackim(max_elem_weightdepth)oinletweight2=SChunk.weightback2inlets2=promoteback2oinletweight1=weight-weight2-applymxinlets1=populate_levelBackweight1frontmiddleback1oins1,x,s2endelsebegin(* The desired element lies in the middle. *)(* Split the middle sequence. *)letmiddle1,p,middle2=three_way_splitmiddleiMSWeight(depth+1)oin(* We now know that the desired element lies in the schunk [p]. *)letweight_middle1=weight_ofmiddle1inleti=i-weight_middle1in(* Split this schunk! *)letp1,x,p2=SChunk.three_way_splitpim(max_elem_weightdepth)oin(* To the left of [x], we have [front], [middle1], and [p1]. *)letweight1=SChunk.weightfront+weight_middle1+SChunk.weightp1inlets1=populate_nonempty_levelBackweight1frontmiddle1p1oin(* To the right of [x], we have [p2], [middle2], and [back]. *)letweight2=weight-weight1-applymxin(* or: SChunk.weight p2 + weight_of middle2 + SChunk.weight back *)lets2=populate_nonempty_levelFrontweight2p2middle2backoins1,x,s2end(* Check that [three_way_split] satisfies its postcondition. *)let[@inline]three_way_splitsimdeptho=letresult=three_way_splitsimdepthoinassert(lets1,x,_s2=resultinweight_ofs1<=i&&i<weight_ofs1+applymx);result(* -------------------------------------------------------------------------- *)(* Take. *)(* [take] is a specialized version of [three_way_split]. Instead of returning
a triple [s1, x, s2], it returns only the pair [s1, x]. *)letrectake:'a.'at->weight->'ameasure->depth->owner->'at*'a=funsimdeptho->assert(SChunk.correct_measuremdepth);matchswith|Zero_->assertfalse|One_|Short_->(* Not handled here. *)assertfalse|Level{weight;front;middle;back}->assert(0<=i&&i<weight);letweight_front=SChunk.weightfrontinifi<weight_frontthenbegin(* The desired element lies in the front schunk. *)letfront1,x=SChunk.takefrontim(max_elem_weightdepth)oinlets1=promotefront1oins1,xendelseleti=i-weight_frontinletweight_middle=weight_ofmiddleinifweight_middle<=ithenbeginleti=i-weight_middlein(* The desired element lies in the back schunk. *)letback1,x=SChunk.takebackim(max_elem_weightdepth)oinletweight1=SChunk.weightfront+weight_ofmiddle+SChunk.weightback1inlets1=populate_levelBackweight1frontmiddleback1oins1,xendelsebegin(* The desired element lies in the middle. *)(* Split the middle sequence. *)letmiddle1,p=takemiddleiMSWeight(depth+1)oin(* We now know that the desired element lies in the schunk [p]. *)letweight_middle1=weight_ofmiddle1inleti=i-weight_middle1in(* Split this schunk! *)letp1,x=SChunk.takepim(max_elem_weightdepth)oin(* To the left of [x], we have [front], [middle1], and [p1]. *)letweight1=SChunk.weightfront+weight_middle1+SChunk.weightp1inlets1=populate_nonempty_levelBackweight1frontmiddle1p1oins1,xend(* -------------------------------------------------------------------------- *)(* Drop. *)(* [drop] is a specialized version of [three_way_split]. Instead of returning
a triple [s1, x, s2], it returns only the pair [x, s2]. *)letrecdrop:'a.'at->weight->'ameasure->depth->owner->'a*'at=funsimdeptho->assert(SChunk.correct_measuremdepth);matchswith|Zero_->assertfalse|One_|Short_->(* Not handled here. *)assertfalse|Level{weight;front;middle;back}->assert(0<=i&&i<weight);letweight_front=SChunk.weightfrontinifi<weight_frontthenbegin(* The desired element lies in the front schunk. *)letx,front2=SChunk.dropfrontim(max_elem_weightdepth)oinletweight2=SChunk.weightfront2+weight_ofmiddle+SChunk.weightbackinlets2=populate_levelFrontweight2front2middlebackoinx,s2endelseleti=i-weight_frontinletweight_middle=weight_ofmiddleinifweight_middle<=ithenbeginleti=i-weight_middlein(* The desired element lies in the back schunk. *)letx,back2=SChunk.dropbackim(max_elem_weightdepth)oinlets2=promoteback2oinx,s2endelsebegin(* The desired element lies in the middle. *)(* Split the middle sequence. *)letp,middle2=dropmiddleiMSWeight(depth+1)oin(* We now know that the desired element lies in the schunk [p]. *)letweight_of_middle1=weight_ofmiddle-SChunk.weightp-weight_ofmiddle2inleti=i-weight_of_middle1in(* Split this schunk! *)letx,p2=SChunk.droppim(max_elem_weightdepth)oin(* To the right of [x], we have [p2], [middle2], and [back]. *)letweight2=SChunk.weightp2+weight_ofmiddle2+SChunk.weightbackinlets2=populate_nonempty_levelFrontweight2p2middle2backoinx,s2end(* -------------------------------------------------------------------------- *)(* Get. *)(* [get s i m depth] can be viewed as a specialized version of
[three_way_split s i m depth o]. Instead of returning a triple [s1, x, s2],
it returns only the pair [i - weight_of s1, x]. *)letrecget:'a.'at->weight->'ameasure->depth->weight*'a=funsimdepth->assert(SChunk.correct_measuremdepth);matchswith|Zero_|One_|Short_->assertfalse|Level{weight;front;middle;back}->assert(0<=i&&i<weight);letweight_front=SChunk.weightfrontinifi<weight_frontthen(* The desired element lies in the front schunk. *)SChunk.get_by_weightm(max_elem_weightdepth)frontielseleti=i-weight_frontinletweight_middle=weight_ofmiddleinifweight_middle<=ithenleti=i-weight_middlein(* The desired element lies in the back schunk. *)SChunk.get_by_weightm(max_elem_weightdepth)backielse(* The desired element lies in the middle. *)leti,p=getmiddleiMSWeight(depth+1)inSChunk.get_by_weightm(max_elem_weightdepth)pi(* -------------------------------------------------------------------------- *)(* Update. *)(* Whereas [get] returns a pair of an adjusted weight-index [i] and element
[x], [update] passes [i] and [x] to the update function [f] that it
receives as an argument. Thus, there is a CPS-style intuition. *)(* The code is written so as to preserve sharing: if possible, the resulting
sequence is physically equal to [s]. This can be the case either when the
sequence uniquely owns the schunk that is updated or when this schunk is
shared but the update has no effect. *)(* For simplicity, we assume that the update is weight-invariant, that is, it
replaces an element with a new element of equal weight. This allows us to
not update the [weight] field. The code could be generalized to allow
changes in the weight: it should be sufficient to take an additional
parameter [delta] and increase the weight by [delta] at every level. In
that case, as soon as [delta] is nonzero, sharing becomes impossible. *)letrecupdate:'a.'ameasure->depth->owner->'aupdate->'atupdate=funmdepthofsi->assert(SChunk.correct_measuremdepth);matchswith|Zero_->assertfalse|One_|Short_->(* Not handled here. *)assertfalse|Level{weight;front;middle;back}->assert(0<=i&&i<weight);letweight_front=SChunk.weightfrontinifi<weight_frontthen(* The desired element lies in the front schunk. *)letfront'=SChunk.update_by_weightm(max_elem_weightdepth)offrontiiniffront==front'thenselseletfront=front'inLevel{weight;front;middle;back}elseleti=i-weight_frontinletweight_middle=weight_ofmiddleinifweight_middle<=ithenleti=i-weight_middlein(* The desired element lies in the back schunk. *)letback'=SChunk.update_by_weightm(max_elem_weightdepth)ofbackiinifback==back'thenselseletback=back'inLevel{weight;front;middle;back}else(* The desired element lies in the middle sequence. *)letmiddle'=updateMSWeight(depth+1)o(SChunk.update_by_weightm(max_elem_weightdepth)of)middleiinifmiddle==middle'thenselseletmiddle=middle'inLevel{weight;front;middle;back}(* Set. *)let[@inline]setsimdepthox'=letfxi=(* [x] and [x'] must have the same weight. *)assert(applymx=applymx');(* The weight index [i] must fall inside [x],
and inside [x'], which is the same thing. *)assert(0<=i&&i<applymx);x'inupdatemdepthofsi(* For the record, a naive implementation of [set]:
let set s i m o x =
let s1, _, s2 = three_way_split s i m o in
concat s1 (push Front s2 x m o) o
*)(* -------------------------------------------------------------------------- *)(* Public bindings. *)letweight=weight_ofend(* Make *)