Let S = [x1, x2, ..., xn] be a random sequence of distinct numbers. We can define a run of S as:
- a sequence [x1, x2, ..., xi], for 1 ≤ i ≤ n, such that x1 < x2 < ... < xi, and i = n or xi > xi+1; or
- a sequence [xi, xi-1, ..., x1], for 1 ≤ i ≤ n, such that x1 > x2 > ... > xi, and i = n or xi < xi+1.
The predicate runs/2 collects all runs of a given sequence of distinct numbers (for successive non-overlapping ascending or descending prefixes).
% runs(+Sequence, -Runs)
runs([], []).
runs([X|Xs], [Run|Runs]) :-
run(Xs, X, Run, Rest),
runs(Rest, Runs).
run([], X, [X], []).
run([X1|Xs], X0, Run, Rest) :-
compare(Order, X0, X1),
run_case(Order, X0, X1, Xs, Run, Rest).
run_case(<, X0, X1, Xs, [X0|Run], Rest) :-
ascending(Xs, X1, Run, Rest).
run_case(>, X0, X1, Xs, Run, Rest) :-
descending(Xs, X1, [X0], Run, Rest).
% for an ascending prefix, the run is built up as a QUEUE
ascending([], X0, [X0], []).
ascending([X1|Xs], X0, [X0|Run0], Rest) :-
( X0 @> X1
-> Run0 = [],
Rest = [X1|Xs]
; ascending(Xs, X1, Run0, Rest) ).
% for a descending prefix, the run is built up as a STACK
descending([], X0, Run, [X0|Run], []).
descending([X1|Xs], X0, Run0, Run, Rest) :-
( X0 @< X1
-> Run = [X0|Run0],
Rest = [X1|Xs]
; descending(Xs, X1, [X0|Run0], Run, Rest) ).
Example:
?- runs([1, 2, 9, 8, 5, 3, 7, 6, 4, 0], R).
R = [[1, 2, 9], [3, 5, 8], [0, 4, 6, 7]].
PROBLEM: How to extend this code to deal with sequences containing repeating numbers:
- modifying only the predicates
run/3andrun_case/6(in the latter, the arity can be changed); - without explicitly using the predicate
reverse/2; - without processing the same item more than once;
- using only tail-recursive predicates; and
- the predicate
runs/2must take time O(n).
After the modifications, the predicate run/2 should work as follows:
?- runs([5, 5, 3, 2, 2, 4, 4, 7, 6, 6, 1, 7, 8, 8, 9], Runs).
Runs = [[2, 2, 3, 5, 5], [4, 4, 7], [1, 6, 6], [7, 8, 8, 9]].
MOTIVATION: The predicate runs/2 helps to implement a (bottom-up) version of natural merge sort that takes time O(n) to sort sequences that are already sorted (ascending or descending), as well as greatly reducing the execution time to sort sequences that are already "almost ordered".
I came up with this (ascending and descending remain the same):
Sample runs: