To extend on the existing answer, I think it is better if the scanner doesn't expect a newline at the beginning ("\n%d %d"
) or the end ("%d %d\n"
) of the pattern, because typically you read data from a stream that doesn't start with a newline and may not end with a newline (e.g. end of file).
I'd suggest splitting the input into lines and scanning them individually. Also, it is preferable to define smaller functions that do one thing each instead of trying to mix everything in a single big function.
For example, first let's define a function that scans two integers:
# let scan_couple str =
Scanf.sscanf str "%d %d" (fun a b -> (a, b))
val scan_couple : string -> int * int = <fun>
It works as follows:
# scan_couple "42 69";;
- : int * int = (42, 69)
Nice, now let's define a function that scans the next couple from a channel, assuming couples are entries separated by newlines:
# let scan_next_couple c =
match (In_channel.input_line c) with
None -> None
| Some line -> Some (scan_couple line);;
val scan_next_couple : In_channel.t -> (int * int) option = <fun>
Hopefully each definition is a bit simpler to understand.
You still have to handle exceptions if an entry does not match the scanner format, etc. You may want to handle all the possible exceptions listed in the Scanf manual:
let scan_next_couple c =
match (In_channel.input_line c) with
| None -> None
| Some line ->
try Some (scan_couple line) with
| Scanf.Scan_failure _
| Failure _
| End_of_file
| Invalid_argument _ -> None
But then you cannot distinguish between an end of file and a single line that is malformed, which can be a problem. Depending on how much effort you want to spend on it, you can be more or less robust to errors here (e.g. maybe wrap option
values in a result
type, or define another type).