Prolog convert mins to hours

谁说我不能喝 提交于 2019-12-23 16:56:43

问题


This is the code I have created.

mins_to_hours(In, H, M):-
  In < 60,
  H = 0,
  M is In.
mins_to_hours(In, H, M):-
  In >= 60,
  H is H1+1,
  In1 is In-60,
  mins_to_hours(In1, H1, M).

It works fine when the minutes are less than 60, e.g.

?- mins_to_hours(20,H,M).
H = 0,
M = 20 ;
false.

However when trying to run it for more than 60 minutes

?- mins_to_hours(80,H,M).

it outputs an exception

ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:    [9] _3198 is _3204+1
ERROR:    [8] mins_to_hours(80,_3232,_3234) at c:/.../xyz.pl:11
ERROR:    [7] <user>

at the position H is H1+1,.

Any ideas how to fix this?


回答1:


Here is the corrected version of your code.

mins_to_hours(Minutes_in, H, M) :-
    mins_to_hours_helper(0, Minutes_in, H, M).

mins_to_hours_helper(H0, M0, H0, M0):-
  M0 < 60, !.
mins_to_hours_helper(H0, M0, H, M):-
  M0 >= 60,
  H1 is H0+1,
  M1 is M0-60,
  mins_to_hours_helper(H1, M1, H, M).

The major changes are:

  1. To avoid the error message (Arguments are not sufficiently instantiated) since your code is recursive it needs separate incoming and outgoing variables, i.e. H0 with H1, and M0 with M1.
  2. To be able to use the additional variables necessities adding a helper predicate, i.e. mins_to_hours_helper.
  3. The minutes in and the starting minutes variable are actually the same in the helper predicate.
  4. Being recursive the code creates choice-points, but the answer is expected to be deterministic. This is solved with a cut (!) in the base case.

Here are some test cases (Uses SWI-Prolog):

:- begin_tests(mins_to_hours).

test(-1) :-
    mins_to_hours(-1,H,M),
    assertion(H == 0),
    assertion(M == -1).

test(0) :-
    mins_to_hours(0,H,M),
    assertion(H == 0),
    assertion(M == 0).

test(1) :-
    mins_to_hours(1,H,M),
    assertion(H == 0),
    assertion(M == 1).

test(59) :-
    mins_to_hours(59,H,M),
    assertion(H == 0),
    assertion(M == 59).

test(60) :-
    mins_to_hours(60,H,M),
    assertion(H == 1),
    assertion(M == 0).

test(600) :-
    mins_to_hours(600,H,M),
    assertion(H == 10),
    assertion(M == 0).

test(601) :-
    mins_to_hours(601,H,M),
    assertion(H == 10),
    assertion(M == 1).

:- end_tests(mins_to_hours).

To run the test cases:

?- run_tests.
% PL-Unit: mins_to_hours ....... done
% All 7 tests passed
true.

Note: run_tests. does not work with SWISH,

No permission to call sandboxed `'$current_module'(_4002,_4004)'

so you will have to enter each query manually and manually check the results.

See: sandbox.pl


Now for a much better way to do this.

mins_to_hours(Minutes_in, H, M) :-
    H is Minutes_in // 60,
    M is Minutes_in rem 60.

Notice that this is deterministic, is not recursive, and passes all of the test cases.

See: f-///2 (Integer division) and rem/2 (Remainder of integer division)


Note.

Since you did not specify what should happen when the minutes are negative but did provide code that takes the minutes less than 60 and moves them to the result, this code reproduces that response.

A variation on the code is to use mod/2 instead of rem/2. This will give a different answer depending upon the input, but might be the desired result.


Update based on feedback from @false.

When code is written beyond simple exercises it needs to written with modes in mind.

The original answer code was written as

Declaration: mins_to_hours(++Minutes_in:int, -H:int, -M:int) is det.

meaning that

Minutes_in has to be bound to an integer
H has to be a variable
M has to be a variable

however as @false notes

?- mins_to_hours(Total, H, 1), Total = 61, H = 1.
false.

Declaration: mins_to_hours(-Minutes_in:int, -H:int, +M:int) is det.

?- Total = 61, H = 1, mins_to_hours(Total, H, 1).
Total = 61,
H = 1.

Declaration: mins_to_hours(+Minutes_in:int, +H:int, +M:int) is det.

The first example returns false, (fails) but should have returned true, and the second example returns a valid answer for the same values but in a different mode.

While my answer only works in one mode the answer by Daniel Lyons works with others because it uses constraints.

?- mins_to_hours(Total, H, 1), Total = 61, H = 1.
Total = 61,
H = 1.

So to avoid the first example by @false from returning false which is actually wrong, it should throw an Arguments are not sufficiently instantiated error. @false also notes the simplest way to do this is to

delay unification after the cut

Here is the updated code:

mins_to_hours(Minutes_in, H, M) :-
    mins_to_hours_helper(0, Minutes_in, H, M).

mins_to_hours_helper(H0, M0, H1, M1):-
  M0 < 60, !,
  H0 = H1,
  M0 = M1.
mins_to_hours_helper(H0, M0, H, M):-
  M0 >= 60,
  H1 is H0+1,
  M1 is M0-60,
  mins_to_hours_helper(H1, M1, H, M).

which passes the test cases

?- run_tests.
% PL-Unit: mins_to_hours ....... done
% All 7 tests passed
true.

and gives the ERROR for the first example:

?- mins_to_hours(Total, H, 1), Total = 61, H = 1.
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:   [11] _6584<60
ERROR:   [10] mins_to_hours_helper(0,_6612,_6614,1) at c:/XYZ.pl:23
ERROR:    [8] '<meta-call>'(user:(...,...)) <foreign>
ERROR:    [7] <user>
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.



回答2:


Another version that might be interesting would be one using clpfd:

:- use_module(library(clpfd)).

min_hours(TotalMinutes, Hours, Minutes) :- 
    [TotalMinutes, Hours] ins 0..sup, 
    Minutes in 0..59, 
    TotalMinutes #= Hours*60 + Minutes.

This has the advantage that it works with different instantiations:

?- min_hours(600, Hours, Minutes).
Hours = 10,
Minutes = 0.

?- min_hours(TotalHours, 10, 30).
TotalHours = 630.



回答3:


So much code that I am getting dizzy.

For SWI-Prolog:

mins_to_hours(Min, H, M) :-
    divmod(Min, 60, H, M).

For any Prolog:

mins_to_hours(Min, H, M) :-
    H is Min div 60,
    M is Min mod 60.


来源:https://stackoverflow.com/questions/54688256/prolog-convert-mins-to-hours

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!