···11+Instanciate a Range functor and defining some pretty-printers
22+33+```ocaml
44+let range_to_string r =
55+ let (a, b) = Lunar.Date.Range.bounds r in
66+ Lunar.Date.to_string a ^ " .. " ^ Lunar.Date.to_string b
77+88+module S = Desert.Range_set.Make_over_range
99+ (Lunar.Date) (Lunar.Date.Range)
1010+1111+let dump r = r |> S.to_list |> List.map range_to_string
1212+```
1313+1414+```ocaml
1515+let today = Lunar.Date.from_string_exn "2026-04-07"
1616+let start = Lunar.Date.start_of_month today
1717+let stop = Lunar.Date.end_of_month today
1818+let stopw = Lunar.Date.end_of_week today
1919+let startw = Lunar.Date.start_of_week today
2020+let r = S.empty
2121+2222+let range_a = Lunar.Date.Range.make ~first:start ~last:today
2323+let range_b = Lunar.Date.Range.make ~first:today ~last:stop
2424+let range_c = Lunar.Date.Range.make ~first:start ~last:stop
2525+let range_d = Lunar.Date.Range.make ~first:startw ~last:stopw
2626+let range_e = Lunar.Date.Range.make ~first:startw ~last:today
2727+let range_f = Lunar.Date.Range.make ~first:today ~last:stopw
2828+let range_g = Lunar.Date.Range.make ~first:start ~last:startw
2929+let range_h = Lunar.Date.Range.make ~first:stopw ~last:stop
3030+```
3131+3232+```ocaml
3333+# dump r ;;
3434+- : string list = []
3535+```
3636+3737+```ocaml
3838+# r |> S.add range_a |> dump
3939+- : string list = ["2026-04-01 .. 2026-04-07"]
4040+```
4141+4242+```ocaml
4343+# r |> S.add range_a |> S.add range_b |> dump
4444+- : string list = ["2026-04-01 .. 2026-04-30"]
4545+```
4646+4747+```ocaml
4848+# r |> S.add range_a |> S.add range_e |> dump
4949+- : string list = ["2026-04-01 .. 2026-04-07"]
5050+```
5151+5252+```ocaml
5353+# r |> S.add range_a |> S.add range_f |> dump
5454+- : string list = ["2026-04-01 .. 2026-04-12"]
5555+```
5656+5757+```ocaml
5858+# r |> S.add range_g |> S.add range_h |> dump
5959+- : string list = ["2026-04-01 .. 2026-04-06"; "2026-04-12 .. 2026-04-30"]
6060+```
6161+6262+```ocaml
6363+# r |> S.add range_g |> S.add range_h |> S.remove range_c |> dump
6464+- : string list = []
6565+```
6666+6767+6868+```ocaml
6969+# r |> S.add range_g |> S.add range_h |> S.remove range_a |> dump
7070+- : string list = ["2026-04-12 .. 2026-04-30"]
7171+```
7272+7373+
+57
lib/desert/range_set.ml
···11+module Make_over_range
22+ (C : Lunar.Sigs.COMPARABLE)
33+ (R : Lunar.Range.S with type elt = C.t) =
44+struct
55+ module S = Set.Make (struct
66+ type t = R.t
77+88+ let compare a b =
99+ let c = C.compare (R.min_elt a) (R.min_elt b) in
1010+ if Int.equal 0 c then C.compare (R.max_elt a) (R.max_elt b) else c
1111+ ;;
1212+ end)
1313+1414+ type elt = R.t
1515+ type t = S.t
1616+1717+ let substract by range =
1818+ match R.intersection by range with
1919+ | None -> [ range ]
2020+ | Some inter ->
2121+ let rmin, rmax = R.bounds range
2222+ and imin, imax = R.bounds inter in
2323+ let acc =
2424+ if C.compare rmin imin = 0
2525+ then []
2626+ else [ R.make ~first:rmin ~last:imin ]
2727+ in
2828+ if C.compare rmax imax = 0
2929+ then acc
3030+ else R.make ~first:imax ~last:rmax :: acc
3131+ ;;
3232+3333+ let empty = S.empty
3434+ let singleton = S.singleton
3535+3636+ let add elt set =
3737+ let overlapping, disjoint = S.partition (R.overlaps elt) set in
3838+ S.add (elt |> S.fold R.span overlapping) disjoint
3939+ ;;
4040+4141+ let remove elt set =
4242+ S.fold
4343+ (fun range acc ->
4444+ let p = substract elt range in
4545+ List.fold_left (fun acc r -> S.add r acc) acc p)
4646+ set
4747+ S.empty
4848+ ;;
4949+5050+ let of_list = List.fold_left (fun acc x -> add x acc) empty
5151+ let of_seq = Seq.fold_left (fun acc x -> add x acc) empty
5252+ let to_list = S.to_list
5353+ let to_seq = S.to_seq
5454+end
5555+5656+module Make (C : Lunar.Sigs.COMPARABLE) =
5757+ Make_over_range (C) (Lunar.Range.Make (C))
+23
lib/desert/range_set.mli
···11+(** Describes a normalized set of disjoint intervals (or more simply a
22+ Merged Interval Set).
33+44+ Unlike a traditional set, this one merges overlapping ranges. *)
55+66+module Make_over_range
77+ (C : Lunar.Sigs.COMPARABLE)
88+ (R : Lunar.Range.S with type elt = C.t) : sig
99+ type elt = R.t
1010+ type t
1111+1212+ val empty : t
1313+ val singleton : elt -> t
1414+ val add : elt -> t -> t
1515+ val remove : elt -> t -> t
1616+ val of_list : elt list -> t
1717+ val of_seq : elt Seq.t -> t
1818+ val to_seq : t -> elt Seq.t
1919+ val to_list : t -> elt list
2020+end
2121+2222+module Make (C : Lunar.Sigs.COMPARABLE) :
2323+ module type of Make_over_range (C) (Lunar.Range.Make (C))