(** Functions for performing binary searches over ordered sequences given
    [length] and [get] functions.

    These functions can be specialized and added to a data structure using the functors
    supplied in {{!Base.Binary_searchable}[Binary_searchable]} and described in
    {{!Base.Binary_searchable_intf}[Binary_searchable_intf]}.

    {2:examples Examples}

    Below we assume that the functions [get], [length] and [compare] are in scope:

    {[
      (* Find the index of an element [e] in [t] *)
      binary_search t ~get ~length ~compare `First_equal_to e;

      (* Find the index where an element [e] should be inserted *)
      binary_search t ~get ~length ~compare `First_greater_than_or_equal_to e;

      (* Find the index in [t] where all elements to the left are less than [e] *)
      binary_search_segmented t ~get ~length ~segment_of:(fun e' ->
        if compare e' e <= 0 then `Left else `Right) `First_on_right
    ]} *)

open! Import

(** [binary_search ?pos ?len t ~length ~get ~compare which elt] takes [t] that is sorted
    in increasing order according to [compare], where [compare] and [elt] divide [t] into
    three (possibly empty) segments:

    {v
      |  < elt  |  = elt  |  > elt  |
    v}

    [binary_search] returns the index in [t] of an element on the boundary of segments
    as specified by [which].  See the diagram below next to the [which] variants.

    By default, [binary_search] searches the entire [t].  One can supply [?pos] or
    [?len] to search a slice of [t].

    [binary_search] does not check that [compare] orders [t], and behavior is
    unspecified if [compare] doesn't order [t].  Behavior is also unspecified if
    [compare] mutates [t]. *)
val binary_search
  :  ?pos:int
  -> ?len:int
  -> 't
  -> length:(('t -> int)[@local])
  -> get:(('t -> int -> 'elt)[@local])
  -> compare:(('elt -> 'key -> int)[@local])
  -> [ `Last_strictly_less_than (**        {v | < elt X |                       v} *)
     | `Last_less_than_or_equal_to (**     {v |      <= elt       X |           v} *)
     | `Last_equal_to (**                  {v           |   = elt X |           v} *)
     | `First_equal_to (**                 {v           | X = elt   |           v} *)
     | `First_greater_than_or_equal_to (** {v           | X       >= elt      | v} *)
     | `First_strictly_greater_than (**    {v                       | X > elt | v} *)
     ]
  -> 'key
  -> int option

(** [binary_search_segmented ?pos ?len t ~length ~get ~segment_of which] takes a
    [segment_of] function that divides [t] into two (possibly empty) segments:

    {v
      | segment_of elt = `Left | segment_of elt = `Right |
    v}

    [binary_search_segmented] returns the index of the element on the boundary of the
    segments as specified by [which]: [`Last_on_left] yields the index of the last
    element of the left segment, while [`First_on_right] yields the index of the first
    element of the right segment.  It returns [None] if the segment is empty.

    By default, [binary_search] searches the entire [t].  One can supply [?pos] or
    [?len] to search a slice of [t].

    [binary_search_segmented] does not check that [segment_of] segments [t] as in the
    diagram, and behavior is unspecified if [segment_of] doesn't segment [t].  Behavior
    is also unspecified if [segment_of] mutates [t]. *)
val binary_search_segmented
  :  ?pos:int
  -> ?len:int
  -> 't
  -> length:(('t -> int)[@local])
  -> get:(('t -> int -> 'elt)[@local])
  -> segment_of:(('elt -> [ `Left | `Right ])[@local])
  -> [ `Last_on_left | `First_on_right ]
  -> int option
