123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108moduletypeELEMENT=sigtypetvalcompare:t->t->intvalpp:Format.formatter->t->unitendmoduletypeS=sigtypeelttypetvalzero:tvalis_zero:t->boolvalsingleton:elt->int->tvalof_list:(elt*int)list->tvalto_list:t->(elt*int)listvalweight:t->elt->intvaladd:t->t->tvalnegate:t->tvalsub:t->t->tvalscale:int->t->tvalmap:(elt->elt)->t->tvalfilter:(elt->bool)->t->tvaldistinct:t->tvalsupport:t->eltlistvalcardinality:t->intvaltotal_weight:t->intvaliter:(elt->int->unit)->t->unitvalfold:(elt->int->'a->'a)->t->'a->'avalequal:t->t->boolvalpp:Format.formatter->t->unitendmoduleMake(E:ELEMENT)=structmoduleM=Map.Make(E)typeelt=E.t(* Invariant: no entry ever has weight 0 — all constructors below drop them, so
[equal] is exact and [is_zero] is just emptiness. *)typet=intM.tletzero=M.emptyletis_zero=M.is_empty(* Add [w] to [e]'s weight, removing the entry if it cancels to 0. This is the
chokepoint for the {e summing} paths ([singleton]/[of_list]/[add]/[map]),
where two contributions can cancel. The direct-build ops below
([negate]/[scale]/[filter]/[distinct]) each preserve the no-zero-weight
invariant by construction on a canonical input, so they may bypass it. *)letadd_weightmew=ifw=0thenmelseM.updatee(function|None->Somew|Somew0->lets=w0+winifs=0thenNoneelseSomes)m;;letsingletonew=add_weightM.emptyewletof_listpairs=List.fold_left(funm(e,w)->add_weightmew)M.emptypairsletto_listm=M.bindingsmletweightme=matchM.find_optemwith|Somew->w|None->0;;letaddab=M.fold(funewacc->add_weightaccew)ba(* invariant-safe: negating a nonzero weight stays nonzero *)letnegatem=M.map(funw->-w)mletsubab=adda(negateb)(* invariant-safe: [n = 0] must short-circuit to empty, else [n * w] (n, w both
nonzero) stays nonzero *)letscalenm=ifn=0thenM.emptyelseM.map(funw->n*w)mletmapfm=M.fold(funewacc->add_weightacc(fe)w)mM.empty(* invariant-safe: keeps a subset of already-nonzero entries *)letfilterpm=M.filter(fune_->pe)m(* invariant-safe: only emits weight 1 *)letdistinctm=M.fold(funewacc->ifw>0thenM.adde1accelseacc)mM.emptyletsupportm=List.mapfst(M.bindingsm)letcardinalitym=M.cardinalmlettotal_weightm=M.fold(fun_wacc->acc+w)m0letiterfm=M.iterfmletfoldfmacc=M.foldfmaccletequalab=M.equalInt.equalabletppppfm=Format.fprintfppf"{@[";letfirst=reftrueinM.iter(funew->if!firstthenfirst:=falseelseFormat.fprintfppf",@ ";Format.fprintfppf"%a: %d"E.ppew)m;Format.fprintfppf"@]}";;end