/* INTERVALS.PL */ /* Sets as intervals. Written by Jocelyn Paine. */ /* SPECIFICATION. -------------- This file defines predicates for set algebra on sets of integers. Each set is to be thought of as the union of disjoint intervals, and is represented as a list, each element of which is one such interval. PUBLIC intervals_are_disjoint( I1+, I2+ ): Intervals I1 and I2 are disjoint - i.e. have no points in common. PUBLIC intervals_meet( I1+, I2+ ): Intervals I1 and I2 are not disjoint - i.e. have at least one point in common. PUBLIC interval_union( I1+, I2+, I3- ): I3 is that interval which is the union of intervals I1 and I2. Only defined if I1 and I2 are coalescable. PUBLIC interval_intersection( I1+, I2+, I3- ): I3 is that interval which is the intersection of intervals I1 and I2. Only defined if I1 and I2 are not disjoint. PUBLIC interval_difference( I1+, I2+, I3- ): I3 is that I-set which is the difference of intervals I1 and I2. PUBLIC interval_is_less_and_not_coalescable( I1+, I2+ ): Interval I1 is strictly less than I2; and I1 union I2 can not be expressed as one interval. I.e. the upper bound of I1 is at least 2 less than the lower bound of I2. PUBLIC interval_is_less( I1+, I2+ ): Interval I1 is strictly less than I2. I.e. there is no point P2 in I2 such that I1 contains a point greater than or equal to P2. |-----| |----| I1 I2 interval_is_less( I1, I2 ) implies disjoint( I1, I2 ). PUBLIC intervals_are_not_coalescable( I1+, I2+ ): True if I1 union I2 can not be expressed as one interval. PUBLIC intervals_are_coalescable( I1+, I2+ ): True if I1 union I2 can be expressed as one interval. PUBLIC interval_ends_first( I1+, I2+ ): The upper bound of I1 is less than that of I2. PUBLIC interval_includes( I1+, I2+ ): I1 is a superset of (or equal to) I2. PUBLIC interval_contains( I1+, P+ ): P is a member of I1. PUBLIC iset_union( A+, B+, U- ): U is the union of A and B, where A, B, and U are I-sets. PUBLIC iset_intersection( A+, B+, I- ): U is the intersection of A and B, where A, B, and I are I-sets. PUBLIC iset_difference( A+, B+, D- ): U is the difference of A and B, where A, B, and D are I-sets. PUBLIC iset_complement( I+, A+, C- ): C is the complement of I-set A in interval I. PUBLIC canonical_iset( S+, C- ): S is a list of intervals. They can be in any order, and need not be disjoint. C is the corresponding I-set. PUBLIC iset_includes( I1+, I2+ ): I1 is a superset of (or equal to) I2. PUBLIC iset_contains( I1+, P+ ): P is a member of I-set I1. IMPLEMENTATION. --------------- The intervals are represented as L -- U where L and U are integers, and L must be less than or equal to U. Note that 10--9 (corresponding to the null set) is not a valid interval, and the predicates will not cope with it. I call the sets of intervals I-sets. Their representation has these properties: (1) [] is an I-set. (2) [ I ] is an I-set, for any interval I. (3) [ I1, I2, ... In ] is an I-set if: (3.1) All Ii, Ij are disjoint. (3.2) For any pair of adjacent intervals, Ii and Ii+1, the upper bound of I1 is less than the lower bound of I2. This ordering makes the I-sets more efficient to process. (3.3) For any pair of adjacent intervals, Ii and Ii+1, the upper bound of I1 is at least 2 less than the lower bound of I2. This enforces a unique representation, prohibiting I-sets like [ 1--9, 10--12 ] and [ 1--3, 4--12 ] being equivalent to [ 1--12 ] Proofs of correctness: I'll summarise these for iset_union, iset_intersection, iset_difference, and iset_complement, since those predicates are rather tricky. We can divide each proof into five parts: (1) Proof that the predicates terminate on correct arguments. (2) Proof that the result is algebraically correct (even though it may not be a valid I-set). (3) Proof that the result's intervals are disjoint. (4) Proof that the result's intervals are ordered in ascending order. (5) Proof that the result contains no adjacent pairs like 1--2,3--4 which should be coalesced. The last three correspond to proving that the result is a canonical I-set. (1) Proof of termination. This is easy. Sets are represented as lists. We can show that on each recursive call, the sets grow smaller - either because a whole interval is removed from a list, or because it is reduced in size. (2) Proof of algebraic correctness. For 'iset_union', this is trivial, because the only operations used in the tail of 'iset_union' are set unions. The operation [H|T] on an I-set corresponds to H u T. The predicates 'interval_join', and 'union_overlap' also correspond to union. All we need do is show that the union denoted by the head of each clause is equal to that denoted by its tail. For 'iset_intersection', we use the distributive law: (A1 u A2 u A3...) /\ (B1 u B2 u B3... ) = (A1 /\ B1 ) u (A2 /\ B1 ) u (A1 /\ B2) u (A2 /\ B2) ... We can optimise by noting that Ai/\Bi = {} if Ai B, !. max( A, B, B ). /* min( A+, B+, M- ): M is the minimum of A and B. */ min( A, B, A ) :- A < B, !. min( A, B, B ). /* INTERVALS and I-SETS */ /* L--U is the interval [L,U] : i.e. that containing L and U as endpoints. L and U must be integers, not reals. */ ?- op( 31, xfx, -- ). intervals_are_disjoint( L1--U1, L2--U2 ) :- U1 < L2, !. intervals_are_disjoint( L1--U1, L2--U2 ) :- U2 < L1, !. intervals_meet( I1, I2 ) :- not( intervals_are_disjoint(I1,I2) ). interval_union( L1--U1, L2--U2, L3--U3 ) :- min( L1, L2, L3 ), max( U1, U2, U3 ). interval_intersection( L1--U1, L2--U2, L3--U3 ) :- max( L1, L2, L3 ), min( U1, U2, U3 ). interval_difference( I1, I2, [I1] ) :- intervals_are_disjoint( I1, I2 ), !. interval_difference( L1--U1, L2--U2, [ L1--L2_less_1, U2_add_1--U1 ] ) :- L1 < L2, U1 > U2, U2_add_1 is U2 + 1, L2_less_1 is L2 - 1, !. interval_difference( L1--U1, L2--U2, [ L1--L2_less_1 ] ) :- L1 < L2, U1 =< U2, L2_less_1 is L2 - 1, !. interval_difference( L1--U1, L2--U2, [ U2_add_1--U1 ] ) :- L1 >= L2, U1 > U2, U2_add_1 is U2 + 1, !. interval_difference( L1--U1, L2--U2, [] ) :- L1 >= L2, U1 =< U2, !. interval_is_less_and_not_coalescable( L1--U1, L2--U2 ) :- L2_less_1 is L2-1, U1 < L2_less_1, !. interval_is_less( L1--U1, L2--U2 ) :- U1 < L2, !. intervals_are_not_coalescable( I1, I2 ) :- interval_is_less_and_not_coalescable( I1, I2 ), !. intervals_are_not_coalescable( I1, I2 ) :- interval_is_less_and_not_coalescable( I2, I1 ), !. intervals_are_coalescable( I1, I2 ) :- not( intervals_are_not_coalescable( I1, I2 ) ). interval_ends_first( _--U1, _--U2 ) :- U1 < U2, !. interval_includes( L1--U1, L2--U2 ) :- L1 =< L2, U1 >= U2. interval_contains( L1--U1, P ) :- L1 =< P, U1 >= P. iset_union( A, [], A ) :- !. iset_union( [], B, B ) :- !. iset_union( [A1|A2_n], [B1|B2_n], [B1|URest] ) :- interval_is_less_and_not_coalescable( B1, A1 ), iset_union( [A1|A2_n], B2_n, URest ), !. iset_union( [A1|A2_n], [B1|B2_n], [A1|URest] ) :- interval_is_less_and_not_coalescable( A1, B1 ), iset_union( A2_n, [B1|B2_n], URest ), !. iset_union( [A1|A2_n], [B1|B2_n], U ) :- /* A1 overlaps B1 */ interval_union( A1, B1, U1 ), union_overlap( U1, A2_n, B2_n, U ). union_overlap( U1, [A1|A2_n], B, URest ) :- intervals_are_coalescable( U1, A1 ), interval_union( U1, A1, J ), union_overlap( J, A2_n, B, URest ), !. union_overlap( U1, A, [B1|B2_n], URest ) :- intervals_are_coalescable( U1, B1 ), interval_union( U1, B1, J ), union_overlap( J, A, B2_n, URest ), !. union_overlap( U1, A, B, [U1|URest] ) :- iset_union( A, B, URest ), !. iset_intersection( A, [], [] ) :- !. iset_intersection( [], B, [] ) :- !. iset_intersection( [A1|A2_n], [B1|B2_n], IRest ) :- interval_is_less( A1, B1 ), iset_intersection( A2_n, [B1|B2_n], IRest ), !. iset_intersection( [A1|A2_n], [B1|B2_n], IRest ) :- interval_is_less( B1, A1 ), iset_intersection( [A1|A2_n], B2_n, IRest ), !. iset_intersection( [A1|A2_n], [B1|B2_n], [I1|IRest] ) :- interval_intersection( A1, B1, I1 ), drop_lowest( [A1|A2_n], [B1|B2_n], A_Rest, B_Rest ), iset_intersection( A_Rest, B_Rest, IRest ). drop_lowest( [I|A2_n], [I|B2_n], A2_n, B2_n ) :- !. drop_lowest( [A1|A2_n], [B1|B2_n], A2_n, [B1|B2_n] ) :- interval_ends_first( A1, B1 ), !. drop_lowest( A, [B1|B2_n], A, B2_n ). iset_difference( A, [], A ) :- !. iset_difference( [], B, [] ) :- !. iset_difference( [A1|A2_n], [B1|B2_n], [A1|DRest] ) :- interval_is_less( A1, B1 ), iset_difference( A2_n, [B1|B2_n], DRest ), !. iset_difference( [A1|A2_n], [B1|B2_n], DRest ) :- interval_is_less( B1, A1 ), iset_difference( [A1|A2_n], B2_n, DRest ), !. iset_difference( [A1_low--A1_high|A2_n], [B1_low--B1_high|B2_n], [ A1_low--B1_low_less_1 | D_Rest ] ) :- A1_low < B1_low, !, B1_low_less_1 is B1_low - 1, iset_difference( [ B1_low--A1_high | A2_n ], [ B1_low--B1_high | B2_n ], D_Rest ), !. iset_difference( [A1_low--High|A2_n], [B1_low--High|B2_n], D ) :- iset_difference( A2_n, B2_n, D ), !. iset_difference( [A1_low--A1_high|A2_n], [B1_low--B1_high|B2_n], D ) :- A1_high > B1_high, !, B1_high_add_1 is B1_high + 1, iset_difference( [ B1_high_add_1--A1_high | A2_n ], B2_n, D ), !. iset_difference( [A1_low--A1_high|A2_n], [B1_low--B1_high|B2_n], D ) :- A1_high < B1_high, !, A1_high_add_1 is A1_high + 1, iset_difference( A2_n, [ A1_high_add_1--B1_high | B2_n ], D ), !. iset_complement( L--U, A, C ) :- iset_complement( L, U, A, C ). iset_complement( L, U, _, [] ) :- L > U, !. iset_complement( L, U, [], [L--U] ) :- !. iset_complement( L, U, [ A1_low--A1_high | A2_n ], C ) :- A1_high < L, !, /* A1 disjoint from and less than I. */ iset_complement( L, U, A2_n, C ). iset_complement( L, U, [ A1_low--_ | _ ], [L--U] ) :- A1_low > U, /* A1 disjoint from and greater than I. */ !. iset_complement( L, U, [ A1_low--A1_high | A2_n ], [ L -- A1_low_less_1 | C_Rest ] ) :- A1_low > L, !, /* A1 greater than lower end of I. */ A1_low_less_1 is A1_low - 1, A1_high_add_1 is A1_high + 1, iset_complement( A1_high_add_1, U, A2_n, C_Rest ). iset_complement( L, U, [ A1_low--A1_high | A2_n ], C_Rest ) :- A1_low =< L, !, /* A1 overlaps lower end of I. */ A1_high_add_1 is A1_high + 1, iset_complement( A1_high_add_1, U, A2_n, C_Rest ). canonical_iset( [], [] ) :- !. canonical_iset( [X|L], U ) :- canonical_iset( L, LC ), iset_union( [X], LC, U ). iset_includes( _, [] ) :- !. iset_includes( [A1|A2_n], [B1|B2_n] ) :- interval_is_less( A1, B1 ), !, iset_includes( A2_n, [B1|B2_n] ). iset_includes( [A1|A2_n], [B1|B2_n] ) :- interval_includes( A1, B1 ), iset_includes( [A1|A2_n], B2_n ), !. iset_contains( [A1|_], P ) :- interval_contains( A1, P ), !. iset_contains( [_|A2_n], P ) :- iset_contains( A2_n, P ), !.