A naive approach that works can be described as follows:
- Start with two sets (lists?) Of points: those that are known to belong to the region,
Region , and the rest, Rest . First, you can select any point that should belong to Region , and all that remains is in Rest . - See
Rest for a point that is a neighbor of any point in a Region.- if you find a neighbor, move it from
Rest to Region and repeat - If you have not found a neighbor, stop
- If at the end there are dots in
Rest , this is not a region.
Here is an easier way to define neighbors/2 :
neighbors([X1,Y1], [X2,Y2]) :- abs(X1-X2) + abs(Y1-Y2) =:= 1.
You can find a point in one list that is a neighbor of a point in another list, just try any possible combination:
% add_to_region(+Region0, +Rest0, -Region, -Rest) %% Look in Rest0 for a neighbor to Region0; %% Region is Region0 with the neighbor, %% Rest is Rest0 without it add_to_region(Region, Rest0, [B|Region], Rest) :- member(A, Region), select(B, Rest0, Rest), neighbors(A, B).
A call to member / 2 selects each point in the region by A, by backtracking. A call to select / 3 selects each point in Rest0 for B, and the remaining points in Rest. If two points are neighbors, B is added to the front of the region.
This will not succeed if there are no more neighbors with the current region in Rest and succeed at least once, if any. You can call it with once(add_to_region(Region0, Rest0, Region, Rest)) so that you don't have unnecessary selection points. Using your examples:
?- once( add_to_region( [[1,1],[1,2],[1,3],[2,1]], [[2,2],[2,3],[3,1],[4,1],[4,2]], Region, Rest)). Region = [[2, 2], [1, 1], [1, 2], [1, 3], [2, 1]], Rest = [[2, 3], [3, 1], [4, 1], [4, 2]].
See how [2,2] was selected from Rest and added to Region .
?- add_to_region( [[1,1],[1,2],[1,3],[1,4],[1,5],[1,6]], [[3,1],[4,1],[4,2]], Region, Rest). false.
This is not done because none of the points in Rest is adjacent to any of the points in Region .
Edit
As explained above, it is definitely doable, but with a little modification, we can have an algorithm that is much easier to implement in Prolog. This happens as follows:
set_region_rest(+Set, -Region, -Rest)
- Sort a set of points to the standard order of terms; you now have an
ordset . - Divide this set into a region and Rest that does not belong to it.
To do the splitting, we will save one additional list. We will call it the list of Open nodes: nodes that we have not yet studied. First, the first element of our input list is the only open node. The remaining elements are transmitted as they are. The last two arguments: the region and the rest, are the output arguments.
open_set_closed_rest(Open, Set, Closed, Rest)
- If the Open set is empty, then the rest of the closed set (now Region); the rest Set - Rest.
- Otherwise:
- Take the first pair of coordinates from the open list; immediately place it in front of the closed coordinates.
- Try to find any of the neighbors of these first pairs in the coordinate set; if you find them, add them to the front of the Open set; the rest of Set after deleting neighbors is the new Set.
- Try again with the new Open set, the rest of the private set, the remaining set, and Rest.
To do this in Prolog, I will first clear the coordinate view. Itβs a little annoying that they are included in the lists of two: itβs much less write if we used the pair instead: [X,Y] ---> XY . To do this, I add this predicate:
xy(XY) :- coordinates(C), maplist([[X,Y], XY]>>true, C, XY). xy([1-1,1-3,1-2]). xy([1-2,2-1,2-2]). xy([1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5]). xy([1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-3, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5]). coordinates([[1,1],[1,2],[1,3],[2,1],[2,2],[2,3],[3,1],[4,1],[4,2]]). coordinates([[1,1],[1,2],[1,3],[1,4],[1,5],[1,6],[3,1],[4,1],[4,2]]).
(I also added 4 additional test suites!)
So I get:
?- xy(XY). XY = [1-1, 1-2, 1-3, 2-1, 2-2, 2-3, 3-1, 4-1, ... - ...] [write] % press 'w' here XY = [1-1, 1-2, 1-3, 2-1, 2-2, 2-3, 3-1, 4-1, 4-2] ; XY = [1-1, 1-2, 1-3, 1-4, 1-5, 1-6, 3-1, 4-1, 4-2] ; XY = [1-1, 1-3, 1-2] ; XY = [1-2, 2-1, 2-2] ; XY = [1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5] ; XY = [1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-3, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5].
Here's how to try to write the above algorithms in code:
set_region_rest([A|As], Region, Rest) :- sort([A|As], [B|Bs]), open_set_closed_rest([B], Bs, Region, Rest).
It just sorted the Set input and separated the first element from it. The first element is the first coordinate pair in the Open set, the rest is Set, then the output arguments.
Now, to split Set into Region and Rest, we need to continue to grow Region, if we have coordinate pairs in the Open set. If the Open set is empty, this means that our region is completed, and the remaining Set is Rest:
:- use_module(library(clpfd)). open_set_closed_rest([], Rest, [], Rest).
To find out which neighboring coordinates are in the set, we use ord_intersection/4 , which gives us the neighbors in Set and the rest of the set at the same time.
NB : 4 neighboring coordinates mapped!
open_set_closed_rest([XY|As], Set, [XY|Closed0], Rest) :- X0
It is he. In doing so, I get the following 6 solutions:
?- xy(XY), set_region_rest(XY, Region, Rest). XY = [1-1, 1-2, 1-3, 2-1, 2-2, 2-3, 3-1, 4-1, 4-2], Region = [1-1, 1-2, 1-3, 2-3, 2-2, 2-1, 3-1, 4-1, 4-2], Rest = [] ; XY = [1-1, 1-2, 1-3, 1-4, 1-5, 1-6, 3-1, 4-1, 4-2], Region = [1-1, 1-2, 1-3, 1-4, 1-5, 1-6], Rest = [3-1, 4-1, 4-2] ; XY = [1-1, 1-3, 1-2], Region = [1-1, 1-2, 1-3], Rest = [] ; XY = [1-2, 2-1, 2-2], Region = [1-2, 2-2, 2-1], Rest = [] ; XY = [1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5], Region = [1-1, 1-2, 1-3, 1-4, 1-5, 2-5, 3-5, 4-5, 5-5, 5-4, 5-3, 5-2, 5-1, 4-1, 3-1, 2-1], Rest = [] ; XY = [1-1, 1-2, 1-3, 1-4, 1-5, 2-1, 2-5, 3-1, 3-3, 3-5, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5], Region = [1-1, 1-2, 1-3, 1-4, 1-5, 2-5, 3-5, 4-5, 5-5, 5-4, 5-3, 5-2, 5-1, 4-1, 3-1, 2-1], Rest = [3-3].
By the way, using set_region_rest/3 as a building block, we can easily split the set of coordinates into regions:
set_regions([], []). set_regions([X|Xs], [R|Rs]) :- set_region_rest([X|Xs], R, Rest), set_regions(Rest, Rs).
So now:
?- set_regions([1-1, 1-2, 1-3, 1-4, 1-5, 1-7, 2-1, 2-5, 2-7, 3-1, 3-3, 3-5, 3-7, 4-1, 4-5, 5-1, 5-2, 5-3, 5-4, 5-5, 5-7], R). R = [[1-1, 1-2, 1-3, 1-4, 1-5, 2-5, 3-5, 4-5, 5-5, 5-4, 5-3, 5-2, 5-1, 4-1, 3-1, 2-1], [1-7, 2-7, 3-7], [3-3], [5-7]].