'Longest prefix of an OCaml `string list` ending in a specific `string` value

I am trying to work out whether there is a particularly neat or efficient way of truncating a string after the final occurrence of a specific element. For my purposes, it is a monomorphized string list and the string I am looking for the final (highest index) occurrence of is known at compile-time, since I am only using it in one case.

The motivation for this is to find the nearest ancestor in a Unix directory system of the CWD whose name in its parent is a particular folder name. I.E., if I wanted to find the nearest ancestor called bin and I was running the executable from a CWD of /home/anon/bin/projects/sample/src/bin/foo/, then I would want to get back /home/anon/bin/projects/sample/src/bin

The current implementation I am using is the following:

let reverse_prune : tgt:string -> string -> string =
  let rec drop_until x ys =
    match ys with
    | [] -> []
    | y :: _ when x = y -> ys
    | _ :: yt -> drop_until x yt
  in
  fun ~tgt path ->
    String.split_on_char '/' path
    |> List.rev |> drop_until tgt |> List.rev |> String.concat "/"

It isn't a particularly common or expensive code-path so there isn't actually a real need to optimize, but since I am still trying to learn practical OCaml techniques, I wanted to know if there was a cleaner way of doing this.

I also realize that it may technically be possible to avoid the string-splitting altogether and just operate on the raw CWD string without splitting it. I am, of course, welcome to such suggestions as well, but I am specifically curious if there is something that would replace the List.rev |> drop_until tgt |> List.rev snippet, rather than solve the overall problem in a different way.



Solution 1:[1]

I don't think this has anything to do with OCaml actually since I'd say the easiest way to do this is by using a regular expression:

let reverse_prune tgt path =
  let re =
    Str.regexp (Format.sprintf {|^[/a-zA-Z_-]*/%s\([/a-zA-Z_-]*\)$|} tgt)
  in
  Str.replace_first re {|\1|} path

let () =
  reverse_prune "bin" "/home/anon/bin/projects/sample/src/bin/foo/"
  |> Format.printf "%s@."

Is there a reason you want to reimplement regular expression searching in a string? If no, just use a solution like mine, I'd say.


If you want the part that comes before just change the group:

let reverse_prune tgt path =
  let re =
    Str.regexp (Format.sprintf {|^\([/a-zA-Z_-]*/\)%s[/a-zA-Z_-]*$|} tgt)
  in
  Str.replace_first re {|\1|} path

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1